gpt4 book ai didi

c++ - 使用 GoogleTest 测试私有(private)方法的最佳方法是什么?

转载 作者:IT老高 更新时间:2023-10-28 12:48:13 30 4
gpt4 key购买 nike

关闭。这个问题是 opinion-based 。它目前不接受答案。












想改善这个问题吗?更新问题,以便可以通过 editing this post 用事实和引文来回答。

3年前关闭。



Improve this question




我想使用 GoogleTest 测试一些私有(private)方法。

class Foo
{
private:
int bar(...)
}

GoogleTest 允许使用几种方法来执行此操作。

选项 1

与 FRIEND_TEST:

class Foo
{
private:
FRIEND_TEST(Foo, barReturnsZero);
int bar(...);
}

TEST(Foo, barReturnsZero)
{
Foo foo;
EXPECT_EQ(foo.bar(...), 0);
}

这意味着在生产源文件中包含“gtest/gtest.h”。

选项 2

声明一个测试夹具作为类的 friend 并在夹具中定义访问器:

class Foo
{
friend class FooTest;
private:
int bar(...);
}

class FooTest : public ::testing::Test
{
protected:
int bar(...) { foo.bar(...); }
private:
Foo foo;
}

TEST_F(FooTest, barReturnsZero)
{
EXPECT_EQ(bar(...), 0);
}

选项 3

Pimpl 成语。

详情: Google Test: Advanced guide

还有其他方法可以测试私有(private)方法吗?每个选项的优缺点是什么?

最佳答案

至少还有两个选择。我将通过解释某种情况列出一些您应该考虑的其他选项。

选项 4:

考虑重构您的代码,以便您要测试的部分在另一个类中是公开的。通常,当您想测试一个类的私有(private)方法时,这是一个糟糕设计的迹象。我看到的最常见的(反)模式之一是 Michael Feathers 所说的“冰山”类。 “冰山”类有一个公共(public)方法,其余的都是私有(private)的(这就是测试私有(private)方法很诱人的原因)。它可能看起来像这样:

RuleEvaluator (stolen from Michael Feathers)

例如,您可能希望通过在字符串上连续调用 GetNextToken() 并查看它返回预期结果来测试它。像这样的函数确实需要进行测试:这种行为并非微不足道,特别是如果您的标记规则很复杂。让我们假设它不是那么复杂,我们只想用空间分隔标记。所以你写了一个测试,也许它看起来像这样:

TEST(RuleEvaluator, canParseSpaceDelimtedTokens)
{
std::string input_string = "1 2 test bar";
RuleEvaluator re = RuleEvaluator(input_string);
EXPECT_EQ(re.GetNextToken(), "1");
EXPECT_EQ(re.GetNextToken(), "2");
EXPECT_EQ(re.GetNextToken(), "test");
EXPECT_EQ(re.GetNextToken(), "bar");
EXPECT_EQ(re.HasMoreTokens(), false);
}

嗯,这实际上看起来很不错。我们希望确保在进行更改时保持这种行为。但是 GetNextToken() 是私有(private)函数!所以我们不能像这样测试它,因为它甚至不会编译。但是如何更改 RuleEvaluator 类以遵循单一职责原则(Single Responsibility Principle)呢?例如,我们似乎将解析器、分词器和求值器集中在一个类中。把这些责任分开不是更好吗?最重要的是,如果您创建一个 Tokenizer 类,那么它的公共(public)方法将是 HasMoreTokens()GetNextTokens()RuleEvaluator 类可以有一个 Tokenizer 对象作为成员。现在,我们可以保持与上面相同的测试,除了我们正在测试 Tokenizer 类而不是 RuleEvaluator 类。

下面是它在 UML 中的样子:

Refactored RuleEvaluator class

请注意,这种新设计增加了模块化,因此您可能会在系统的其他部分重用这些类(在此之前,私有(private)方法根​​据定义是不可重用的)。这是分解 RuleEvaluator 的主要优点,同时提高了可理解性/局部性。

该测试看起来非常相似,但这次实际上会编译,因为 GetNextToken() 方法现在在 Tokenizer 类上是公开的:
TEST(Tokenizer, canParseSpaceDelimtedTokens)
{
std::string input_string = "1 2 test bar";
Tokenizer tokenizer = Tokenizer(input_string);
EXPECT_EQ(tokenizer.GetNextToken(), "1");
EXPECT_EQ(tokenizer.GetNextToken(), "2");
EXPECT_EQ(tokenizer.GetNextToken(), "test");
EXPECT_EQ(tokenizer.GetNextToken(), "bar");
EXPECT_EQ(tokenizer.HasMoreTokens(), false);
}

选项 5

只是不要测试私有(private)函数。有时它们不值得测试,因为它们将通过公共(public)接口(interface)进行测试。很多时候我看到的是看起来非常相似的测试,但是测试两个不同的功能/方法。最终会发生的事情是,当需求发生变化时(他们总是这样做),你现在有 2 个损坏的测试而不是 1 个。如果你真的测试了所有私有(private)方法,你可能会有更多的 10 个损坏的测试而不是 1 个。 简而言之,测试可以通过公共(public)接口(interface)测试的私有(private)函数(通过使用 FRIEND_TEST 或将它们公开)会导致测试重复 。你真的不想要这个,因为没有什么比你的测试套件拖慢你更伤人的了。它应该减少开发时间并降低维护成本!如果您测试通过公共(public)接口(interface)以其他方式测试的私有(private)方法,则测试套件很可能会反其道而行之,并积极增加维护成本并增加开发时间。当您将私有(private)函数设为公开时,或者如果您使用诸如 FRIEND_TEST 之类的东西,您通常最终会后悔。

考虑以下 Tokenizer 类的可能实现:

Possible impl of Tokenizer

假设 SplitUpByDelimiter() 负责返回一个 std::vector<std::string>,这样 vector 中​​的每个元素都是一个标记。此外,让我们说 GetNextToken() 只是这个 vector 上的一个迭代器。所以你的测试可能是这样的:
TEST(Tokenizer, canParseSpaceDelimtedTokens)
{
std::string input_string = "1 2 test bar";
Tokenizer tokenizer = Tokenizer(input_string);
EXPECT_EQ(tokenizer.GetNextToken(), "1");
EXPECT_EQ(tokenizer.GetNextToken(), "2");
EXPECT_EQ(tokenizer.GetNextToken(), "test");
EXPECT_EQ(tokenizer.GetNextToken(), "bar");
EXPECT_EQ(tokenizer.HasMoreTokens(), false);
}

// Pretend we have some class for a FRIEND_TEST
TEST_F(TokenizerTest, canGenerateSpaceDelimtedTokens)
{
std::string input_string = "1 2 test bar";
Tokenizer tokenizer = Tokenizer(input_string);
std::vector<std::string> result = tokenizer.SplitUpByDelimiter(" ");
EXPECT_EQ(result.size(), 4);
EXPECT_EQ(result[0], "1");
EXPECT_EQ(result[1], "2");
EXPECT_EQ(result[2], "test");
EXPECT_EQ(result[3], "bar");
}

好吧,现在假设需求发生了变化,您现在需要通过“,”而不是空格进行解析。自然地,您会期望一个测试失败,但是当您测试私有(private)函数时,痛苦会增加。 IMO,谷歌测试不应允许 FRIEND_TEST。这几乎从来都不是你想做的。 Michael Feathers 将 FRIEND_TEST 之类的东西称为“摸索工具”,因为它试图触摸别人的隐私部位。

我建议您尽可能避免使用选项 1 和选项 2,因为它通常会导致“测试重复”,因此,当需求发生变化时,会中断比必要更多的测试。将它们用作最后​​的手段。 选项 1 和 2 是现在和现在“测试私有(private)方法”的最快方法(如实现最快),但从长远来看,它们确实会损害生产力。

PIMPL 也很有意义,但它仍然允许一些非常糟糕的设计。小心点。

我推荐选项 4(重构为更小的可测试组件)作为正确的起点,但有时您真正想要的是选项 5(通过公共(public)接口(interface)测试私有(private)函数)。

附言这是关于冰山类(class)的相关讲座: https://www.youtube.com/watch?v=4cVZvoFGJTU

附言至于软件中的一切,答案是视情况而定。没有一种尺寸适合所有人。解决您问题的选项将取决于您的具体情况。

关于c++ - 使用 GoogleTest 测试私有(private)方法的最佳方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47354280/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com