GoogleTest(Fatal assertion)-5
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GoogleTest(Fatal assertion)-5相关的知识,希望对你有一定的参考价值。
参考技术AFatal assertion翻译过来就是致命断言,指的是程序崩溃。通常在测试过程中,我们需要考虑各种各样的输入,有的输入可能直接导致程序崩溃,这时就需要检查程序是否按照预期的方式挂掉。googletest的死亡测试能做到在一个安全的环境下执行崩溃的测试案例,同时又对崩溃结果进行验证。
由于有些异常只在Debug下抛出,因此还提供了*_DEBUG_DEATH,用来处理Debug和Realease下的不同。
编写致命断言测试案例时,TEST的第一个参数,即testcase_name,请使用DeathTest后缀。原因是googletest会优先运行死亡测试案例,应该是为线程安全考虑。
Debug版和Release版本下, *_DEBUG_DEATH的定义不一样。因为很多异常只会在Debug版本下抛出,而在Realease版本下不会抛出,所以针对Debug和Release分别做了不同的处理。
例:
注:\\f:换页。将当前位置移到下一页开头。
\\n:回车换行。将当前位置移到下一行开头。
\\r:回车。将当前位置移到本行开头。
\\t:水平制表符。将当前位置移到下一个Tab位置。
\\v:垂直制表符。直接跳到下一行的当前位置。
googletest有两种宏,用来表示当前系统支持哪套正则表达式风格:
文章参考: https://www.cnblogs.com/coderzh/archive/2009/04/08/1432043.html
使用 GoogleTest 测试私有方法的最佳方法是啥? [关闭]
【中文标题】使用 GoogleTest 测试私有方法的最佳方法是啥? [关闭]【英文标题】:What is the best way of testing private methods with GoogleTest? [closed]使用 GoogleTest 测试私有方法的最佳方法是什么? [关闭] 【发布时间】:2018-05-01 10:40:36 【问题描述】:我想使用 GoogleTest 测试一些私有方法。
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
声明一个测试夹具作为类的朋友并在夹具中定义访问器:
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。
还有其他方法可以测试私有方法吗?每个选项的优缺点是什么?
【问题讨论】:
一般来说,您不应该直接对私有方法进行单元测试。相反,您应该为调用方法编写更多测试,以涵盖各种边缘情况。 我已经更新了问题和我的答案,使其不再那么“基于意见”。我认为它应该保持开放,因为可能还有其他未列举的解决这个问题的方法。此外,OP表示他们发现答案很有用。 OP 也可能会发现另一个有用的答案。 @SebastianLenartowicz 不过,直接测试私有(或受保护!)方法通常更简单、更清晰。来自班级用户的 POV 的封装可能与来自班级测试人员的 POV 不同。单元测试是细粒度的。如果代码足够复杂,可以分离成自己的函数(私有或非私有),那么它应该有自己的单元测试。 【参考方案1】:至少还有两个选项。我将通过解释特定情况列出您应该考虑的其他一些选项。
选项 4:
考虑重构您的代码,以便您要测试的部分在另一个类中是公开的。通常,当您想测试一个类的私有方法时,这是一个糟糕设计的标志。我看到的最常见的(反)模式之一是 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()
是一个私有 函数!所以我们不能像这样测试它,因为它甚至不会编译。但是更改RuleEvaluator
类以遵循单一职责原则(Single Responsibility Principle)呢?例如,我们似乎将解析器、标记器和评估器塞进了一个类。把这些责任分开不是更好吗?最重要的是,如果您创建一个Tokenizer
类,那么它的公共方法将是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_TEST
或将它们公开)会导致测试重复。你真的不想要这个,因为没有什么比你的测试套件减慢你的速度更伤人的了。它应该减少开发时间并降低维护成本!如果您测试通过公共接口测试的私有方法,测试套件可能会做相反的事情,并积极增加维护成本并增加开发时间。当你公开一个私有函数时,或者如果你使用类似FRIEND_TEST
的东西,你通常会后悔。
考虑以下Tokenizer
类的可能实现:
假设SplitUpByDelimiter()
负责返回一个std::vector<std::string>
,这样向量中的每个元素都是一个标记。此外,假设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");
好吧,现在假设需求发生了变化,您现在需要用“,”而不是空格来解析。自然地,你会期望一个测试会失败,但是当你测试私有函数时痛苦会增加。 IMO,谷歌测试不应该允许 FRIEND_TEST。这几乎从来都不是你想做的。 Michael Feathers 将FRIEND_TEST
之类的东西称为“摸索工具”,因为它试图触碰别人的私处。
我建议尽可能避免使用选项 1 和 2,因为它通常会导致“测试重复”,因此,当需求发生变化时,更多的测试会中断。将它们用作最后的手段。 选项 1 和 2 是目前“测试私有方法”的最快方法(以最快的速度实施),但从长远来看,它们确实会损害生产力。
PIMPL 也可以,但它仍然允许一些非常糟糕的设计。小心点。
我推荐选项 4(重构为更小的可测试组件)作为正确的起点,但有时您真正想要的是选项 5(通过公共接口测试私有函数)。
附:这是有关冰山课程的相关讲座:https://www.youtube.com/watch?v=4cVZvoFGJTU
附言至于软件中的一切,答案是视情况而定。没有一种尺寸适合所有人。解决您的问题的选项将取决于您的具体情况。
【讨论】:
这是一个很好的答案,我已将该视频添加到我的观看列表中。但是,有一个部分让我感到困扰,即使它与您的观点完全相切:您能否在示例测试结束时添加一个检查HasMoreTokens
返回 false
(以及 EXPECT_EQ(result.size(), 4)
用于 canGenerateSpaceDelimitedTokens
)?
@DanielH 当然!好的建议。完整性很重要。不错的收获!
@mwm314 非常感谢您广泛而详细的回答以及您花费时间编写它!这是我的第一个 Stack Overflow 问题,我认为这些答案确实有助于知识共享。我实际上同意你刚才所说的一切。你帮了我很多。再次感谢!
只是我关于测试私有函数的辩论中的 2 美分:我更愿意测试我的私有函数,因为有时它们不是微不足道的;此外,如果我正在使用旧代码,我想尽快添加测试以帮助我重构代码,即使我正在编写自己的代码,我也想测试私有方法,直到它们被重构为其他抽象类的公共方法.以上是关于GoogleTest(Fatal assertion)-5的主要内容,如果未能解决你的问题,请参考以下文章