gtest使用简介
gtest是谷歌开发的开源测试框架,用于帮助c++开发者实现测试用例。使用下来感觉gtest简单实用,基本可以满足各类的测试需求。
gtest的使用并不复杂,这里主要是整理一下基本的使用方法和一些实际开发中碰到的问题。通过 https://github.com/google/googletest 可以得到源码,也有比较详细的使用说明。
一. 一个简单的例子
以下例子来自于gtest源码下提供的sample。
| C++ int Factorial(int n) { int result = 1; for (int i = 1; i <= n; i++) { result *= i; }
return result; } |
以上是一个计算阶乘的函数,我们需要写测试代码验证它的正确性。
| C++ #include "gtest/gtest.h"
// Tests factorial of negative numbers. TEST(FactorialTest, Negative) { // This test is named "Negative", and belongs to the "FactorialTest" // test case. EXPECT_EQ(1, Factorial(-5)); EXPECT_EQ(1, Factorial(-1)); EXPECT_GT(Factorial(-10), 0);
// // // EXPECT_EQ(expected, actual) is the same as // // EXPECT_TRUE((expected) == (actual)) // // except that it will print both the expected value and the actual // value when the assertion fails. This is very helpful for // debugging. Therefore in this case EXPECT_EQ is preferred. // // On the other hand, EXPECT_TRUE accepts any Boolean expression, // and is thus more general. // // }
// Tests factorial of 0. TEST(FactorialTest, Zero) { EXPECT_EQ(1, Factorial(0)); }
// Tests factorial of positive numbers. TEST(FactorialTest, Positive) { EXPECT_EQ(1, Factorial(1)); EXPECT_EQ(2, Factorial(2)); EXPECT_EQ(6, Factorial(3)); EXPECT_EQ(40320, Factorial(8)); } |
如上,测试代码的实现,都通过宏TEST来开始:
| C++ #define TEST(test_suite_name, test_name) GTEST_TEST(test_suite_name, test_name) |
TEST需要两个参数:第一个是test suite的名字,test suite代表一组相关的测试脚本,比如如上的三个Test的test suite的名字都是FactorialTest,都是针对于函数Factorial的测试;第二参数是为当前测试块的名字。这样做的目的是为了能将测试用例分成不同的层次,当在开发过程中,你只希望针对一些特殊的用例进行测试的时候,可以通过 --gtest_filter 来选择只运行指定的用例。
TEST展开的结果,简单理解就是它会生成一个名字为test_suite_name##_##test_name##_Test
的类,该类继承于gtest提供的类Test。比如 TEST(FactorialTest, Zero) 展开的大概样子:
| C++ class FactorialTest_Zeor_Test : public Test { ...... private: void TestBody() override; static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_; } ::testing::TestInfo* const FactorialTest_Zeor_Test ::test_info_ = ::testing::internal::MakeAndRegisterTestInfo("Factorialtest", "Zeor_Test", nullptr, nullptr, ..., new TestFactoryImpl) ;
void FactorialTest_Zeor_Test::TestBody() { EXPECT_EQ(1, Factorial(0)); } |
TEST展开后,TEST()后跟着的{}中的内容,就是TestBody的实现。这里的TestBody是继承类Test中虚函数的实现。
再简单看一下test_info_ ,是一个静态变量。在FactorialTest_Zeor_Test中,其实它并没有被用到。它存在的目的是通过MakeAndRegisterTestInfo函数,将test_suite_name,test_name,以及这里定义的FactorialTest_Zeor_Test保存到到一个单例对象中:
| C++ UnitTest* UnitTest::GetInstance() { // CodeGear C++Builder insists on a public destructor for the // default implementation. Use this implementation to keep good OO // design with private destructor.
#if defined(__BORLANDC__) static UnitTest* const instance = new UnitTest; return instance; #else static UnitTest instance; return &instance; #endif // defined(__BORLANDC__) } |
这里的组织形式,
| C++ 在 UnitTestImpl中定义了 test_suites_ // The vector of TestSuites in their original order. It owns the // elements in the vector. std::vector test_suites_; 在 TestSuite 中有一下定义了 test_info_list_ // The vector of TestInfos in their original order. It owns the // elements in the vector. std::vector test_info_list_; |
在程序启动阶段,TEST定义的所有信息就会按照以上的形式保存到对应的vecotr中。
回看一下MakeAndRegisterTestInfo中的最后一个参数 new TestFactoryImpl。这里构建了一个TestFactoryImpl的模板对象:
| C++ // Defines the abstract factory interface that creates instances // of a Test object. class TestFactoryBase { public: virtual ~TestFactoryBase() {}
// Creates a test instance to run. The instance is both created and destroyed // within TestInfoImpl::Run() virtual Test* CreateTest() = 0; TestFactoryBase() {}
private: TestFactoryBase(const TestFactoryBase&) = delete; TestFactoryBase& operator=(const TestFactoryBase&) = delete; }; // This class provides implementation of TestFactoryBase interface. // It is used in TEST and TEST_F macros. template class TestFactoryImpl : public TestFactoryBase { public: Test* CreateTest() override { return new TestClass; } }; |
这是个工厂类,目的就是生成以上定义的Test的子类(FactorialTest_Zeor_Test),工厂类对象被保存到了TestInfo中。
| C++ internal::TestFactoryBase* const factory_; // The factory that creates // the test object |
通过以上的组织方式,不再需要使用者再写其它代码去运行测试用例。而由统一的main函数来完成:
| C++ GTEST_API_ int main(int argc, char** argv) { // Since Google Mock depends on Google Test, InitGoogleMock() is // also responsible for initializing Google Test. Therefore there's // no need for calling testing::InitGoogleTest() separately. testing::InitGoogleMock(&argc, argv); return RUN_ALL_TESTS(); } |
一般main函数,如上形式。没有特殊需求时候,不需要修改main函数,RUN_ALL_TESTS会根据之前保存到vector中的信息,去运行测试用例。
二. 运行结果
编译以上的程序,生成可执行文件sample1_unittest.exe。运行结果如下:
| Bash D:\code\googletest-main\out\build\x64-Debug\googletest>sample1_unittest.exe --gtest_filter=FactorialTest.* Running main() from D:\code\googletest-main\googletest\src\gtest_main.cc Note: Google Test filter = FactorialTest.* [==========] Running 3 tests from 1 test suite. [----------] Global test environment set-up. [----------] 3 tests from FactorialTest [ RUN ] FactorialTest.Negative [ OK ] FactorialTest.Negative (0 ms) [ RUN ] FactorialTest.Zero [ OK ] FactorialTest.Zero (0 ms) [ RUN ] FactorialTest.Positive [ OK ] FactorialTest.Positive (0 ms) [----------] 3 tests from FactorialTest (3 ms total)
[----------] Global test environment tear-down [==========] 3 tests from 1 test suite ran. (9 ms total) [ PASSED ] 3 tests. |
可以修改一下,得到一些错误结果。
| Bash D:\code\googletest-main\out\build\x64-Debug\googletest>sample1_unittest.exe --gtest_filter=FactorialTest.* Running main() from D:\code\googletest-main\googletest\src\gtest_main.cc Note: Google Test filter = FactorialTest.* [==========] Running 3 tests from 1 test suite. [----------] Global test environment set-up. [----------] 3 tests from FactorialTest [ RUN ] FactorialTest.Negative [ OK ] FactorialTest.Negative (0 ms) [ RUN ] FactorialTest.Zero D:\code\googletest-main\googletest\samples\sample1_unittest.cc(100): error: Expected equality of these values: 10 Factorial(0) Which is: 1 [ FAILED ] FactorialTest.Zero (0 ms) [ RUN ] FactorialTest.Positive D:\code\googletest-main\googletest\samples\sample1_unittest.cc(107): error: Expected equality of these values: 40310 Factorial(8) Which is: 40320 [ FAILED ] FactorialTest.Positive (0 ms) [----------] 3 tests from FactorialTest (1 ms total)
[----------] Global test environment tear-down [==========] 3 tests from 1 test suite ran. (5 ms total) [ PASSED ] 1 test. [ FAILED ] 2 tests, listed below: [ FAILED ] FactorialTest.Zero [ FAILED ] FactorialTest.Positive
2 FAILED TESTS |
比如将 Factorial(0) 的期望值改为10,如上图测试程序将报出错误信息以及代码所在位置。
除了在终端显示外,还可以生成文档报告:
| Bash --gtest_output=(json|xml)[:DIRECTORY_PATH\|:FILE_PATH] |
通过指定--gtest_output可以生成xml或者json文件形式的报告。
三. 测试覆盖率(另一个简单的例子)
在实际的开发过程中,项目中往往要求测试覆盖率,要求尽可能多的覆盖到所有的分支。简单看一下可能要注意到内容:
| C++ // Returns true if and only if 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 square 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; } |
以上是一个检测是否输入是质数的函数。
| C++ // Tests negative input. TEST(IsPrimeTest, Negative) { // This test belongs to the IsPrimeTest test case.
EXPECT_FALSE(IsPrime(-1)); EXPECT_FALSE(IsPrime(-2)); EXPECT_FALSE(IsPrime(INT_MIN)); }
// Tests some trivial cases. TEST(IsPrimeTest, Trivial) { EXPECT_FALSE(IsPrime(0)); EXPECT_FALSE(IsPrime(1)); EXPECT_TRUE(IsPrime(2)); EXPECT_TRUE(IsPrime(3)); }
// Tests positive input. TEST(IsPrimeTest, Positive) { EXPECT_FALSE(IsPrime(4)); EXPECT_TRUE(IsPrime(5)); EXPECT_FALSE(IsPrime(6)); EXPECT_FALSE(IsPrime(9)); EXPECT_TRUE(IsPrime(23)); EXPECT_FALSE(IsPrime(25)); } |
如上的测试用例中,考虑到负数,一些特殊的数(0,1,2,3)以奇偶数的判断。实际开发中代码要比这个复杂很多。要提高代码覆盖率,很多时候需要借助fake,mock等手段来完成。
四. 断言
ASSERT_*系列的断言
当断言失败时,退出当前函数。
EXPECT_*系列的断言
当断言失败时,继续向下执行。
所有的断言都支持 << 操作:
| C++ EXPECT_TRUE(IsPrime(0)) << " input is 0"; EXPECT_FALSE(IsPrime(1)) << " input is 1"; |
在断言失败后,打印信息将包含<<输出的内容。
1. 显式成功失败
SUCCEED() 标记成功。实际上并不产生任何操作,在代码中起到类似注释的作用。
| Fatal assertion | Nonfatal assertion |
| FAIL() | ADD_FAILURE() |
FAIL类似于ASSERT_*断言,会退出当前函数。ADD_FAILURE会继续向下执行。
| C++ switch(expression) { case 1: ... some checks ... case 2: ... some other checks ... default: FAIL() << "We shouldn't get here."; } |
一般在一些测试分支中,可能用到以上断言。
2. 条件断言
| Fatal assertion | Nonfatal assertion | Verifies |
| ASSERT_TRUE(condition); | EXPECT_TRUE(condition); | condition is true |
| ASSERT_FALSE(condition); | EXPECT_FALSE(condition); | condition is false |
3. 二元比较
| Fatal assertion | Nonfatal assertion | Verifies |
| ASSERT_EQ(expected,actual); | EXPECT_EQ(expected,actual); | expected == actual |
| 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 |
4. 字符串比较
| Fatal assertion | Nonfatal assertion | Verifies |
| ASSERT_STREQ(expected_str, actual_str); | EXPECT_STREQ(expected_str, actual_str); | the two C strings have the same content |
| ASSERT_STRNE(str1, str2); | EXPECT_STRNE(str1, str2); | the two C strings have different content |
| ASSERT_STRCASEEQ(expected_str, actual_str); | EXPECT_STRCASEEQ(expected_str, actual_str); | the two C strings have the same content, ignoring case |
| ASSERT_STRCASENE(str1, str2); | EXPECT_STRCASENE(str1, str2); | the two C strings have different content, ignoring case |
5. 浮点数相等比较
| Fatal assertion | Nonfatal assertion | Verifies |
| ASSERT_FLOAT_EQ(expected, actual); | EXPECT_FLOAT_EQ(expected, actual); | the two float values are almost equal |
| ASSERT_DOUBLE_EQ(expected, actual); | EXPECT_DOUBLE_EQ(expected, actual); | the two double values are almost equal |
相近比较
| Fatal assertion | Nonfatal assertion | Verifies |
| ASSERT_NEAR(val1, val2, abs_error); | EXPECT_NEAR(val1, val2, abs_error); | the difference between val1 and val2 doesn’t exceed the given absolute error |
可以自行设置abs_error来进行比较。
6. 异常断言
| Fatal assertion | Nonfatal assertion | Verifies |
| ASSERT_THROW(statement,exception_type) | EXPECT_THROW(statement,exception_type) | Verifies that statement throws an exception of type exception_type. |
| ASSERT_ANY_THROW(statement) | EXPECT_ANY_THROW(statement) | Verifies that statement throws an exception of any type. |
| ASSERT_NO_THROW(statement) | EXPECT_NO_THROW(statement) | Verifies that statement does not throw any exception. |
| C++ int Div(int m, int n) { if (n == 0) { throw std::runtime_error("n can't be 0.\n");
} return m / n; } |
比如我希望检测以上的代码是否可以正常输出异常。
| C++ TEST(DivTest, exception) { EXPECT_THROW({ Div(10, 0); }, std::runtime_error ); EXPECT_NO_THROW({ Div(10, 10); }); } |
7. 谓词断言
EXPECT_PRED*
| Fatal assertion | Nonfatal assertion | Verifies |
| ASSERT_PRED1(pred,val1) | EXPECT_PRED1(pred,val1) | Verifies that the predicate pred returns true when passed the given values as arguments. |
| ASSERT_PRED2(pred,val1,val2) | EXPECT_PRED2(pred,val1,val2) |
| ASSERT_PRED3(pred,val1,val2,val3) | EXPECT_PRED3(pred,val1,val2,val3) |
| ASSERT_PRED4(pred,val1,val2,val3,val4) | EXPECT_PRED4(pred,val1,val2,val3,val4) |
| ASSERT_PRED5(pred,val1,val2,val3,val4,val5) | EXPECT_PRED5(pred,val1,val2,val3,val4,val5) |
这些可以代替EXPECT_TRUE。pred是函数名,val*是输入的参数。好处是当输入的参数可以打印出来。
| C++ TEST(MutuallyPrimeTest, positive) { int a = 4; int b = 10; EXPECT_TRUE(MutuallyPrime(a, b)); EXPECT_PRED2(MutuallyPrime, a, b); } |
比如以上的输出是:
| Bash [ RUN ] MutuallyPrimeTest.positive D:\code\googletest-main\googletest\samples\sample1_unittest.cc(158): error: Value of: MutuallyPrime(a, b) Actual: false Expected: true D:\code\googletest-main\googletest\samples\sample1_unittest.cc(159): error: MutuallyPrime(a, b) evaluates to false, where a evaluates to 4 b evaluates to 10 [ FAILED ] MutuallyPrimeTest.positive (0 ms) |
使用EXPECT_PRED2可以打印出a,b的值。
EXPECT_PRED_FORMAT*
| Fatal assertion | Nonfatal assertion | Verifies |
| ASSERT_PRED_FORMAT1(pred_formatter,val1) | EXPECT_PRED_FORMAT1(pred_formatter,val1) | Verifies that the predicate pred_formatter succeeds when passed the given values as arguments. |
| ASSERT_PRED_FORMAT2(pred_formatter,val1,val2) | EXPECT_PRED_FORMAT2(pred_formatter,val1,val2) |
| ASSERT_PRED_FORMAT3(pred_formatter,val1,val2,val3) | EXPECT_PRED_FORMAT3(pred_formatter,val1,val2,val3) |
| ASSERT_PRED_FORMAT4(pred_formatter,val1,val2,val3,val4) | EXPECT_PRED_FORMAT4(pred_formatter,val1,val2,val3,val4) |
| ASSERT_PRED_FORMAT5(pred_formatter,val1,val2,val3,val4,val5) | EXPECT_PRED_FORMAT5(pred_formatter,val1,val2,val3,val4,val5) |
与EXPECT_PRED*的差别是,pred_for_matter需要返回一个testing::AssertionResult对象。也就是说这里不能直接调用准备测试的函数,而是要另外实现一个测试函数:
要测试的函数:
| C++ // Returns the smallest prime common divisor of m and n, // or 1 when m and n are mutually prime. int SmallestPrimeCommonDivisor(int m, int n) { ... }
// Returns true if m and n have no common divisors except 1. bool MutuallyPrime(int m, int n) { ... } |
测试代码:
| C++ // A predicate-formatter for asserting that two integers are mutually prime. testing::AssertionResult AssertMutuallyPrime(const char* m_expr, const char* n_expr, int m, int n) { if (MutuallyPrime(m, n)) return testing::AssertionSuccess();
return testing::AssertionFailure() << m_expr << " and " << n_expr << " (" << m << " and " << n << ") are not mutually prime, " << "as they have a common divisor " << SmallestPrimeCommonDivisor(m, n); }
... const int a = 3; const int b = 4; const int c = 10; ... EXPECT_PRED_FORMAT2(AssertMutuallyPrime, a, b); // Succeeds EXPECT_PRED_FORMAT2(AssertMutuallyPrime, b, c); // Fails |
需要再实现一个AssertMutuallyPrime,用来返回 AssertionResult 对象。
8. 死亡断言
| Fatal assertion | Nonfatal assertion | Verifies |
| ASSERT_DEATH(statement,matcher) | EXPECT_DEATH(statement,matcher) | Verifies that statement causes the process to terminate with a nonzero exit status and produces stderr output that matches matcher. |
| ASSERT_DEATH_IF_SUPPORTED(statement,matcher) | EXPECT_DEATH_IF_SUPPORTED(statement,matcher) | If death tests are supported, behaves the same as EXPECT_DEATH. Otherwise, verifies nothing. |
| ASSERT_DEBUG_DEATH(statement,matcher) | EXPECT_DEBUG_DEATH(statement,matcher) | In debug mode, behaves the same as EXPECT_DEATH. When not in debug mode (i.e. NDEBUG is defined), just executes statement. |
| ASSERT_EXIT(statement,predicate,matcher) | EXPECT_EXIT(statement,predicate,matcher) | Verifies that statement causes the process to terminate with an exit status that satisfies predicate, and produces stderr output that matches matcher. |
一个例子:
| C++ void exittest() { std::cerr << "my error" << std::endl; exit(1); }
TEST(MyDeathTest, exit) { EXPECT_DEATH(exittest(), "my error"); } |
一些注意点:
1. gtest文档里要求死亡测试的test_suit_name,定义为*DeathTest的形式。gtest会将死亡测试拍到最前面,最先运行。
2. EXPECT_DEATH 中的matcher也需要与代码中的错误信息匹配。gtest中提供了正则表达式用于匹配。需要的话,可以参考其文档。
3. 死亡测试的基本原理是,gtest会启动一个进程来运行死亡测试。
9. HRESULT断言
| Fatal assertion | Nonfatal assertion | Verifies |
| ASSERT_HRESULT_SUCCEEDED(expression) | EXPECT_HRESULT_SUCCEEDED(expression) | Verifies that expression is a success HRESULT. |
| ASSERT_HRESULT_FAILED(expression) | EXPECT_HRESULT_FAILED(expression) | Verifies that expression is a failure HRESULT. |
针对windows系统中的HRESULT的断言。
10. 泛化的断言
| Fatal assertion | Nonfatal assertion | Verifies |
| ASSERT_THAT(value,matcher) | EXPECT_THAT(value,matcher) | Verifies that value matches the matcher matcher. |
这里的matcher可用来对比字符串,正则表达式等内容。
| C++ using ::testing::AllOf; using ::testing::Gt; using ::testing::Lt; using ::testing::MatchesRegex; using ::testing::StartsWith;
... EXPECT_THAT(value1, StartsWith("Hello")); EXPECT_THAT(value2, MatchesRegex("Line \\d+")); ASSERT_THAT(value3, AllOf(Gt(5), Lt(10))); |
五. TEST_F
之前的例子中,都是用TEST来写测试用例。更多的时候是用TEST_F来实现,TEST_F引入了类。
| C++ class QueueTest : public ::testing::Test { protected: void SetUp() override { q1_.Enqueue(1); q2_.Enqueue(2); q2_.Enqueue(3); }
void TearDown() override {}
Queue q0_; Queue q1_; Queue q2_; }; |
| C++ 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; } |
- 这里构建了QueueTest 继承于Test。这里有函数SetUp和TearDown可以帮助构建一些通用的数据成员。
- 使用TEST_F构建测试用例
| C++ #define TEST_F(test_fixture, test_name) GTEST_TEST_F(test_fixture, test_name) |
TEST_F的展开与TEST类似,比如 TEST_F(QueueTest, IsEmptyInitially)展开后的的代码大致如下:
| C++ class QueueTest_IsEmptyInitially_Test : public QueueTest { |
而 TEST_F(QueueTest, DequeueWorks) 展开后:
| C++ class QueueTest_DequeueWorks_Test : public QueueTest { |
可以看到每个测试用例,其实都生成一个类,共同点是都继承自QueueTest,所以可以使用基类的一些共同资源。所以两个用例不能共享运行状态,比如 TEST_F(QueueTest, IsEmptyInitially) 中新增或者删除了元素,并不会对 TEST_F(QueueTest, DequeueWorks) 产生影响。
六. TEST_P
文档里描述的TEST_P的用法有三种,分别看一下。
1. 程序中可以输入不同的参数
| C++ bool Foo(const std::string& input) { if (input == "cat") { return true; } else if (input == "dog") { return true; } else { return false; } } |
比如一段函数可以输入不同的参数,但一些参数的意思相同(比如大小写?)。这个例子不太好,不过大概是那个意思。
| C++ class FooTest : public testing::TestWithParam {
}; |
要使用TEST_P,需要继承 TestWithParam。或者
| C++ class FooTest : public testing::Test, public testing::WithParamInterface { }; |
这里只是简单使用,FooTest中不需要其它代码。
| C++ TEST_P(FooTest, Foo) { EXPECT_TRUE(Foo(GetParam())); }
INSTANTIATE_TEST_SUITE_P(MyFooTest, FooTest, testing::Values("cat", "dog", "rabbit")); |
与TEST和TEST_T不同,TEST_P并不将用例直接加入运行列表,只负责定义类。实例化需要通过INSTANTIATE_TEST_SUITE_P 来完成。
| Bash [----------] 3 tests from MyFooTest/FooTest [ RUN ] MyFooTest/FooTest.Foo/0 [ OK ] MyFooTest/FooTest.Foo/0 (0 ms) [ RUN ] MyFooTest/FooTest.Foo/1 [ OK ] MyFooTest/FooTest.Foo/1 (0 ms) [ RUN ] MyFooTest/FooTest.Foo/2 D:\code\googletest-main\googletest\samples\sample1_unittest.cc(189): error: Value of: Foo(GetParam()) Actual: false Expected: true [ FAILED ] MyFooTest/FooTest.Foo/2, where GetParam() = "rabbit" (0 ms) [----------] 3 tests from MyFooTest/FooTest (6 ms total)
[----------] Global test environment tear-down [==========] 4 tests from 2 test suites ran. (129 ms total) [ PASSED ] 3 tests. [ FAILED ] 1 test, listed below: [ FAILED ] MyFooTest/FooTest.Foo/2, where GetParam() = "rabbit"
1 FAILED TEST |
运行结果如上。大致过程是 TEST_P(FooTest, Foo) 中通过GetParam()得到testing::Values("cat", "dog", "rabbit") 提供的值,每个值运行一次。
2. 针对不同的策略
比如说有两个函数:
| C++ // Returns true if and only if 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 square 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; } |
和
| C++ bool IsPrimeFast(int n) { if (n <= 1) return false;
for (int i = 2; i * i <= n; i++) { // n is divisible by an integer other than 1 and itself. if ((n % i) == 0) return false; }
return true; } |
都用来求质数。一套测试case应该可以满足两个函数,可以这样实现:
| C++ class MyPrimeTest : public testing::TestWithParam> {
void SetUp() override { fun_ = GetParam(); } void TearDown() override { }
protected: std::function fun_;
};
// Tests negative input. TEST_P(MyPrimeTest, Negative) { // This test belongs to the IsPrimeTest test case.
EXPECT_FALSE(fun_(-1)); EXPECT_FALSE(fun_(-2)); EXPECT_FALSE(fun_(INT_MIN)); }
// Tests some trivial cases. TEST_P(MyPrimeTest, Trivial) { EXPECT_FALSE(fun_(0)); EXPECT_FALSE(fun_(1)); EXPECT_TRUE(fun_(2)); EXPECT_TRUE(fun_(3)); }
INSTANTIATE_TEST_SUITE_P(MyTwoPrimeTest, MyPrimeTest, testing::Values(IsPrime, IsPrimeFast)); |
这里IsPrime和IsPrimeFast作为std::function对象传入。在MyPrimeTest的SetUp中通过GetParam可以分别得到两个std::function对象。然后TEST_P中通过fun_来调用,就可以为两个函数使用同一套测试用例。
这个例子不太好,可以参考gtest中提供的例子,为不同的派生类使用同一套用例。
3. 不同的输入,拥有相同的结果。
文档里给的说明是数据驱动模型,不过没有例子。感觉起来和第一种情形类似吧。
七. Fake & Mock
实际开发过程中,一些运行条件并不容易满足。比如说一段程序是从摄像头中读取数据,并检测图像是否存在被遮挡,模糊,冻结等情形。测试程序接一个摄像头并不是一个好的解决方法,而且也无法控制摄像头产生遮挡,模糊等情形的图像。有的时候是我们需要借助第三方库进行开发,测试用例针对的是我们自身开发的代码,并不需要关注第三方库的内部逻辑。
这时候我们可以应用fake&mock的方式来写测试代码。gtest中强调fake和mock的区别,我的理解是借助于gtest中提供的gmock库实现的就是mock,其它实现就是fake。为此,我们需要先理解mock。
1. 构建mock类
| C++ class Turtle { ... virtual ~Turtle() {} virtual void PenUp() = 0; virtual void PenDown() = 0; virtual void Forward(int distance) = 0; virtual void Turn(int degrees) = 0; virtual void GoTo(int x, int y) = 0; virtual int GetX() const = 0; virtual int GetY() const = 0; }; |
Turtle代表实际环境中要用到的一个类,我们要维护的代码中用到了该类。测试用例中,希望不要用到它,这时候可以构建mock类:
| C++ #include "gmock/gmock.h" // Brings in gMock.
class MockTurtle : public Turtle { public: ... MOCK_METHOD(void, PenUp, (), (override)); MOCK_METHOD(void, PenDown, (), (override)); MOCK_METHOD(void, Forward, (int distance), (override)); MOCK_METHOD(void, Turn, (int degrees), (override)); MOCK_METHOD(void, GoTo, (int x, int y), (override)); MOCK_METHOD(int, GetX, (), (const, override)); MOCK_METHOD(int, GetY, (), (const, override)); }; |
这里主要用到的是MOCK_METHOD。
| C++ MOCK_METHOD(ReturnType, MethodName, (Args...)); MOCK_METHOD(ReturnType, MethodName, (Args...), (Specs...)); |
MOCK_METHOD可以接收3个或者4个参数,前三个函数对应于函数类型。Specs可以是const,override,noexcept,Calltype和ref, 其实也是针对于函数的定义。
2. 使用mock对象
| C++ #include "path/to/mock-turtle.h" #include "gmock/gmock.h" #include "gtest/gtest.h"
using ::testing::AtLeast; // #1
TEST(PainterTest, CanDrawSomething) { MockTurtle turtle; // #2 EXPECT_CALL(turtle, PenDown()) // #3 .Times(AtLeast(1));
Painter painter(&turtle); // #4
EXPECT_TRUE(painter.DrawCircle(0, 0, 10)); // #5 } |
构建mock类后,就可以在测试代码中使用它。以上代码中,构建了MockTurtle对象,这段代码的主要测试对象是Painter,所以需要用turtle构建Painter对象。
EXCEPT_CALL表示在painter.DrawCircle运行过程中,希望可以调用到turtle对象中的PenDown函数。 Times表示调用到次数,这里的AtLeast(1)表示最少一次。
这里给出的例子中Painter是通过构造函数中传入指针的方式来使用Turtle对象。不过实际代码中很多时候要mock的对象可能是在函数内部构建的,也有可能是通过外部参数得到的。我的解决方法是为Painter增加一个SetXXX函数,将mock对象设置进去(可以通过宏控制之只在测试代码SetXXX才生效)。
3. 设置期望
a. 基本语法
| C++ EXPECT_CALL(mock_object, method(matchers)) .Times(cardinality) .WillOnce(action) .WillRepeatedly(action); |
用一个例子来看看它的意思:
| C++ EXPECT_CALL(turtle, GetX()) .Times(5) .WillOnce(Return(100)) .WillOnce(Return(150)) .WillRepeatedly(Return(200)); |
表示期望调用turtle中的GetX函数,Times表示调用5次。第一次的返回值是100,第二次是150,后面都返回200。
b. Matchers : 希望使用什么参数
一些情况下,我们需要确定传入到mock对象的参数是否正确。
| C++ // Expects the turtle to move forward by 100 units. EXPECT_CALL(turtle, Forward(100)); |
比如我们希望调用Forward时传入的参数是100。
| C++ using ::testing::_; ... // Expects that the turtle jumps to somewhere on the x=50 line. EXPECT_CALL(turtle, GoTo(50, _)); |
只对部分参数感兴趣,可以只用占位符。
| C++ using ::testing::Ge; ... // Expects the turtle moves forward by at least 100. EXPECT_CALL(turtle, Forward(Ge(100))); |
或者需要输入参数满足一定的条件。
| C++ // Expects the turtle to move forward. EXPECT_CALL(turtle, Forward); // Expects the turtle to jump somewhere. EXPECT_CALL(turtle, GoTo); |
当不关心输入参数时。
c. Cardinalities : 希望调用多少次
之前的例子中已经看到过,可以通过Times来指定希望调用多少次。
WillOnce 以可以代替Times的工作,当有1个WillOnce的时候(没有WillRepeatedly)表示期望调用一次。当有n个WillOnce时,期望调用n次。
没有Times和willOnce的时候,表示不关心调用次数。
d. Action: 希望函数做什么
类似之前的例子:
| C++ using ::testing::Return; ... EXPECT_CALL(turtle, GetY()) .WillOnce(Return(100)) .WillOnce(Return(200)) .WillRepeatedly(Return(300)); |
通过Return,可以指定函数被调用后的返回值。
| C++ using testing::ReturnPointee; ... int x = 0; MockFoo foo; EXPECT_CALL(foo, GetValue()) .WillRepeatedly(ReturnPointee(&x)); // Note the & here. x = 42; EXPECT_EQ(42, foo.GetValue()); // This will succeed now. |
可以返回指针。
| C++ using ::testing::ReturnRef;
class MockFoo : public Foo { ... MOCK_METHOD(Bar&, GetBar, (), (override)); MOCK_METHOD(const Bar&, GetBar, (), (const, override)); };
... MockFoo foo; Bar bar1, bar2; EXPECT_CALL(foo, GetBar()) // The non-const GetBar(). .WillOnce(ReturnRef(bar1)); EXPECT_CALL(Const(foo), GetBar()) // The const GetBar(). .WillOnce(ReturnRef(bar2)); |
可以返回引用。在不同的模式下,可以返回不同的值。
e. 可以设定期望的调用次序
| C++ using ::testing::InSequence; ... TEST(FooTest, DrawsLineSegment) { ... { InSequence seq;
EXPECT_CALL(turtle, PenDown()); EXPECT_CALL(turtle, Forward(100)); EXPECT_CALL(turtle, PenUp()); } Foo(); } |
通过InSequence实现。
4. 关于Fake
大部分时候,Mock可以帮助我们实现测试功能。但某些情形下,可能Mock并不容易实现。比如说测试程序需要被调用的Mock函数sleep一段时间,因为外部函数要做时间统计,对于函数超时要做特殊处理。这时候可以通过Fake类来实现:
| C++ class FakeTurtle : public Turtle { ........ void PenUp() override { sleep(100); } ......... }; |
比如以上代码,继承Turtle后,可以在继承的函数中做一些自己的处理。
这里无法使用mock中的Times等函数。但在一些环境下,也是一种有效的实现方式。实现起来方便灵活,而且可以在FakeTurtle中增加新的变量函数等,实现不同的要求。