C++ 使用GoogleTest测试私有方法的最佳方法是什么?

C++ 使用GoogleTest测试私有方法的最佳方法是什么?,c++,unit-testing,private,googletest,C++,Unit Testing,Private,Googletest,我想用GoogleTest测试一些私有方法 class-Foo { 私人: 整型条(…) } GoogleTest允许几种方法来实现这一点 选项1 通过朋友测试: class-Foo { 私人: FRIEND_测试(Foo,barReturnsZero); 整数条(…); } 测试(Foo、barReturnsZero) { 富富,; 预期均衡(食物棒(…),0); } 这意味着在生产源文件中包含“gtest/gtest.h” 选项2 将测试夹具声明为类的朋友,并在夹具中定义访问器: cla

我想用GoogleTest测试一些私有方法

class-Foo
{
私人:
整型条(…)
}
GoogleTest允许几种方法来实现这一点

选项1

通过朋友测试:

class-Foo
{
私人:
FRIEND_测试(Foo,barReturnsZero);
整数条(…);
}
测试(Foo、barReturnsZero)
{
富富,;
预期均衡(食物棒(…),0);
}
这意味着在生产源文件中包含“gtest/gtest.h”

选项2

将测试夹具声明为类的朋友,并在夹具中定义访问器:

class-Foo
{
朋友级足尖;
私人:
整数条(…);
}
类FooTest:public::testing::Test
{
受保护的:
intbar(…){foo.bar(…);}
私人:
富富,;
}
测试F(最脚部,巴雷特恩斯泽罗)
{
期望q(bar(…),0);
}
选项3

皮姆普尔成语

有关详细信息:


还有其他方法来测试私有方法吗?每个选项的优缺点是什么?

至少还有两个选项。我将列出一些其他的选择,你应该通过解释某个情况来考虑。< /P> 选项4:

考虑重构代码,以便要测试的部分在另一个类中是公共的。通常,当您试图测试类的私有方法时,这是设计糟糕的标志。我所看到的最常见的(反)模式之一是迈克尔·费瑟所谓的“冰山”类。“冰山”类有一个公共方法,其余的是私有的(这就是为什么测试私有方法的原因)。它可能看起来像这样:

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()
。像这样的函数确实需要进行测试:这种行为不是微不足道的,特别是当您的标记化规则很复杂时。让我们假设它没有那么复杂,我们只想用空格分隔标记。所以你写了一个测试,可能看起来像这样:

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()
是一个私有函数!所以我们不能像这样测试它,因为它甚至不会编译。但是将
规则评估器
类更改为遵循单一责任原则(Single Responsibility Principle)怎么样?例如,我们似乎将解析器、标记器和求值器塞进了一个类中。把这些责任分开不是更好吗?除此之外,如果您创建一个
标记器
类,那么它的公共方法将是
HasMoreTokens()
GetNextTokens()
RuleEvaluator
类可以有一个
Tokenizer
对象作为成员。现在,我们可以保持与上面相同的测试,除了测试
Tokenizer
类,而不是
RuleEvaluator

在UML中,它可能是这样的:

请注意,这种新设计增加了模块性,因此您可以在系统的其他部分潜在地重用这些类(在您不能重用之前,私有方法在定义上是不可重用的)。这是将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

只是不要测试私有函数。有时它们不值得测试,因为它们将通过公共接口进行测试。很多时候我看到的是看起来非常相似的测试,但是测试两个不同的函数/方法。最终发生的是,当需求发生变化时(它们总是这样),您现在有2个中断的测试,而不是1个。如果你真的测试了你所有的私有方法,你可能会有10个失败的测试而不是1个简而言之,测试可能通过公共接口进行测试的私有函数(通过使用
FRIEND\u TEST
或将其公开)会导致测试重复。你真的不想这样,因为没有什么比你的测试套件更让你慢下来了。它应该减少开发时间和维护成本!如果您测试通过公共接口测试的私有方法,那么测试套件很可能会做相反的事情,并积极增加维护成本和增加开发时间。当你将一个私有函数公之于众,或者如果你使用了类似于
FRIEND\u TEST
的东西,你通常会后悔

考虑
标记器
类的以下可能实现:

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);
}

假设
SplitUpByDelimiter()
负责返回
std::vector
,这样向量中的每个元素都是一个标记。此外,让我们假设
GetNextToken()
只是这个向量上的迭代器。因此,您的测试可能如下所示:

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");
}
TEST(标记器,canParseSpaceDelimtedTokens)
{
std::string input_string=“1 2测试条”;
标记器标记器=标记器(输入字符串);
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);
}
//假装我们有课要考朋友
测试F(标记化测试,canGenerateSpaceDelimtedTokens)
{
std::string input_string=“1 2测试条”;
标记器标记器=标记器(输入字符串);
std::vector result=tokenizer.SplitUpByDelimiter(“”);
EXPECT_EQ(result.size(),4);
期望(结果[0],“1”);
期望(结果[1],“2”);
预期(结果[2],“测试”);