#include <iostream>
using namespace std;
//
// 简单示例
int Foo(int a, int b)
{
if (a == 0 || b == 0)
{
throw "don't do that";
}
int c = a % b;
if (c == 0)
{
return b;
}
return Foo(b, c);
}
//
TEST(ABC, ABC1)
{
// ASSERT_EQ(expected, actual);
// ASSERT_TRUE(condition);
// ASSERT_STREQ(expected_str, actual_str);
// ASSERT_FLOAT_EQ(expected, actual);
// ASSERT_DOUBLE_EQ(expected, actual);
// ASSERT_NEAR(val1, val2, abs_error);
// ASSERT_NE
// ASSERT_GT
// ASSERT_LT
// ASSERT_GE
// ASSERT_LE
// ASSERT_STREQ
// ASSERT_STRNE
// ASSERT_STRCASEEQ
// ASSERT_STRCASENE
// ASSERT_PRED1
ASSERT_TRUE(1); // assert可以用expect代替,assert失败时程序停止执行,expect失败时程序继续执行
ASSERT_FALSE(1);
}
// 添加一个测试,第一个参数google称为test case name(在XUnit中叫suite),第二个参数为test name(在XUnit中叫test case)
TEST(FooTestSuite, FooTestCase1)
{
EXPECT_EQ(2, Foo(4, 10));
EXPECT_EQ(6, Foo(30, 18));
}
//
// predicate
bool FooABC(int m, int n)
{
return Foo(m, n) > 1;
}
TEST(PredicateAssertionTestSuite, TestCase1)
{
int m = 5, n = 6;
EXPECT_EQ(1, Foo(5, 6));
EXPECT_PRED2(FooABC, m, n); // FooABC(m, n)返回true时通过测试,否则不通过。2表示有2个参数
}
//
// predicate,失败时按自己的格式输出错误信息,注意函数的声明
testing::AssertionResult AssertFoo(const char* m_expr, const char* n_expr, const char* k_expr,
int m, int n, int k)
{
if (Foo(m, n) == k)
{
return testing::AssertionSuccess();
}
testing::Message msg;
msg << m_expr << " 和 " << n_expr << " 的最大公约数应该是:" << Foo(m, n) << " 而不是:" << k_expr;
return testing::AssertionFailure(msg);
}
TEST(AssertFooTestSuite, TestCase1)
{
EXPECT_PRED_FORMAT3(AssertFoo, 3, 6, 2); // 3表示有3个参数
}
//
// 类型检查,类型不是期待的则编译不通过
template<typename T>
class StaticAssertTypeEqTestHelper
{
public:
StaticAssertTypeEqTestHelper()
{
testing::StaticAssertTypeEq<bool, T>();
}
};
TEST(StaticAssertTypeEqTest, WorksInClass)
{
StaticAssertTypeEqTestHelper<bool>();
}
//
// 全局事件,继承testing::Environment类,是所有测试用例共用的全局环境。需要在main函数中调用AddGlobalTestEnvironment注册,可以写很多个这样的类,然后将它们都注册上去。
// 当需要在所有的测试间共享一个相同的环境来保存和传递状态,或者环境的状态是只读的,可以只初始化一次,或者创建环境的开销很大,要求只初始化一次等
class GlobalEnv : public testing::Environment{
public:
virtual void SetUp()
{
cout << "all begin" << endl; // 该函数在执行全部的测试用例(所有的suite)之前执行
}
virtual void TearDown()
{
cout << "all end" << endl; // 该函数在执行全部的测试用例之后执行
}
};
int main(int argc, char* argv[])
{
testing::AddGlobalTestEnvironment(new GlobalEnv());
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
class SharedRes
{
private:
int i;
};
// 继承自testing::Test
class FooTest : public testing::Test
{
protected:
// Some expensive resource shared by all tests.
static SharedRes* shared_res;
static void SetUpTestCase() // 静态方法,在一组测试用例(suite,即本类中定义的测试用例)前执行。当需要在本类的各个测试间共享一个相同的环境来保存和传递状态,或者环境的状态是只读的,可以只初始化一次,或者创建环境的开销很大,要求只初始化一次等。注意是静态的。
{
shared_res = new SharedRes();
cout << "test suite begin" << endl;
}
static void TearDownTestCase() // 静态方法,在一组测试用例(suite,即本类中定义的测试用例)后执行
{
delete shared_res;
shared_res = NULL;
cout << "test suite end" << endl;
}
};
SharedRes* FooTest::shared_res = 0;
// 需要使用TEST_F宏,第一个参数必须是上面Test的子类名(表示同一个suite)。F是fixture的意思,TEST_F又从用户定义的类自动派生了一个类,因此要求public或protected的访问权限
TEST_F(FooTest, FooTest1)
{
// you can refer to shared_res here
// 可以访问FooTest类的非私有成员
EXPECT_EQ(1, 1);
EXPECT_NE(1, 2);
}
TEST_F(FooTest, FooTest2)
{
// you can refer to shared_res here
EXPECT_LT(1, 2);
EXPECT_GT(1, 2);
EXPECT_LE(1, 2);
EXPECT_GE(1, 2);
}
//
class Bar
{
public:
int Init()
{
return 0;
}
int Finalize()
{
return 0;
}
int Do(int m, int n)
{
return m - n;
}
};
// 继承自testing::Test
class BarTest : public testing::Test // 同样是Test的子类
{
protected:
Bar bar;
virtual void SetUp() // 在每个test之前执行
{
bar.Init();
cout << "test case begin" << endl;
}
virtual void TearDown() // 在每个test之后执行
{
bar.Finalize();
cout << "test case end" << endl;
}
int Foo()
int i;
for (i = 0; i < 100; ++i)
{
;
}
return 0;
}
};
// 需要使用TEST_F宏,第一个参数必须是上面的类名(Test的子类)
TEST_F(BarTest, BarTest1)
{
// 可以访问BarTest类的成员
EXPECT_EQ(4, bar.Do(12, 16));
}
TEST_F(BarTest, BarTest2)
{
EXPECT_EQ(0, Foo()); // 直接调用类的成员函数
//
// 值参数化测试(Value Parameterized Test)
// Returns true iff n is a prime number.
bool IsPrime(int n)
{
// Trivial case 1: small numbers
if (n <= 1)
return false;
// Trivial case 2: even numbers
if (n % 2 == 0)
return n == 2;
// Now, we have that n is odd and n >= 3.
// Try to divide n by every odd number i, starting from 3
for (int i = 3;; i += 2)
{
// We only have to try i up to the squre root of n
if (i > n / i)
break;
// Now, we have i <= n/i < n.
// If n is divisible by i, n is not prime.
if (n % i == 0)
return false;
}
// n has no integer factor in the range (1, n), and thus is prime.
return true;
}
// 需要添加一个类,继承testing::TestWithParam<T>,其中T就是需要参数化的参数类型
class IsPrimeParamTest : public ::testing::TestWithParam<int>
{
};
// 需要使用TEST_P宏,第一个参数是上面TestWithParam的子类名,在TEST_P宏里,使用GetParam()获取当前的参数的具体值
TEST_P(IsPrimeParamTest, HandleTrueReturn)
{
int n = GetParam();
EXPECT_TRUE(IsPrime(n));
}
// 设置参数列表,第一个参数是前缀名,随便取,第二个参数是上面TestWithParam的子类名,第三个参数是测试参数列表
INSTANTIATE_TEST_CASE_P(TrueReturn, IsPrimeParamTest, testing::Values(3, 5, 11, 23, 17));// 类似的函数有Range(begin, end[, step])等
//
int main(int argc, char* argv[])
{
testing::AddGlobalTestEnvironment(new GlobalEnv); // 添加一个全局事件,可以添加多个
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
//
// 异常测试
ASSERT_NO_THROW
ASSERT_ANY_THROW
ASSERT_THROW
//
// 直接返回成功或失败
TEST(ExplicitTest, Demo)
{
ADD_FAILURE() << "Sorry"; // None Fatal Asserton,继续往下执行。
//FAIL(); // Fatal Assertion,不往下执行该案例。
SUCCEED();
}
五、类型参数化
gtest还提供了应付各种不同类型的数据时的方案,以及参数化类型的方案。我个人感觉这个方案有些复杂。首先要了解一下类型化测试,就用gtest里的例子了。
首先定义一个模版类,继承testing::Test:
template <typename T>
class FooTest : public testing::Test {
public:
typedef std::list<T> List;
static T shared_;
T value_;
};
接着我们定义需要测试到的具体数据类型,比如下面定义了需要测试char,int和unsigned int :
typedef testing::Types<char, int, unsigned int> MyTypes;
TYPED_TEST_CASE(FooTest, MyTypes);
又是一个新的宏,来完成我们的测试案例,在声明模版的数据类型时,使用TypeParam
TYPED_TEST(FooTest, DoesBlah) {
// Inside a test, refer to the special name TypeParam to get the type
// parameter. Since we are inside a derived class template, C++ requires
// us to visit the members of FooTest via 'this'.
TypeParam n = this->value_;
// To visit static members of the fixture, add the 'TestFixture::'
// prefix.
n += TestFixture::shared_;
// To refer to typedefs in the fixture, add the 'typename TestFixture::'
// prefix. The 'typename' is required to satisfy the compiler.
typename TestFixture::List values;
values.push_back(n);
}
上面的例子看上去也像是类型的参数化,但是还不够灵活,因为需要事先知道类型的列表。gtest还提供一种更加灵活的类型参数化的方式,允许你在完成测试的逻辑代码之后再去考虑需要参数化的类型列表,并且还可以重复的使用这个类型列表。下面也是官方的例子:
template <typename T>
class FooTest : public testing::Test {
};
TYPED_TEST_CASE_P(FooTest);
接着又是一个新的宏TYPED_TEST_P类完成我们的测试案例:
TYPED_TEST_P(FooTest, DoesBlah) {
// Inside a test, refer to TypeParam to get the type parameter.
TypeParam n = 0;
}
TYPED_TEST_P(FooTest, HasPropertyA) { }
接着,我们需要我们上面的案例,使用REGISTER_TYPED_TEST_CASE_P宏,第一个参数是testcase的名称,后面的参数是test的名称
REGISTER_TYPED_TEST_CASE_P(FooTest, DoesBlah, HasPropertyA);
接着指定需要的类型列表:
typedef testing::Types<char, int, unsigned int> MyTypes;
INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, MyTypes);
这种方案相比之前的方案提供更加好的灵活度,当然,框架越灵活,复杂度也会随之增加。
一、前言
“死亡测试”名字比较恐怖,这里的“死亡”指的的是程序的崩溃。通常在测试过程中,我们需要考虑各种各样的输入,有的输入可能直接导致程序崩溃,这时我们就需要检查程序是否按照预期的方式挂掉,这也就是所谓的“死亡测试”。gtest的死亡测试能做到在一个安全的环境下执行崩溃的测试案例,同时又对崩溃结果进行验证。
二、使用的宏
Fatal assertion | Nonfatal assertion | Verifies |
ASSERT_DEATH(statement, regex`); | EXPECT_DEATH(statement, regex`); | statement crashes with the given error |
ASSERT_EXIT(statement, predicate, regex`); | EXPECT_EXIT(statement, predicate, regex`); | statement exits with the given error and its exit code matches predicate |
由于有些异常只在Debug下抛出,因此还提供了*_DEBUG_DEATH,用来处理Debug和Realease下的不同。
三、*_DEATH(statement,regex`)
1. statement是被测试的代码语句
2. regex是一个正则表达式,用来匹配异常时在stderr中输出的内容
如下面的例子:
void Foo()
{
int *pInt = 0;
*pInt = 42 ;
}
TEST(FooDeathTest, Demo)
{
EXPECT_DEATH(Foo(), "");
}
重要:编写死亡测试案例时,TEST的第一个参数,即testcase_name,请使用DeathTest后缀。原因是gtest会优先运行死亡测试案例,应该是为线程安全考虑。
四、*_EXIT(statement, predicate,regex`)
1. statement是被测试的代码语句
2. predicate 在这里必须是一个委托,接收int型参数,并返回bool。只有当返回值为true时,死亡测试案例才算通过。gtest提供了一些常用的predicate:
testing::ExitedWithCode(exit_code)
如果程序正常退出并且退出码与exit_code相同则返回 true
testing::KilledBySignal(signal_number) // Windows下不支持
如果程序被signal_number信号kill的话就返回true
3. regex是一个正则表达式,用来匹配异常时在stderr中输出的内容
这里, 要说明的是,*_DEATH其实是对*_EXIT进行的一次包装,*_DEATH的predicate判断进程是否以非0退出码退出或被一个信号杀死。
例子:
TEST(ExitDeathTest, Demo)
{
EXPECT_EXIT(_exit(1), testing::ExitedWithCode(1), "");
}
五、*_DEBUG_DEATH
先来看定义:
#ifdef NDEBUG
#define EXPECT_DEBUG_DEATH(statement, regex) \
do { statement; } while (false)
#define ASSERT_DEBUG_DEATH(statement, regex) \
do { statement; } while (false)
#else
#define EXPECT_DEBUG_DEATH(statement, regex) \
EXPECT_DEATH(statement, regex)
#define ASSERT_DEBUG_DEATH(statement, regex) \
ASSERT_DEATH(statement, regex)
#endif // NDEBUG for EXPECT_DEBUG_DEATH
可以看到,在Debug版和Release版本下, *_DEBUG_DEATH的定义不一样。因为很多异常只会在Debug版本下抛出,而在Realease版本下不会抛出,所以针对Debug和Release分别做了不同的处理。看gtest里自带的例子就明白了:
int DieInDebugElse12(int* sideeffect) {
if (sideeffect) *sideeffect = 12;
#ifndef NDEBUG
GTEST_LOG_(FATAL, "debug death inside DieInDebugElse12()");
#endif // NDEBUG
return 12;
}
TEST(TestCase, TestDieOr12WorksInDgbAndOpt)
{
int sideeffect = 0;
// Only asserts in dbg.
EXPECT_DEBUG_DEATH(DieInDebugElse12(&sideeffect), "death");
#ifdef NDEBUG
// opt-mode has sideeffect visible.
EXPECT_EQ(12, sideeffect);
#else
// dbg-mode no visible sideeffect.
EXPECT_EQ(0, sideeffect);
#endif
}
六、关于正则表达式
在POSIX系统(Linux, Cygwin, 和 Mac)中,gtest的死亡测试中使用的是POSIX风格的正则表达式,想了解POSIX风格表达式可参考:
1. POSIX extended regularexpression
2. Wikipedia entry.
在Windows系统中,gtest的死亡测试中使用的是gtest自己实现的简单的正则表达式语法。 相比POSIX风格,gtest的简单正则表达式少了很多内容,比如 ("x|y"),("(xy)"),("[xy]") 和("x{5,7}")都不支持。
下面是简单正则表达式支持的一些内容:
matches any literal character c | |
\\d | matches any decimal digit |
\\D | matches any character that's not a decimal digit |
\\f | matches \f |
\\n | matches \n |
\\r | matches \r |
\\s | matches any ASCII whitespace, including \n |
\\S | matches any character that's not a whitespace |
\\t | matches \t |
\\v | matches \v |
\\w | matches any letter, _, or decimal digit |
\\W | matches any character that \\w doesn't match |
\\c | matches any literal character c, which must be a punctuation |
. | matches any single character except \n |
A? | matches 0 or 1 occurrences of A |
A* | matches 0 or many occurrences of A |
A+ | matches 1 or many occurrences of A |
^ | matches the beginning of a string (not that of each line) |
$ | matches the end of a string (not that of each line) |
xy | matches x followed by y |
gtest定义两个宏,用来表示当前系统支持哪套正则表达式风格:
1. POSIX风格:GTEST_USES_POSIX_RE = 1
2. Simple风格:GTEST_USES_SIMPLE_RE=1
七、死亡测试运行方式
1. fast方式(默认的方式)
testing::FLAGS_gtest_death_test_style = "fast";
2. threadsafe方式
testing::FLAGS_gtest_death_test_style = "threadsafe";
你可以在 main() 里为所有的死亡测试设置测试形式,也可以为某次测试单独设置。Google Test会在每次测试之前保存这个标记并在测试完成后恢复,所以你不需要去管这部分工作 。如:
TEST(MyDeathTest, TestOne) {
testing::FLAGS_gtest_death_test_style = "threadsafe";
// This test is run in the "threadsafe" style:
ASSERT_DEATH(ThisShouldDie(), "");
}
TEST(MyDeathTest, TestTwo) {
// This test is run in the "fast" style:
ASSERT_DEATH(ThisShouldDie(), "");
}
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
testing::FLAGS_gtest_death_test_style = "fast";
return RUN_ALL_TESTS();
}
八、注意事项
1. 不要在死亡测试里释放内存。
2. 在父进程里再次释放内存。
3. 不要在程序中使用内存堆检查。
九、总结
关于死亡测试,gtest官方的文档已经很详细了,同时在源码中也有大量的示例。如想了解更多的请参考官方的文档,或是直接看gtest源码。
简单来说,通过*_DEATH(statement, regex`)和*_EXIT(statement, predicate, regex`),我们可以非常方便的编写导致崩溃的测试案例,并且在不影响其他案例执行的情况下,对崩溃案例的结果进行检查。
///
一、前言
使用gtest编写的测试案例通常本身就是一个可执行文件,因此运行起来非常方便。同时,gtest也为我们提供了一系列的运行参数(环境变量、命令行参数或代码里指定),使得我们可以对案例的执行进行一些有效的控制。
二、基本介绍
前面提到,对于运行参数,gtest提供了三种设置的途径:
1. 系统环境变量
2. 命令行参数
3. 代码中指定FLAG
因为提供了三种途径,就会有优先级的问题, 有一个原则是,最后设置的那个会生效。不过总结一下,通常情况下,比较理想的优先级为:
命令行参数 > 代码中指定FLAG> 系统环境变量
为什么我们编写的测试案例能够处理这些命令行参数呢?是因为我们在main函数中,将命令行参数交给了gtest,由gtest来搞定命令行参数的问题。
int _tmain(int argc, _TCHAR* argv[])
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
这样,我们就拥有了接收和响应gtest命令行参数的能力。如果需要在代码中指定FLAG,可以使用testing::GTEST_FLAG这个宏来设置。比如相对于命令行参数--gtest_output,可以使用testing::GTEST_FLAG(output)= "xml:";来设置。注意到了,不需要加--gtest前缀了。同时,推荐将这句放置InitGoogleTest之前,这样就可以使得对于同样的参数,命令行参数优先级高于代码中指定。
int _tmain(int argc, _TCHAR* argv[])
{
testing::GTEST_FLAG(output) = "xml:";
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
最后再来说下第一种设置方式-系统环境变量。如果需要gtest的设置系统环境变量,必须注意的是:
1. 系统环境变量全大写,比如对于--gtest_output,响应的系统环境变量为:GTEST_OUTPUT
2. 有一个命令行参数例外,那就是--gtest_list_tests,它是不接受系统环境变量的。(只是用来罗列测试案例名称)
三、参数列表
了解了上面的内容,我这里就直接将所有命令行参数总结和罗列一下。如果想要获得详细的命令行说明,直接运行你的案例,输入命令行参数:/? 或 --help 或 -help
1. 测试案例集合
命令行参数 | 说明 |
--gtest_list_tests | 使用这个参数时,将不会执行里面的测试案例,而是输出一个案例的列表。 |
--gtest_filter | 对执行的测试案例进行过滤,支持通配符 ? 单个字符 * 任意字符 - 排除,如,-a 表示除了a : 取或,如,a:b 表示a或b 比如下面的例子: ./foo_test 没有指定过滤条件,运行所有案例 |
--gtest_also_run_disabled_tests | 执行案例时,同时也执行被置为无效的测试案例。关于设置测试案例无效的方法为: 在测试案例名称或测试名称中添加DISABLED前缀,比如: // Tests that Foo does Abc. |
--gtest_repeat=[COUNT] | 设置案例重复运行次数,非常棒的功能!比如: --gtest_repeat=1000 重复执行1000次,即使中途出现错误。 |
2. 测试案例输出
命令行参数 | 说明 |
--gtest_color=(yes|no|auto) | 输出命令行时是否使用一些五颜六色的颜色。默认是auto。 |
--gtest_print_time | 输出命令行时是否打印每个测试案例的执行时间。默认是不打印的。 |
--gtest_output=xml[:DIRECTORY_PATH\|:FILE_PATH] | 将测试结果输出到一个xml中。 1.--gtest_output=xml: 不指定输出路径时,默认为案例当前路径。 2.--gtest_output=xml:d:\ 指定输出到某个目录 3.--gtest_output=xml:d:\foo.xml 指定输出到d:\foo.xml 如果不是指定了特定的文件路径,gtest每次输出的报告不会覆盖,而会以数字后缀的方式创建。xml的输出内容后面介绍吧。 |
3. 对案例的异常处理
命令行参数 | 说明 |
--gtest_break_on_failure | 调试模式下,当案例失败时停止,方便调试 |
--gtest_throw_on_failure | 当案例失败时以C++异常的方式抛出 |
--gtest_catch_exceptions | 是否捕捉异常。gtest默认是不捕捉异常的,因此假如你的测试案例抛了一个异常,很可能会弹出一个对话框,这非常的不友好,同时也阻碍了测试案例的运行。如果想不弹这个框,可以通过设置这个参数来实现。如将--gtest_catch_exceptions设置为一个非零的数。 注意:这个参数只在Windows下有效。 |
四、XML报告输出格式
<?xml version="1.0" encoding="UTF-8"?>
<testsuites tests="3" failures="1" errors="0" time="35" name="AllTests">
<testsuite name="MathTest" tests="2" failures="1"* errors="0" time="15">
<testcase name="Addition" status="run" time="7" classname="">
<failure message="Value of: add(1, 1) Actual: 3 Expected: 2" type=""/>
<failure message="Value of: add(1, -1) Actual: 1 Expected: 0" type=""/>
</testcase>
<testcase name="Subtraction" status="run" time="5" classname="">
</testcase>
</testsuite>
<testsuite name="LogicTest" tests="1" failures="0" errors="0" time="5">
<testcase name="NonContradiction" status="run" time="5" classname="">
</testcase>
</testsuite>
</testsuites>
从报告里可以看出,我们之前在TEST等宏中定义的测试案例名称(testcase_name)在xml测试报告中其实是一个testsuite name,而宏中的测试名称(test_name)在xml测试报告中是一个testcase name,概念上似乎有点混淆,就看你怎么看吧。
当检查点通过时,不会输出任何检查点的信息。当检查点失败时,会有详细的失败信息输出来failure节点。
在我使用过程中发现一个问题,当我同时设置了--gtest_filter参数时,输出的xml报告中还是会包含所有测试案例的信息,只不过那些不被执行的测试案例的status值为“notrun”。而我之前认为的输出的xml报告应该只包含我需要运行的测试案例的信息。不知是否可提供一个只输出需要执行的测试案例的xml报告。因为当我需要在1000个案例中执行其中1个案例时,在报告中很难找到我运行的那个案例,虽然可以查找,但还是很麻烦。
五、总结
本篇主要介绍了gtest案例执行时提供的一些参数的使用方法,这些参数都非常有用。在实际编写gtest测试案例时肯定会需要用到的时候。至少我现在比较常用的就是:
1. --gtest_filter
2.--gtest_output=xml[:DIRECTORY_PATH\|:FILE_PATH]
3. --gtest_catch_exceptions
最后再总结一下我使用过程中遇到的几个问题:
1. 同时使用--gtest_filter和--gtest_output=xml:时,在xml测试报告中能否只包含过滤后的测试案例的信息。
2. 有时,我在代码中设置testing::GTEST_FLAG(catch_exceptions) = 1和我在命令行中使用--gtest_catch_exceptions结果稍有不同,在代码中设置FLAG方式有时候捕捉不了某些异常,但是通过命令行参数的方式一般都不会有问题。这是我曾经遇到过的一个问题,最后我的处理办法是既在代码中设置FLAG,又在命令行参数中传入--gtest_catch_exceptions。不知道是gtest在catch_exceptions方面不够稳定,还是我自己测试案例的问题。
/
一、前言
“深入解析”对我来说的确有些难度,所以我尽量将我学习到和观察到的gtest内部实现介绍给大家。本文算是抛砖引玉吧,只能是对gtest的整体结构的一些介绍,想要了解更多细节最好的办法还是看gtest源码,如果你看过gtest源码,你会发现里面的注释非常的详细!好了,下面就开始了解gtest吧。
二、从TEST宏开始
前面的文章已经介绍过TEST宏的用法了,通过TEST宏,我们可以非法简单、方便的编写测试案例,比如:
TEST(FooTest, Demo)
{
EXPECT_EQ(1, 1);
}
我们先不去看TEST宏的定义,而是先使用/P参数将TEST展开。如果使用的是VistualStudio的话:
1. 选中需要展开的代码文件,右键 - 属性 - C/C++ - Preprocessor
2. Generate Preprocessed File 设置Without Line Numbers (/EP /P) 或 With Line Numbers (/P)
3. 关闭属性对话框,右键选中需要展开的文件,右键菜单中点击:Compile
编译过后,会在源代码目录生成一个后缀为.i的文件,比如我对上面的代码进行展开,展开后的内容为:
class FooTest_Demo_Test : public ::testing::Test
{
public:
FooTest_Demo_Test() {}
private:
virtual void TestBody();
static ::testing::TestInfo* const test_info_;
FooTest_Demo_Test(const FooTest_Demo_Test &);
void operator=(const FooTest_Demo_Test &);
};
::testing::TestInfo* const FooTest_Demo_Test
::test_info_ =
::testing::internal::MakeAndRegisterTestInfo(
"FooTest", "Demo", "", "",
(::testing::internal::GetTestTypeId()),
::testing::Test::SetUpTestCase,
::testing::Test::TearDownTestCase,
new ::testing::internal::TestFactoryImpl< FooTest_Demo_Test>);
void FooTest_Demo_Test::TestBody()
{
switch (0)
case 0:
if (const ::testing::AssertionResult
gtest_ar =
(::testing::internal:: EqHelper<(sizeof(::testing::internal::IsNullLiteralHelper(1)) == 1)>::Compare("1", "1", 1, 1)))
;
else
::testing::internal::AssertHelper(
::testing::TPRT_NONFATAL_FAILURE,
".\\gtest_demo.cpp",
9,
gtest_ar.failure_message()
) = ::testing::Message();
}
展开后,我们观察到:
1. TEST宏展开后,是一个继承自testing::Test的类。
2. 我们在TEST宏里面写的测试代码,其实是被放到了类的TestBody方法中。
3. 通过静态变量test_info_,调用MakeAndRegisterTestInfo对测试案例进行注册。
如下图:
上面关键的方法就是MakeAndRegisterTestInfo了,我们跳到MakeAndRegisterTestInfo函数中:
// 创建一个 TestInfo 对象并注册到 Google Test;
// 返回创建的TestInfo对象
//
// 参数:
//
// test_case_name: 测试案例的名称
// name: 测试的名称
// test_case_comment: 测试案例的注释信息
// comment: 测试的注释信息
// fixture_class_id: test fixture类的ID
// set_up_tc: 事件函数SetUpTestCases的函数地址
// tear_down_tc: 事件函数TearDownTestCases的函数地址
// factory: 工厂对象,用于创建测试对象(Test)
TestInfo* MakeAndRegisterTestInfo(
const char* test_case_name, const char* name,
const char* test_case_comment, const char* comment,
TypeId fixture_class_id,
SetUpTestCaseFunc set_up_tc,
TearDownTestCaseFunc tear_down_tc,
TestFactoryBase* factory) {
TestInfo* const test_info =
new TestInfo(test_case_name, name, test_case_comment, comment,
fixture_class_id, factory);
GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info);
return test_info;
}
我们看到,上面创建了一个TestInfo对象,然后通过AddTestInfo注册了这个对象。TestInfo对象到底是一个什么样的东西呢?
TestInfo对象主要用于包含如下信息:
1. 测试案例名称(testcase name)
2. 测试名称(test name)
3. 该案例是否需要执行
4. 执行案例时,用于创建Test对象的函数指针
5. 测试结果
我们还看到,TestInfo的构造函数中,非常重要的一个参数就是工厂对象,它主要负责在运行测试案例时创建出Test对象。我们看到我们上面的例子的factory为:
new ::testing::internal::TestFactoryImpl< FooTest_Demo_Test>
我们明白了,Test对象原来就是TEST宏展开后的那个类的对象(FooTest_Demo_Test),再看看TestFactoryImpl的实现:
template <class TestClass>
class TestFactoryImpl : public TestFactoryBase {
public:
virtual Test* CreateTest() { return new TestClass; }
};
这个对象工厂够简单吧,嗯,Simple is better。当我们需要创建一个测试对象(Test)时,调用factory的CreateTest()方法就可以了。
创建了TestInfo对象后,再通过下面的方法对TestInfo对象进行注册:
GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info);
GetUnitTestImpl()是获取UnitTestImpl对象:
inline UnitTestImpl* GetUnitTestImpl() {
return UnitTest::GetInstance()->impl();
}
其中UnitTest是一个单件(Singleton),整个进程空间只有一个实例,通过UnitTest::GetInstance()获取单件的实例。上面的代码看到,UnitTestImpl对象是最终是从UnitTest对象中获取的。那么UnitTestImpl到底是一个什么样的东西呢?可以这样理解:
UnitTestImpl是一个在UnitTest内部使用的,为执行单元测试案例而提供了一系列实现的那么一个类。(自己归纳的,可能不准确)
我们上面的AddTestInfo就是其中的一个实现,负责注册TestInfo实例:
// 添加TestInfo对象到整个单元测试中
//
// 参数:
//
// set_up_tc: 事件函数SetUpTestCases的函数地址
// tear_down_tc: 事件函数TearDownTestCases的函数地址
// test_info: TestInfo对象
void AddTestInfo(Test::SetUpTestCaseFunc set_up_tc,
Test::TearDownTestCaseFunc tear_down_tc,
TestInfo * test_info) {
// 处理死亡测试的代码,先不关注它
if (original_working_dir_.IsEmpty()) {
original_working_dir_.Set(FilePath::GetCurrentDir());
if (original_working_dir_.IsEmpty()) {
printf("%s\n", "Failed to get the current working directory.");
abort();
}
}
// 获取或创建了一个TestCase对象,并将testinfo添加到TestCase对象中。
GetTestCase(test_info->test_case_name(),
test_info->test_case_comment(),
set_up_tc,
tear_down_tc)->AddTestInfo(test_info);
}
我们看到,TestCase对象出来了,并通过AddTestInfo添加了一个TestInfo对象。这时,似乎豁然开朗了:
1. TEST宏中的两个参数,第一个参数testcase_name,就是TestCase对象的名称,第二个参数test_name就是Test对象的名称。而TestInfo包含了一个测试案例的一系列信息。
2. 一个TestCase对象对应一个或多个TestInfo对象。
我们来看看TestCase的创建过程(UnitTestImpl::GetTestCase):
// 查找并返回一个指定名称的TestCase对象。如果对象不存在,则创建一个并返回
//
// 参数:
//
// test_case_name: 测试案例名称
// set_up_tc: 事件函数SetUpTestCases的函数地址
// tear_down_tc: 事件函数TearDownTestCases的函数地址
TestCase* UnitTestImpl::GetTestCase(const char* test_case_name,
const char* comment,
Test::SetUpTestCaseFunc set_up_tc,
Test::TearDownTestCaseFunc tear_down_tc) {
// 从test_cases里查找指定名称的TestCase
internal::ListNode<TestCase*>* node = test_cases_.FindIf(
TestCaseNameIs(test_case_name));
if (node == NULL) {
// 没找到,我们来创建一个
TestCase* const test_case =
new TestCase(test_case_name, comment, set_up_tc, tear_down_tc);
// 判断是否为死亡测试案例
if (internal::UnitTestOptions::MatchesFilter(String(test_case_name),
kDeathTestCaseFilter)) {
// 是的话,将该案例插入到最后一个死亡测试案例后
node = test_cases_.InsertAfter(last_death_test_case_, test_case);
last_death_test_case_ = node;
} else {
// 否则,添加到test_cases最后。
test_cases_.PushBack(test_case);
node = test_cases_.Last();
}
}
// 返回TestCase对象
return node->element();
}
三、回过头看看TEST宏的定义
#define TEST(test_case_name, test_name)\
GTEST_TEST_(test_case_name, test_name, \
::testing::Test, ::testing::internal::GetTestTypeId())
同时也看看TEST_F宏
#define TEST_F(test_fixture, test_name)\
GTEST_TEST_(test_fixture, test_name, test_fixture, \
::testing::internal::GetTypeId<test_fixture>())
都是使用了GTEST_TEST_宏,在看看这个宏如何定义的:
#define GTEST_TEST_(test_case_name, test_name, parent_class, parent_id)\
class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public parent_class {\
public:\
GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {}\
private:\
virtual void TestBody();\
static ::testing::TestInfo* const test_info_;\
GTEST_DISALLOW_COPY_AND_ASSIGN_(\
GTEST_TEST_CLASS_NAME_(test_case_name, test_name));\
};\
\
::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_case_name, test_name)\
::test_info_ =\
::testing::internal::MakeAndRegisterTestInfo(\
#test_case_name, #test_name, "", "", \
(parent_id), \
parent_class::SetUpTestCase, \
parent_class::TearDownTestCase, \
new ::testing::internal::TestFactoryImpl<\
GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>);\
void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody()
不需要多解释了,和我们上面展开看到的差不多,不过这里比较明确的看到了,我们在TEST宏里写的就是TestBody里的东西。这里再补充说明一下里面的GTEST_DISALLOW_COPY_AND_ASSIGN_宏,我们上面的例子看出,这个宏展开后:
FooTest_Demo_Test(const FooTest_Demo_Test &);
void operator=(const FooTest_Demo_Test &);
正如这个宏的名字一样,它是用于防止对对象进行拷贝和赋值操作的。
四、再来了解RUN_ALL_TESTS宏
我们的测试案例的运行就是通过这个宏发起的。RUN_ALL_TEST的定义非常简单:
#define RUN_ALL_TESTS()\
(::testing::UnitTest::GetInstance()->Run())
我们又看到了熟悉的::testing::UnitTest::GetInstance(),看来案例的执行时从UnitTest的Run方法开始的,我提取了一些Run中的关键代码,如下:
int UnitTest::Run() {
__try {
return impl_->RunAllTests();
} __except(internal::UnitTestOptions::GTestShouldProcessSEH(
GetExceptionCode())) {
printf("Exception thrown with code 0x%x.\nFAIL\n", GetExceptionCode());
fflush(stdout);
return 1;
}
return impl_->RunAllTests();
}
我们又看到了熟悉的impl(UnitTestImpl),具体案例该怎么执行,还是得靠UnitTestImpl。
int UnitTestImpl::RunAllTests() {
// ...
printer->OnUnitTestStart(parent_);
// 计时
const TimeInMillis start = GetTimeInMillis();
printer->OnGlobalSetUpStart(parent_);
// 执行全局的SetUp事件
environments_.ForEach(SetUpEnvironment);
printer->OnGlobalSetUpEnd(parent_);
// 全局的SetUp事件执行成功的话
if (!Test::HasFatalFailure()) {
// 执行每个测试案例
test_cases_.ForEach(TestCase::RunTestCase);
}
// 执行全局的TearDown事件
printer->OnGlobalTearDownStart(parent_);
environments_in_reverse_order_.ForEach(TearDownEnvironment);
printer->OnGlobalTearDownEnd(parent_);
elapsed_time_ = GetTimeInMillis() - start;
// 执行完成
printer->OnUnitTestEnd(parent_);
// Gets the result and clears it.
if (!Passed()) {
failed = true;
}
ClearResult();
// 返回测试结果
return failed ? 1 : 0;
}
上面,我们很开心的看到了我们前面讲到的全局事件的调用。environments_是一个Environment的链表结构(List),它的内容是我们在main中通过:
testing::AddGlobalTestEnvironment(new FooEnvironment);
添加进去的。test_cases_我们之前也了解过了,是一个TestCase的链表结构(List)。gtest实现了一个链表,并且提供了一个Foreach方法,迭代调用某个函数,并将里面的元素作为函数的参数:
template <typename F> // F is the type of the function/functor
void ForEach(F functor) const {
for ( const ListNode<E> * node = Head();
node != NULL;
node = node->next() ) {
functor(node->element());
}
}
因此,我们关注一下:environments_.ForEach(SetUpEnvironment),其实是迭代调用了SetUpEnvironment函数:
static void SetUpEnvironment(Environment* env) { env->SetUp(); }
最终调用了我们定义的SetUp()函数。
再看看test_cases_.ForEach(TestCase::RunTestCase)的TestCase::RunTestCase实现:
static void RunTestCase(TestCase * test_case) { test_case->Run(); }
再看TestCase的Run实现:
void TestCase::Run() {
if (!should_run_) return;
internal::UnitTestImpl* const impl = internal::GetUnitTestImpl();
impl->set_current_test_case(this);
UnitTestEventListenerInterface * const result_printer =
impl->result_printer();
result_printer->OnTestCaseStart(this);
impl->os_stack_trace_getter()->UponLeavingGTest();
// 哈!SetUpTestCases事件在这里调用
set_up_tc_();
const internal::TimeInMillis start = internal::GetTimeInMillis();
// 嗯,前面分析的一个TestCase对应多个TestInfo,因此,在这里迭代对TestInfo调用RunTest方法
test_info_list_->ForEach(internal::TestInfoImpl::RunTest);
elapsed_time_ = internal::GetTimeInMillis() - start;
impl->os_stack_trace_getter()->UponLeavingGTest();
// TearDownTestCases事件在这里调用
tear_down_tc_();
result_printer->OnTestCaseEnd(this);
impl->set_current_test_case(NULL);
}
第二种事件机制又浮出我们眼前,非常兴奋。可以看出,SetUpTestCases和TearDownTestCaess是在一个TestCase之前和之后调用的。接着看test_info_list_->ForEach(internal::TestInfoImpl::RunTest):
static void RunTest(TestInfo * test_info) {
test_info->impl()->Run();
}
哦?TestInfo也有一个impl?看来我们之前漏掉了点东西,和UnitTest很类似,TestInfo内部也有一个主管各种实现的类,那就是TestInfoImpl,它在TestInfo的构造函数中创建了出来(还记得前面讲的TestInfo的创建过程吗?):
TestInfo::TestInfo(const char* test_case_name,
const char* name,
const char* test_case_comment,
const char* comment,
internal::TypeId fixture_class_id,
internal::TestFactoryBase* factory) {
impl_ = new internal::TestInfoImpl(this, test_case_name, name,
test_case_comment, comment,
fixture_class_id, factory);
}
因此,案例的执行还得看TestInfoImpl的Run()方法,同样,我简化一下,只列出关键部分的代码:
void TestInfoImpl::Run() {
// ...
UnitTestEventListenerInterface* const result_printer =
impl->result_printer();
result_printer->OnTestStart(parent_);
// 开始计时
const TimeInMillis start = GetTimeInMillis();
Test* test = NULL;
__try {
// 我们的对象工厂,使用CreateTest()生成Test对象
test = factory_->CreateTest();
} __except(internal::UnitTestOptions::GTestShouldProcessSEH(
GetExceptionCode())) {
AddExceptionThrownFailure(GetExceptionCode(),
"the test fixture's constructor");
return;
}
// 如果Test对象创建成功
if (!Test::HasFatalFailure()) {
// 调用Test对象的Run()方法,执行测试案例
test->Run();
}
// 执行完毕,删除Test对象
impl->os_stack_trace_getter()->UponLeavingGTest();
delete test;
test = NULL;
// 停止计时
result_.set_elapsed_time(GetTimeInMillis() - start);
result_printer->OnTestEnd(parent_);
}
上面看到了我们前面讲到的对象工厂fatory,通过fatory的CreateTest()方法,创建Test对象,然后执行案例又是通过Test对象的Run()方法:
void Test::Run() {
if (!HasSameFixtureClass()) return;
internal::UnitTestImpl* const impl = internal::GetUnitTestImpl();
impl->os_stack_trace_getter()->UponLeavingGTest();
__try {
// Yeah!每个案例的SetUp事件在这里调用
SetUp();
} __except(internal::UnitTestOptions::GTestShouldProcessSEH(
GetExceptionCode())) {
AddExceptionThrownFailure(GetExceptionCode(), "SetUp()");
}
// We will run the test only if SetUp() had no fatal failure.
if (!HasFatalFailure()) {
impl->os_stack_trace_getter()->UponLeavingGTest();
__try {
// 哈哈!!千辛万苦,我们定义在TEST宏里的东西终于被调用了!
TestBody();
} __except(internal::UnitTestOptions::GTestShouldProcessSEH(
GetExceptionCode())) {
AddExceptionThrownFailure(GetExceptionCode(), "the test body");
}
}
impl->os_stack_trace_getter()->UponLeavingGTest();
__try {
// 每个案例的TearDown事件在这里调用
TearDown();
} __except(internal::UnitTestOptions::GTestShouldProcessSEH(
GetExceptionCode())) {
AddExceptionThrownFailure(GetExceptionCode(), "TearDown()");
}
}
上面的代码里非常极其以及特别的兴奋的看到了执行测试案例的前后事件,测试案例执行TestBody()的代码。仿佛整个gtest的流程在眼前一目了然了。
四、总结
本文通过分析TEST宏和RUN_ALL_TEST宏,了解到了整个gtest运作过程,可以说整个过程简洁而优美。之前读《代码之美》,感触颇深,现在读过gtest代码,再次让我感触深刻。记得很早前,我对设计的理解是“功能越强大越好,设计越复杂越好,那样才显得牛”,渐渐得,我才发现,简单才是最好。我曾总结过自己写代码的设计原则:功能明确,设计简单。了解了gtest代码后,猛然发现gtest不就是这样吗,同时gtest也给了我很多惊喜,因此,我对gtest的评价是:功能强大,设计简单,使用方便。
总结一下gtest里的几个关键的对象:
1. UnitTest 单例,总管整个测试,包括测试环境信息,当前执行状态等等。
2. UnitTestImpl UnitTest内部具体功能的实现者。
3. Test 我们自己编写的,或通过TEST,TEST_F等宏展开后的Test对象,管理着测试案例的前后事件,具体的执行代码TestBody。
4. TestCase 测试案例对象,管理着基于TestCase的前后事件,管理内部多个TestInfo。
5. TestInfo 管理着测试案例的基本信息,包括Test对象的创建方法。
6. TestInfoImpl TestInfo内部具体功能的实现者 。