下文是从primer谷歌翻译过来,所以不要纠结细节,能理解就好
简介:googletest好处?
googletest可以帮助您编写更好的C ++测试。
googletest是测试技术团队开发的测试框架,其中考虑了谷歌的特定要求和约束。无论您是在Linux,Windows还是苹果机上工作,如果您编写C ++代码,googletest都可以为您提供帮助。它支持任何类型的测试,而不仅仅是单元测试。
那么什么是一个好的测试,googletest如何做,我们相信:
- 测试应该是独立且可重复的。调试由于其他测试而成功或失败的测试是一件痛苦的事.googletest通过在不同的对象上运行每个测试来隔离测试。当测试失败时,googletest允许您单独运行它以进行快速调试。
- 测试应该组织良好,并反映测试代码的结构。googletest将相关测试分组到可以共享数据和子例程的测试用例中。这种常见模式易于识别,易于维护。当人们切换项目并开始使用新的代码库时,这种一致性特别有用。
- 测试应该是便携式和可重复使用的。谷歌拥有大量与平台无关的代码,其测试也应该与平台无关。谷歌测试适用于不同的操作系统,使用不同的编译器(gcc,icc和MSVC),有或没有例外,因此googletest测试可以轻松地使用各种配置。
- 当测试失败时,他们应尽可能多地提供有关问题的信息。相反,它只会停止当前测试并继续下一个测试。您还可以设置报告非致命故障的测试,然后继续当前测试。因此,您可以在单个运行编辑 - 编译周期中检测并修复多个错误。
- 测试框架应该将测试编写者从家务杂务中解放出来,他们让专注于测试内容。googletest会自动跟踪所定义的所有测试,并且不需要用户枚举它们以便运行它们。
- 应该测试很快。使用googletest,您可以跨测试重用共享资源,只需支付一次设置/拆卸费用,而无需相互依赖测试。
由于googletest基于流行的的xUnit架构,如果您之前使用过的JUnit或PyUnit中,您会感到宾至如归。如果没有,您将花大约10分钟学习基础知识并开始学习。让我们走吧!
小心命名法
注意:由于测试,测试用例和测试套件这两个术语的定义不同,可能会有一些混乱的想法,所以要注意误解这些。
从历史上看,googletest开始使用术语测试用例来分组相关测试,而目前的出版物包括国际软件测试认证委员会(ISTQB)和各种软件质量教科书使用术语测试套件。
在googletest中使用的相关术语Test对应于ISTQB等术语测试用例。
术语测试是常用的足够广泛的意义上说,包括ISTQB的定义测试案例,因此在这里没有太大的问题。但长期测试案例在谷歌测试使用的是矛盾的感觉,从而混淆。
不幸的是用术语测试用例的测试套件整个googletest不易没有打破依赖的项目,TestCase
是在各个地方的公共API的一部分。
因此,请暂时了解这些术语的不同定义:
含义 | googletest术语 | ISTQB期限 |
---|---|---|
使用特定输入值练习特定程序路径并验证结果 | TEST() | TestCase |
一组与一个组件相关的几个测试 | TestCase | TestSuite |
基本概念
使用googletest时,首先要编写断言,这些断言检查条件是否为真。断言的结果可能是成功, 非致命失败或致命失败。如果发生致命故障,则中止当前功能; 否则程序会正常继续。
测试使用断言来验证测试代码的行为。如果测试崩溃或断言失败,则失败 ; 否则它会成功。
一个测试案例包含一个或多个测试。您应该将测试分组到反映测试代码结构的测试用例中。当测试用例中的多个测试需要共享公共对象和子例程时,您可以将它们放入 测试夹具类中。
一个测试程序可以包含多个测试案例。
我们现在将解释如何编写测试程序,从单个断言级别开始,然后构建测试和测试用例。
断言
googletest断言是类似于函数调用的宏。您可以通过对其行为进行断言来测试类或函数。断言失败时,googletest会打印断言的源文件和行号位置以及失败消息。您还可以提供自定义失败消息,该消息将附加到googletest的消息中。
断言成对出现,测试相同的东西,但对当前函数有不同的影响。ASSERT_*
版本在失败时会生成致命的故障,并中止当前的功能。EXPECT_*
版本生成非致命故障,不会中止当前功能。通常EXPECT_*
是首选,因为它们允许在测试中报告多个故障。但是,ASSERT_*
如果在有问题的断言失败时继续没有意义,则应该使用。
由于ASSERT_*
当前函数立即返回失败,可能会跳过其后的清理代码,可能会导致空间泄漏。根据泄漏的性质,它可能或者可能不值得修复 - 因此,如果除了断言错误之外还遇到堆检查错误,请记住这一点。
要提供自定义失败消息,只需使用<<
运算符或一系列此类运算符将其流式传输到宏中 。一个例子:
ASSERT_EQ(x.size(),y.size())<<"Vectors x and y are of unequal length"; for(int i = 0 ; i <x.size(); ++ i){ EXPECT_EQ(x [i],y [i])<< "Vectors x and y differ at index " << i; }
任何可以流式传输的东西都可以流式ostream
传输到断言宏 - 特别是C字符串和string
对象。如果一个宽字符串(wchar_t*
,TCHAR*
在UNICODE
Windows上,或模式std::wstring
)流式传输到一个断言,在打印时将被转换为UTF-8。
基本断言
这些断言进行基本的真/假条件测试。
致命的断言 | 非致命主张 | 验证 |
---|---|---|
ASSERT_TRUE(condition); |
EXPECT_TRUE(condition); |
condition 是真的 |
ASSERT_FALSE(condition); |
EXPECT_FALSE(condition); |
condition 是假的 |
请记住,当它们失败时,会ASSERT_*
产生致命的故障并从当前函数返回,同时EXPECT_*
产生非致命故障,允许该功能继续运行。在任何一种情况下,断言失败意味着其包含的测试失败。
可用性:Linux,Windows,Mac。
二元比较
本节介绍比较两个值的断言。
致命的断言 | 非致命主张 | 验证 |
---|---|---|
ASSERT_EQ(val1, val2); |
EXPECT_EQ(val1, val2); |
val1 == val2 |
ASSERT_NE(val1, val2); |
EXPECT_NE(val1, val2); |
val1 != val2 |
ASSERT_LT(val1, val2); |
EXPECT_LT(val1, val2); |
val1 < val2 |
ASSERT_LE(val1, val2); |
EXPECT_LE(val1, val2); |
val1 <= val2 |
ASSERT_GT(val1, val2); |
EXPECT_GT(val1, val2); |
val1 > val2 |
ASSERT_GE(val1, val2); |
EXPECT_GE(val1, val2); |
val1 >= val2 |
值参数必须由断言的比较运算符进行比较,否则您将收到编译器错误。我们曾经要求参数支持 <<
运算符流式传输ostream
,但不再需要。如果 <<
支持,则在断言失败时将调用它来打印参数; 否则googletest将尝试以最佳方式打印它们。有关更多详细信息以及如何自定义参数的打印,请参阅gMock 配方。)。
这些断言可以与用户定义类型的工作,但只有当你定义相应的比较操作者(例如==
,<
等)。由于Google C ++样式指南不鼓励这样做,您可能需要使用ASSERT_TRUE()
或EXPECT_TRUE()
断言用户定义类型的两个对象的相等性。
但是,如果可能的话,ASSERT_EQ(actual, expected)
最好是 ASSERT_TRUE(actual == expected)
,因为它告诉你actual
和expected
失败的价值观。
参数总是只评估一次。因此,参数可以产生副作用。但是,与任何普通的C / C ++函数一样,参数的评估顺序是未定义的(即编译器可以自由选择任何顺序),并且您的代码不应该依赖于任何特定的参数评估顺序。
ASSERT_EQ()
指针上的指针相等。如果在两个C字符串上使用它,它会测试它们是否在同一个内存位置,而不是它们具有相同的值。因此,如果要按const char*
值比较C字符串(例如),请使用 ASSERT_STREQ()
,稍后将对此进行描述。特别是,断言C字符串是NULL
,使用ASSERT_STREQ(c_string, NULL)
。ASSERT_EQ(c_string, nullptr)
如果支持c ++ 11,请考虑使用。要比较两个string
对象,您应该使用ASSERT_EQ
。
在进行指针比较时使用*_EQ(ptr, nullptr)
而*_NE(ptr, nullptr)
不是*_EQ(ptr, NULL)
和*_NE(ptr, NULL)
。这是因为nullptr
键入而NULL
不是键入。请参阅FAQ 以获取更多详细信
如果您正在使用浮点数,则可能需要使用其中某些宏的浮点变体,以避免因舍入而导致的问题。有关详细信息,请参阅高级googletest主题。
本节中的宏适用于窄字符串对象和宽字符串对象(string
和wstring
)。
可用性:Linux,Windows,Mac。
历史记录:在2016年2月之前*_EQ
有一个将其称为的惯例ASSERT_EQ(expected, actual)
,因此许多现有代码使用此订单。现在 *_EQ
以相同的方式处理这两个参数。
字符串比较
该组中的断言比较两个C字符串。如果要比较两个string
对象,请改用EXPECT_EQ
,EXPECT_NE
等等。
致命的断言 | 非致命主张 | 验证 |
---|---|---|
ASSERT_STREQ(str1, str2); |
EXPECT_STREQ(str1, str2); |
两个C字符串具有相同的内容 |
ASSERT_STRNE(str1, str2); |
EXPECT_STRNE(str1, str2); |
两个C字符串有不同的内容 |
ASSERT_STRCASEEQ(str1, str2); |
EXPECT_STRCASEEQ(str1, str2); |
两个C字符串具有相同的内容,忽略大小写 |
ASSERT_STRCASENE(str1, str2); |
EXPECT_STRCASENE(str1, str2); |
两个C字符串有不同的内容,忽略大小写 |
请注意,断言名称中的“CASE”表示忽略大小写。一个NULL
指针和一个空字符串被认为是不同的。
*STREQ*
并且*STRNE*
还接受宽C字符串(wchar_t*
)。如果两个宽字符串的比较失败,则它们的值将打印为UTF-8窄字符串。
可用性:Linux,Windows,Mac。
另请参见:更多的字符串比较的技巧(串,前缀,后缀和正则表达式匹配,例如),看到 这个 高级googletest指南中。
简单的测试
要创建测试:
- 使用
TEST()
宏来定义和命名测试函数,这些是不返回值的普通C ++函数。 - 在此函数中,与要包含的任何有效C ++语句一起使用各种googletest断言来检查值。
- 测试的结果由断言决定; 如果测试中的任何断言失败(无论是致命的还是非致命的),或者测试崩溃,整个测试都会失败。否则,它会成功。
TEST(TestCaseName,TestName){
...测试体...
}
TEST()
争论从一般到具体。第一个参数是测试用例的名称,第二个参数是测试用例中的测试名称。两个名称都必须是有效的C ++标识符,并且它们不应包含下划线(_
)。测试的全名包含其包含的测试用例及其个人名称。来自不同测试用例的测试可以具有相同的个人名称。
例如,让我们采用一个简单的整数函数:
int Factorial(int n); //返回n的阶乘
此函数的测试用例可能如下所示:
//测试阶乘为0.
TEST(FactorialTest,HandlesZeroInput){
EXPECT_EQ( Factorial( 0), 1);
}
//测试正数的阶乘。
TEST(FactorialTest,HandlesPositiveInput){
EXPECT_EQ( Factorial( 1), 1);
EXPECT_EQ(因子( 2), 2);
EXPECT_EQ(因子( 3), 6);
EXPECT_EQ( Factorial( 8), 40320);
}
googletest按测试用例对测试结果进行分组,因此逻辑相关的测试应该在同一测试用例中; 换句话说,他们的第一个论点TEST()
应该是相同的。在上面的例子中,我们有两个测试, HandlesZeroInput
并且HandlesPositiveInput
,属于同一个测试案例 FactorialTest
。
在命名测试用例和测试时,应遵循与命名函数和类相同的约定。
可用性:Linux,Windows,Mac。
测试夹具:为多个测试使用相同的数据配置
如果您发现自己编写了两个或多个对类似数据进行操作的测试,则可以使用测试夹具。它允许您为多个不同的测试重用相同的对象配置。
要创建一个夹具:
- 从中派生一个班级
::testing::Test
。从protected:
我们想要从子类访问夹具成员开始它的主体。 - 在类中,声明您计划使用的任何对象。
- 如有必要,编写一个默认构造函数或
SetUp()
函数来为每个测试准备对象。一个常见的错误是拼写SetUp()
为Setup()
小u
-override
在C ++ 11中使用以确保拼写正确 - 如有必要,编写析构函数或
TearDown()
函数以释放您分配的任何资源SetUp()
。要了解何时应该使用构造函数/析构函数以及何时应该使用SetUp()/TearDown()
,请阅读此FAQ条目。 - 如果需要,请为要共享的测试定义子例程。
使用夹具时,请使用TEST_F()
而不是TEST()
因为它允许您访问测试夹具中的对象和子程序:
TEST_F(TestCaseName,TestName){
...测试体...
}
比如TEST()
,第一个参数是测试用例名称,但是TEST_F()
必须是测试夹具类的名称。你可能已经猜到了:_F
是夹具。
不幸的是,C ++宏系统不允许我们创建一个可以处理这两种类型测试的宏。使用错误的宏会导致编译器错误。
此外,您必须首先在a中使用它之前定义测试夹具类 TEST_F()
,否则您将获得编译器错误“ virtual outside class declaration
”。
对于每个定义的测试TEST_F()
,googletest将在运行时创建一个新的测试夹具,立即初始化它SetUp()
,运行测试,通过调用清理TearDown()
,然后删除测试夹具。请注意,同一测试用例中的不同测试具有不同的测试夹具对象,并且googletest总是在创建下一个测试夹具之前删除它。googletest并没有重用多个测试相同的测试夹具。一次测试对夹具的任何改变都不会影响其他测试。
举个例子,让我们为名为FIFO的队列类编写测试Queue
,它具有以下接口:
template < typename E> // E是元素类型。
class Queue {
public:
Queue();
void Enqueue(const E&element);
E * Dequeue(); //如果队列为空,则返回NULL。
size_t size()const ;
...
};
首先,定义一个夹具类。按照惯例,您应该为其命名 FooTest
,其中Foo
是被测试的类。
class QueueTest:public :: testing :: Test { protected: void SetUp()override { q1_.Enqueue(1); q2_.Enqueue(2); q2_.Enqueue(3); } // void TearDown()覆盖{} Queue< int > q0_; Queue< int > q1_; Queue< int > q2_; };
在这种情况下,TearDown()
不需要,因为除了析构函数已经完成的操作之外,我们不必在每次测试后进行清理。
现在我们将使用TEST_F()
和这个夹具编写测试。
TEST_F(QueueTest,IsEmptyInitially){ EXPECT_EQ(q0_.size(),0); } TEST_F(QueueTest,DequeueWorks){ int * n = q0_.Dequeue(); EXPECT_EQ(n,nullptr); n = q1_.Dequeue(); ASSERT_NE(n,nullptr); EXPECT_EQ(* n,1); EXPECT_EQ(q1_.size(),0); delete n; n = q2_.Dequeue(); ASSERT_NE(n,nullptr); EXPECT_EQ(* n,2); EXPECT_EQ(q2_.size(),1); delete n; }
以上使用了两者ASSERT_*
和EXPECT_*
断言。经验法则是EXPECT_*
当您希望测试在断言失败后继续显示更多错误ASSERT_*
时使用,并在失败后继续使用时没有意义。例如,在所述第二断言Dequeue
测试是= ASSERT_NE(nullptr中,n)=,因为我们需要取消引用指针n
后,当这将导致一个段错误n
是NULL
。
运行这些测试时,会发生以下情况:
- googletest构造一个
QueueTest
对象(让我们称之为t1
)。 t1.SetUp()
初始化t1
。- 第一个测试(
IsEmptyInitially
)运行t1
。 t1.TearDown()
测试完成后清理。t1
被破坏了。- 对另一个
QueueTest
对象重复上述步骤,这次运行DequeueWorks
测试。
可用性:Linux,Windows,Mac。
调用测试
TEST()
并TEST_F()
使用googletest隐式注册他们的测试。因此,与许多其他C ++测试框架不同,您不必重新列出所有已定义的测试以便运行它们。
在定义测试之后,您可以使用RUN_ALL_TESTS()
它来运行它们,0
如果所有测试都成功,1
则返回它们。请注意,在链接单元中 RUN_ALL_TESTS()
运行所有测试 - 它们可以来自不同的测试用例,甚至是不同的源文件。
调用时,RUN_ALL_TESTS()
宏:
- 保存所有googletest标志的状态
-
为第一次测试创建测试夹具对象。
-
通过初始化它
SetUp()
。 -
在夹具对象上运行测试。
-
通过清理夹具
TearDown()
。 -
删除夹具。
-
恢复所有googletest标志的状态
-
重复上述步骤进行下一次测试,直到所有测试都运行完毕。
如果发生致命故障,将跳过后续步骤。
重要提示:您不能忽略返回值
RUN_ALL_TESTS()
,否则您将收到编译器错误。此设计的基本原理是自动化测试服务根据其退出代码确定测试是否已通过,而不是根据其stdout / stderr输出; 因此你的main()
函数必须返回值RUN_ALL_TESTS()
。此外,你应该
RUN_ALL_TESTS()
只打一次电话。多次调用它会与某些高级googletest功能(例如线程安全死亡测试)冲突,因此不受支持。
可用性:Linux,Windows,Mac。
编写main()函数
在google3
,最简单的方法是使用链接提供的默认main()函数"//testing/base/public:gtest_main"
。如果这不能满足您的需求,您应该编写自己的main()函数,该函数应该返回值RUN_ALL_TESTS()
。链接到"//testing/base/public:gunit"
。你可以从这个样板开始:
#include “this/package/foo.h” #include “gtest/gtest.h” namespace { //用于测试类Foo的夹具。 class FooTest:public :: testing :: Test { protected: //如果其主体 //为空,则可以删除以下任何或所有函数。 FooTest(){ //您可以在此处为每个测试进行设置工作。 } ~FooTest()override{ //你可以做清理工作,不会在这里抛出异常。 } //如果构造函数和析构函数不足以设置 //并清理每个测试,则可以定义以下方法: void SetUp()override { //此处的代码将在构造函数之后立即调用( 在每次测试之前//右键)。 } void TearDown()override { //此处的代码将在每次测试后立即调用( 在析构函数之前//右侧)。 } //这里声明的对象可以被Foo测试用例中的所有测试使用。 }; //测试Foo :: Bar()方法执行Abc。 TEST_F(FooTest,MethodBarDoesAbc){ const std :: string input_filepath = “ this / package / testdata / myinputfile.dat ” ; const std :: string output_filepath = “ this / package / testdata / myoutputfile.dat ” ; Foo f; EXPECT_EQ(f.bar(input_filepath,output_filepath),0); } //测试Foo做Xyz。 TEST_F(FooTest,DoesXyz){ //练习Foo的 Xyz 特性。 } } //命名空间 int main(int argc,char ** argv){ :: testing :: InitGoogleTest(&argc,argv); return RUN_ALL_TESTS(); }
该::testing::InitGoogleTest()
函数解析googletest标志的命令行,并删除所有已识别的标志。这允许用户通过各种标志控制测试程序的行为,我们将在 AdvancedGuide中介绍。您必须在调用之前调用此函数 RUN_ALL_TESTS()
,否则将无法正确初始化标志。
在Windows上,InitGoogleTest()
也可以使用宽字符串,因此它也可以在以UNICODE
模式编译的程序中使用。
但也许您认为编写所有main()函数太多了?我们完全同意您的意见,这就是为什么Google Test提供了main()的基本实现。如果它符合您的需求,那么只需将您的测试与gtest_main库链接起来就可以了。
注意:ParseGUnitFlags()
不赞成使用InitGoogleTest()
。
已知限制
- Google Test旨在提供线程安全性。该实现在
pthreads
库可用的系统上是线程安全的。目前在其他系统(例如Windows)上同时使用来自两个线程的Google Test断言是 不安全的。在大多数测试中,这不是问题,因为断言通常在主线程中完成。如果您想提供帮助,您可以自愿gtest-port.h
为您的平台实施必要的同步原语 。