GoogleTest 之路2-Googletest 入门(Primer)

Posted qifei-liu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GoogleTest 之路2-Googletest 入门(Primer)相关的知识,希望对你有一定的参考价值。

Why googletest?

为啥要用GoogleTest呢?

googletest 是由测试技术Team 开发的带有google 特殊的需求和限制的测试框架。

不管你在什么平台上写C++代码,googleTest 可以帮助你实现任何类型的测试,不仅仅是单元测试

那么是什么成就了一个好的测试,googletest为什么能适用? we believe:

1.  测试必须是独立的,并且是可以重复的。debug 非常痛苦因为发现 一个测试成功失败依赖于其他测试。

googleTest 通过在不同的object上运行test,起到了隔离测试的效果。当一个测试失败时,googletest allow you 运行在一个隔离的环境里做快速debug。

2. Tests 应当合理的组织起来,以反映 测试代码的 结构。googletest 把相关的的测试组织起来,并且可以共享 数据和子程序。

这种共同模式很容易识别并且很容易维护测试case。一致性在人们切换Projects 并且在新的code base 上工作时会更有效

3. 测试必须是便携式的(portable)和 可重用的。Google 有很多平台中立的(platform-neutral)代码,那么其测试代码也应该是平台中立的。GoogleTest在不同的平台上工作。不同的编译器(gcc, icc, and MSVC), with or without exceptions, so googletest tests can easily work with a variety of configurations.

4.当测试失败时,应该提供关于问题更多详细的信息。googletest在第一个test失败时并不停止工作。它只是停止当前测试,并继续下一个测试。You can also set up tests that report non-fatal failures after which the current test continues. Thus, you can detect and fix multiple bugs in a single run-edit-compile cycle.

5. testing framework 把测试 作者从 杂事中解放出来,让他们专注于测试的内容。googletest 自动跟踪所有定义的case,不需要 user枚举并运行他们

6. 测试需要快,googletest可以帮助你在不同的测试中重用共享的资源。and pay for the set-up/tear-down only once, without making tests depend on each other.

因为googleTest 是基于现在最流行的 xUnit 架构的,you‘ll feel right at home( 很地道的用法)如果你使用过Junit 或者PyUnit.如果没使用过花个10分钟学一下基础,就可以开始了。

Beware of the nomenclature

Note: 对于 Test,Test Case and Test Suite 这几个术语有几个不同的定义,你可能会产生混淆。

历史上,googletest 使用 Test Case 来表示 一组测试的集合,然而  目前公开的包含 ISTQB 和 不同的软件质量的专业书籍用 Test Suite 来定义此。

googleTest 里面使用的术语 Test 在 ISTQB里面称为Test Case.

Test 术语是很广的定义,包含了ISTQB里的 Test Case的定义,所以这不是一个很大的问题。 但是 GoogleTest 里面使用的Test Case就是一个有矛盾的术语,因而会让人产生困惑。

GoogleTest 最近开始用 Test Suite来替换 Test Case。推荐使用 TestSuite* 的API.老的 TestCase* API 渐渐的被弃用并且被重构。

Meaninggoogletest TermISTQB Term
Exercise a particular program path with specific input values and verify the results TEST() Test Case

 

 

Basic Concepts(基础概念)

当使用googletest,从写assertions开始,assertion 用来check 什么 condition是 true。 An assertion 的结果可以是 success, nonfatal failure 或者 fatal failure. 如果 fatal failure 发生,当前function 就终止;否则 程序继续正常运行

Tests使用 assertions 来验证 被测代码的行为。如果一个测试crash或者有一个 failed 的 assertion,那么 it fails;否则succeds.

一个 Test case 包含一个或多个 tests。你应当 组合你的 tests into test cases(这边应该叫 test suite 吧?)来反映 tested code 的结构。当多个test在一个 test case 想要share 子程序的时候,你可以把它们放到 test fixture 类里面。

一个 Test program 可以包含多个 test cases。

我们开始解释如何写一个 test program,从每个单独的 assertion 到构建test 和 test cases。

 

Assertions

googletest assertions 类似于函数调用的宏。你可以使用 assertions 对要测试的类和函数进行断言。当一个 assertion 失败。googletest 打印了 assertion的源文件和行号的位置,并且还有失败信息。你也可以提供定制的失败信息。这条信息会被加到googletest 的信息里面。

assertion成对出现,对于同一个 function有不同的影响。 ASSERT_* 版本失败时产生fatal failure,并且终止当前的函数。EXPECT_* 版本产生 nonfatal failures,不终止当前的函数。 通常 EXPECT_* 更推荐使用。因为你可以在一个test里面产生多个 failure。然而,如果继续当前测试没有意思时,你就得用 ASSERT_*

因为一个 failed ASSERT_* 从当前函数中立马返回,也许会提过 clean-up code,可能会引起内存泄漏。取决于泄漏的性质,这个也许值得修复,也许不值得修复。如果是 assertion 导致的内存泄漏就不值得修。

要提供定制的failure 信息,simply 使用 << 操作符,例子:

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 输出的都可以 streamed to 一个 assertion 宏-- 特别是 C 字符串 和 string 对象。如果一个宽的字符(windowsUNICODE模式下的wchar_t*,TCHAR*或者 std::wstring) 被 streamed 到一个断言上,打印时会被转换成 UTF-8。

 

Basic Assertions(基础断言)

下面的断言为一个基础的 true/false 情况的测试

Fatal assertionNonfatal assertionVerifies
ASSERT_TRUE(condition); EXPECT_TRUE(condition); condition is true
ASSERT_FALSE(condition); EXPECT_FALSE(condition); condition is false

Availability: Linux, Windows, Mac.

 

Binary Comparison

下面的断言描述了比较两个值的断言

Fatal assertionNonfatal assertionVerifies
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

如果前后两个值是不可以比较的,就会得到一个编译错误。

过去我们要求 参数要支持<< 符号,但是现在不需要了。如果<< 被支持,当断言失败时,会被调用去打印参数。

否则googletest 会尝试用最好的方法去打印它们。更多详情请参考 recipe

这些断言可以和user-defined类型一起工作,但是只在你定义的相对应的 比较运算符(e.g. ==,<,etc).因为这不被Google C++ style 所鼓励。你也许可以使用 ASSERT_TRUE() 或者 EXPECT_TRUE()来断言比较用户自定义类型的值。

然而,当有可能时,ASSERT_EQ(actual,expected) 更好于 ASSERT_TRUE(actual == expected), 因为 前者会在失败时告诉你 actual 和 expected 的值。

参数经常仅仅被评估一次。因此,参数有些副作用也是可以的。然而,和任何普通的C/C++ 函数相比较,参数的评估顺序是未定义的(i.e.编译器自自由选择顺序 )

所以你的code应该不依赖于任何特定的参数评价顺序。

ASSERT_EQ()使用的是指针比较。如果使用两个Cstring比较,它测试的是是否这两个string使用的是同一地址,而不是是否两值相等。因此如果你想比较两个字符串的值,请使用ASSERT_STREQ(),稍后讨论该内容。尤其是使用C string 和 NULL进行比较ASSERT_STREQ(c_string, NULL)。

当比较两个指针的时候,记得用 *_EQ(ptr, nullptr) 和 *_NE(ptr, nullptr) 而不用 *_EQ(ptr, NULL) 和 *_NE(ptr, NULL)。 因为 nullptr 是类型符号而NULL 不是,See FAQ for more details.

如果你使用浮点数比较  See Advanced googletest Topics for details.

Historical note: Before February 2016 *_EQ had a convention of calling it as ASSERT_EQ(expected, actual), so lots of existing code uses this order. Now *_EQ treats both parameters in the same way.

String Comparison

这组断言比较两个 C string。 如果你想比较两个string object,使用 EXPECT_EQ 和 EXPECT_NE等等

Fatal assertionNonfatal assertionVerifies
ASSERT_STREQ(str1, str2); EXPECT_STREQ(str1, str2); the two C strings have the same content
ASSERT_STRNE(str1, str2); EXPECT_STRNE(str1, str2); the two C strings have different contents
ASSERT_STRCASEEQ(str1, str2); EXPECT_STRCASEEQ(str1, str2); the two C strings have the same content, ignoring case
ASSERT_STRCASENE(str1, str2); EXPECT_STRCASENE(str1, str2); the two C strings have different contents, ignoring case

Note that  "CASE"在一个 assertion 名字里面意味着 case被忽略了。 一个 NULL 指针和一个空字符串 被认为是不一样的

*STREQ* 和 *STRNE* 同样也接受 C字符串(wchar_t *). 如果 一个比较 两个 宽字符的断言失败了,它们的值被打印成UTF-8 的 短字符串

See also: For more string comparison tricks (substring, prefix, suffix, and regular expression matching, for example), seethis in the Advanced googletest Guide.

简单的测试

Simple Tests

1. 使用 TEST()宏 来定义和命名测试 function,这些是普通的C++函数并且不返回值。

2. 在这个函数里,和其他任何你想要包含的C++ 表达式,使用各种各样的googletest 断言去check values

3. 测试的结果由断言所决定;如果测试里面的任何断言失败了(要么fatally 要么 non-fatally),或者如果测试crashes,整个测试失败。否则,成功

TEST(TestSuiteName, TestName) {
  ... test body ...
}

TEST()  参数从 general 到 specific. 第一个参数是test case 的名字,第二个参数是test case里面的 test的名字。 两者的名字都要是有效的C++ identifiers,并且不能有underscore(_)。一个测试的全名包含它 test case和其 individual name。Tests from different test cases can have the same individual name.

例如有一个简单的 integers function:

int Factorial(int n);  // Returns the factorial of n

对于它的测试case 可能像这个样子:

// Tests factorial of 0.
TEST(FactorialTest, HandlesZeroInput) {
  EXPECT_EQ(Factorial(0), 1);
}

// Tests factorial of positive numbers.
TEST(FactorialTest, HandlesPositiveInput) {
  EXPECT_EQ(Factorial(1), 1);
  EXPECT_EQ(Factorial(2), 2);
  EXPECT_EQ(Factorial(3), 6);
  EXPECT_EQ(Factorial(8), 40320);
}

googletest 通过 test cases来组合这些 test,所以说 逻辑上相关的tests应该在同一个test case里面;换句话说, TEST()第一个参数应该是相同的,上面的例子里面,我们有两个tests,HandlesZeroInput 和 HandlesPositiveInput, 它们同属于同一个 test case FactorialTest。

命名 testcases 和 tests,你应该遵循一定的规则 naming functions and classes

Test Fixtures: Using the Same Data Configuration for Multiple Tests

如果你发现你写两个或者多个 tests 用到了相同的数据,你可以使用 test fixture。它可以让多个tests使用相同的配置。

To create a fixture:

1. 集成 ::testing:Test. 使用 protected 字段:我们可以访问子类里的 fixture 成员。

2. 在类里面,申明你想要使用的任何对象。

3. 必要的时候,写一个默认构造函数或者 SetUp() 函数 来准备每个 test的对象。记住拼写 SetUp() 不是Setup()

Use override in C++11 to make sure you spelled it correctly

4.必要的时候,写一个析构函数或者TearDown()释放你在SetUp() 里面分配的资源,想知道什么时候使用 constructor/destructor 什么时候使用etUp()/TearDown(), read this FAQ entry

5. 有必要定义你tests里面的子函数供share。

定义了 fixture,想要在test里面访问 fixture的对象和子函数,就得使用TEST_F() 而不是TEST()

TEST_F(TestSuiteName, TestName) {
  ... test body ...
}

和 TEST()一样,第一个参数是 test case 的名字,但是对于TEST_F()来说第一个参数必须为 test fixture class. 你也许已经猜到了:_F 就是 fixture 的意思。

Unfortunately, C++ macro 系统不允许我们创建一个单独的宏处理不同的tests. 使用错误的宏会导致编译错误。

此外,你必须在使用TEST_F() 之前先定义 一个 test fixture, 否则 你将会收到 compiler 错误 “virtual outside class declaration"

每一个 用TEST_F() 定义的 test,googletest 会在运行时创建一个新的 test fixture,立刻通过 SetUp() 初始化,运行测试,通过调用 TearDown()来清理测试,最后删除test fixture。注意 在 test case 里面的不同tests拥有的时不同的 test fixture 对象。 googletest 经常在 创建下一个 test fixture 前先删除一个。 googletest 对于多个 tests不重复使用同一个 test fixture。 任何对于其中一个 test 的改变不影响其他的 tests。

举一个例子, 我们写了一个 命名为 Queue 的FIFO queue的 tests,接口如下:

template <typename E>  // E is the element type.
class Queue {
 public:
  Queue();
  void Enqueue(const E& element);
  E* Dequeue();  // Returns NULL if the queue is empty.
  size_t size() const;
  ...
};

首先,定义一个 fixture 类。根据命名的传统,FooTest实质上被测的类就是Foo。我们定义QueueTest.

class QueueTest : public ::testing::Test {
 protected:
  void SetUp() override {
     q1_.Enqueue(1);
     q2_.Enqueue(2);
     q2_.Enqueue(3);
  }

  // void TearDown() override {}

  Queue<int> q0_;
  Queue<int> q1_;
  Queue<int> q2_;
};

这个例子里面,TearDown() 是不需要的因为我们不需要在每个test 结束后都清理,我们已经通过析构函数实现了这一功能。

现在我们就可以在TEST_F() 里面使用 这个 fixture

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

上面的测试case 既用到了 ASSERT_* 也用到了 EXPECT_* 断言。 使用EXPECT_*在断言失败之后可以 查看更多错误的信息。 如果断言之后的信息没有意义则使用ASSERT_*。

例如, 在Dequeue 的第二个断言中 =ASSERT_NE(nullptr,n)=, as we need to dereference the pointer n later, which would lead to a segfault when n is NULL.

当这些 tests 运行的时候,如下过程执行了:

1. googletest 构造了 QueueTest 对象(let‘s call it t1).

2. t1.SetUp() 初始化 t1

3. t1 的 第一个 test(IsEmptyInitially) 运行

4. t1.TearDown() 在 test 结束时进行 清理.

5. t1 被析构

6.以上步骤在另一个 QueueTest 对象会被重复,这次运行的时 DequeueWorks test。

 

Invoking the Tests

调用测试

TEST() 和 TEST_F() 隐式向googletest 注册了。所以说 和其他的C++ 测试框架不一样的是,你不必要在运行时重新把test 重新按顺序列出来。

当被调用时, RUN_ALL_TESTS() 宏:

1. 保存所有 googletest flag 的状态

        为第一条test 创建 test fixture 对象

        通过 SetUp() 初始化

        基于 fixture 运行 tests

        通过TearDown() 清理 fixture

        删除 fixture

        重新存储  所有 googletest flags 的状态

        为下一条测试执行上面同样的步骤,直到所有tests 执行完。

如果 fatal failure 发生了,随后的步骤会被跳过。

IMPORTANT: 你不能忽略 RUN_ALL_TESTS() 的返回值,否则你会达到编译错误。这样设计的合理之处在于 自动化测试的服务通过 exit code 来判断其执行是否成功,而不是

通过 其 stdout/stderr 输出。因此你的main 函数必须有RUN_ALL_TESTS() 的返回值

并且,你应当只调用 RUN_ALL_TESTS()一次。多次调用可能会和某些高级功能( thread-safe death tests)相冲突,并且这些在这时不支持的

 

Writing the main() Function

写main 函数

写你自己的 main 函数,应当有 RUN_ALL_TESTS() 的返回值。

#include "this/package/foo.h"
#include "gtest/gtest.h"

namespace {

// The fixture for testing class Foo.
class FooTest : public ::testing::Test {
 protected:
  // You can remove any or all of the following functions if its body
  // is empty.

  FooTest() {
     // You can do set-up work for each test here.
  }

  ~FooTest() override {
     // You can do clean-up work that doesn‘t throw exceptions here.
  }

  // If the constructor and destructor are not enough for setting up
  // and cleaning up each test, you can define the following methods:

  void SetUp() override {
     // Code here will be called immediately after the constructor (right
     // before each test).
  }

  void TearDown() override {
     // Code here will be called immediately after each test (right
     // before the destructor).
  }

  // Objects declared here can be used by all tests in the test case for Foo.
};

// Tests that the Foo::Bar() method does 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);
}

// Tests that Foo does Xyz.
TEST_F(FooTest, DoesXyz) {
  // Exercises the Xyz feature of Foo.
}

}  // namespace

int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

::testing:InitGoogleTest() 函数 从命令行解析 googletest flags, 移除 所有已经识别的 flags。这允许用户通过不同的flags 来控制 测试程序的行为,我们将在 AdvancedGuide里介绍。你必须在 调用 RUN_ALL_TESTS() 之前调用这个函数。否则flags 不会被合理的初始化。

在 Windows上,InitGoogleTest() 也可以在 wide string 模式下运行,所以这也会在 UNICODE模式下编译。

但是也许你想写这么多在main 函数里面太费事了?我们也完全同意你的想法,那就是为什么GoogleTest提供了main函数的记本实施。如果这满足你的需要,把你的测试和gtest_main库建立链接,你就可以开始啦!

 

已知的限制

Known Limitations

GoogleTest  按照线程安全设计的。有 pthreads library 的情况下,系统上的执行是线程安全的。 目前在你的系统上从多个线程使用Google Test 断言是不安全的。在大多数的tests里面这不是一个问题,因为 assertion 都是在主线程里面的。If you want to help, you can volunteer to implement the necessary synchronization primitives in gtest-port.h for your platform.

以上是关于GoogleTest 之路2-Googletest 入门(Primer)的主要内容,如果未能解决你的问题,请参考以下文章

[QtCratot]_0_0_googletest单元测试

[QtCratot]_0_0_googletest单元测试

GoogleTest框架初识

GoogleTest框架初识

GoogleTest初探

Clion + GoogleTest实现单元测试