gtest使用简介
迪丽瓦拉
2024-03-13 23:58:30
0

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

  1. 这里构建了QueueTest 继承于Test。这里有函数SetUp和TearDown可以帮助构建一些通用的数据成员。
  1. 使用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中增加新的变量函数等,实现不同的要求。

相关内容