GoogleTest(gtest)框架初窥

GoogleTest(gtest)框架初窥

本文是由官方文档中的GoogleTest Primer翻译整理而来,其中加了一些自己的理解,有误之处,烦请指正!
原文链接:https://google.github.io/googletest/primer.html

一、简介:

googletest是一个完善的基于xUnit架构的C++测试框架,是由谷歌的测试技术团队根据他们的特定需求开发的。
不论什么平台(Linux、Windows、Mac),只要是C++代码,你都能用到googletest。
这个框架不仅限于单元测试哟!(And it supports any kind of tests, not just unit tests.)

googletest所坚持的原则:

  1. 测试的独立性和可持续性;
  2. 测试应该是有组织的,并且能反映被测代码结构;(测试套件:共享数据和子程序)
  3. 可移植性和可重用性
  4. 测试不通过时提供尽可能详细的信息,并且不中止程序运行,只停止当前失败的测试继续进行下一个测试。
  5. 解放测试人员的双手,将重心放在测试内容而非那些重复简单的工作,googletest自动跟踪所有定义的测试,不需要用户在运行前一一列举。
  6. 提升测试效率。通过googletest,你可以在不同的测试中重复使用共享资源,并且只需进行一次“构造”和“析构”(set-up/tear-down),同时也并不会使每个测试相互依赖。

二、断言

断言(assertions):检查条件是否正确。结果可能为:成功、非致命性失败、致命性失败。

只有出现致命性失败,测试程序才会中止被测程序的执行,其他情况下都会继续执行。

测试不通过的情况:程序崩溃或者有一个失败的断言。

一个test suite包含一个或多个测试。你应该把你的test分组到反映被测代码结构的test suite中。当一个test suite中的多个测试需要共享共同的对象和子程序时,你可以把它们放到一个test fixture class中。

断言失败会打印断言所在源文件、行号和失败信息。也可以自定义一个失败信息加到googletest的信息中去。
ASSERT_* : (致命性错误)一旦检查条件不正确,直接中止当前程序,在后续的程序运行对于后面的测试没有任何意义了的情况下使用。
EXPECT_* : (非致命性错误)条件不正确,继续运行当前程序,这将有助于我们在运行一次测试代码时可以发现多个bug。

googletest提供这两种断言的主要原因还是效率问题,一次测试发现多个bug和中止无用的程序继续执行本质上都是在提升测试效率。至于如何选择两种断言,并非绝对的,但大多数情况下,EXPECT_*是首选。 除非一个测试一个会直接导致后面程序运行无效的function时使用ASSERT_*,其余都使用EXPECT_*即可。

备注:ASSERT_*断言会直接从当前程序执行处立刻返回,可能会因为没有执行后面的释放空间的代码导致内存泄漏。要注意在这种断言返回后,错误信息后可能还跟着一些堆检查器错误(heap checker error),但如果你在这个断言之后确有对应的释放内存的代码,就不必关注这些堆检查器错误了,因为这些在程序正常运行的时候,是不会发生的,只是因为ASSERT_*断言提前中止了程序才导致的这个错误。

要提供一个自定义的失败信息,只需使用“<<”操作符或一连串这样的操作符把它流到宏中。

ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length";

for (int i = 0; i < x.size(); ++i) {
  EXPECT_EQ(x[i], y[i]) << "Vectors x and y differ at index " << i;
}

任何可以流向ostream的东西都可以流向断言宏,特别是C语言的字符串和字符串对象。(注意:宽字符串wchar_t*打印时是以utf-8进行解码的。)

三、test suite 和 test fixture

1. tset suite

简单的test演示:

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

TEST()宏定义并命名了一个测试函数,把它看成一个普通的并且没有返回值的函数即可。
该函数可以正常使用c++语句,你还可以在这个里面加入googletest的断言。
这个test的结果由断言来决定:只要有一个断言失败或者test崩溃,整个test 就失败了,否则,这个test成功。

TEST()的参数从一般到特殊。第一个参数是test suite的名称,第二个参数是test suite中test的名称。这两个名字都必须是有效的C++标识符,它们不应该包含任何下划线(_)。一个测试的全名由其包含的test suite和它的单独test名称组成。来自不同test suite的测试可以有相同的test名称。

//For example, let’s take a simple integer function:
int Factorial(int n);  // Returns the factorial of n
A test suite for this function might look like:
// 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 suite对测试结果进行分组,所以逻辑上相关的test应该在同一个test suite中;换句话说,它们的TEST()的第一个参数应该是一样的。在上面的例子中,我们有两个测试,HandlesZeroInput和HandlesPositiveInput,它们属于同一个test suite: FactorialTest。

2. tset fixture

创建test fixture:

  1. 从::testing::Test中派生出一个类,并且用protected: 修饰它的主体(我们要从子类中访问该类的成员);
  2. 声明要使用的对象;
  3. 必要的话,加上构造/析构函数或者SetUp()/TearDown()函数来为每个test准备对象。
  4. 还可以定义一些可供test共享的子程序。

使用了fixture的话,你的TEST()就要替换成TEST_F()了,因为TEST_F()允许你能够访问fixture中的对象和子程序。同样,要想使用TEST_F(),你首先得创建一个fixture类。

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

TEST_F()的第一个参数是test fixture的类名,TEST_F()没有test suite name(像是TEST()第一个参数一样)这个参数了。
不足:C++的宏 不支持创建一个可以处理两种类型的test的宏。

对于每个用TEST_F()定义的测试,googletest将在运行时创建一个新的fixture,通过SetUp()立即初始化它,运行测试,通过调用TearDown()进行清理,然后删除fixture。注意,同一test suite中的不同测试有不同的fixture对象,googletest总是在创建下一个fixture之前删除一个fixture。 googletest不会为多个测试重复使用同一个fixture。一个测试对fixture的任何改变都不会影响其他测试。保证了每个test的独立性和结果的可靠性。

例子:

/* As an example, let’s write tests for a FIFO queue class named Queue, which has the following interface:*/
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;
  ...
};
/*First, define a fixture class. By convention, you should give it the name FooTest where Foo is the class being tested.*/
class QueueTest : public ::testing::Test {
 protected:
  void SetUp() override {
     // q0_ remains empty
     q1_.Enqueue(1);
     q2_.Enqueue(2);
     q2_.Enqueue(3);
  }

  // void TearDown() override {}

  Queue<int> q0_;
  Queue<int> q1_;
  Queue<int> q2_;
};
/*In this case, TearDown() is not needed since we don’t have to clean up after each test, other than what’s already done by the destructor.*/
/* Now we’ll write tests using TEST_F() and this 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;
}

上述test执行时的情况:

  1. googletest构造一个QueueTest对象(我们称它为t1)。
  2. t1.SetUp()初始化了t1。
  3. 第一个测试(IsEmptyInitially)在t1上运行。
  4. t1.TearDown() 在测试结束后进行清理。
  5. t1被解构。
  6. 上述步骤在另一个 QueueTest 对象上重复,这次是运行 DequeueWorks 测试。

上面同时使用了ASSERT_*和EXPECT_*断言。经验得出,当你希望测试在断言失败后继续测试得出更多的bug时,使用EXPECT_*;当失败后继续测试没有意义时,使用ASSERT_*。例如,Dequeue测试中的第二个断言是ASSERT_NE(n, nullptr),因为我们以后需要解除对指针n的引用,当n为NULL时将导致segfault。

笔者从实践中得出:

  1. 我们在使用test fixture时,创建的class的SetUp函数中会放置一些test必须的前置操作,对于这些前置操作可以使用ASSERT_*断言,因为一旦这些前置操作有误,后面的test就没有继续的必要了。
  2. 对于后面的test,我们一般使用EXPECT_*断言,因为这是我们的待测对象,我们需要完整地跑完所有test,然后整理测试结果。

上述两种情况都是为了提高测试效率,没有绝对的一些选择,我们应该根据实际情况进行调整。

三、调用测试:

1. RUN_ALL_TESTS()

TEST()和TEST_F()隐含地将他们的test注册到googletest。因此,与许多其他的C++测试框架不同,在运行test时不用将其名称一一列出。
定义完test后,你可以用RUN_ALL_TESTS()来运行它们,如果所有的tests都成功了,则返回0,否则返回1。(注意:RUN_ALL_TESTS()运行你的链接单元中的所有测试–它们可以来自不同的test suite,甚至是不同的源代码文件。)

当调用时,RUN_ALL_TESTS()宏执行:

  1. 保存所有googletest标志的状态。
  2. 为第一个测试创建一个测试夹具对象。
  3. 通过SetUp()初始化它。
  4. 在夹具对象上运行测试。
  5. 通过TearDown()清理夹具。
  6. 删除test fixture。
  7. 恢复所有googletest标志的状态。
  8. 为下一个测试重复上述步骤,直到所有测试都运行完毕。

如果发生致命性错误,则后续步骤跳过。

重要提示:你不能忽视RUN_ALL_TESTS()的返回值,否则你会得到一个编译器错误。这样设计的理由是,自动测试服务是根据它的退出代码,而不是它的stdout/stderr输出来决定一个测试是否通过;因此你的main()函数必须返回RUN_ALL_TESTS()的值。

另外,你应该只调用RUN_ALL_TESTS()一次。多次调用它与一些高级的googletest功能(例如,线程安全的死亡测试)相冲突,因此不被支持。

2. 编写main()函数

大多数用户不需要编写自己的主函数,而是使用gtest_main(而不是gtest)进行链接,后者定义了一个合适的入口点。
为了全面一点,下面给出了编写自己的主函数的参考:

// 如果您编写自己的主函数,它应该返回RUN_ALL_TESTS().
// You can start from this boilerplate:
#include "this/package/foo.h"

#include "gtest/gtest.h"

namespace my {
namespace project {
namespace {

// The fixture for testing class Foo.
class FooTest : public ::testing::Test {
 protected:
  // You can remove any or all of the following functions if their bodies would
  // be 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).
  }

  // Class members declared here can be used by all tests in the test suite
  // 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
}  // namespace project
}  // namespace my

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

四、其他

剩余部分只应适用于在测试运行之前需要做一些自定义的事情,而这些事情无法在fixture和suite的框架中表达。(实际没有用到过,留痕以后期查阅)

原文:
The ::testing::InitGoogleTest() function parses the command line for googletest flags, and removes all recognized flags. This allows the user to control a test program’s behavior via various flags, which we’ll cover in the AdvancedGuide. You must call this function before calling RUN_ALL_TESTS(), or the flags won’t be properly initialized.
On Windows, InitGoogleTest() also works with wide strings, so it can be used in programs compiled in UNICODE mode as well.
But maybe you think that writing all those main functions is too much work? We agree with you completely, and that’s why Google Test provides a basic implementation of main(). If it fits your needs, then just link your test with the gtest_main library and you are good to go.
NOTE: ParseGUnitFlags() is deprecated in favor of InitGoogleTest().
Known Limitations
• Google Test is designed to be thread-safe. The implementation is thread-safe on systems where the pthreads library is available. It is currently unsafe to use Google Test assertions from two threads concurrently on other systems (e.g. Windows). In most tests this is not an issue as usually the assertions are done in the main thread. If you want to help, you can volunteer to implement the necessary synchronization primitives in gtest-port.h for your platform.

以下内容来自百度翻译:
:testing::InitGoogleTest()函数解析命令行中的googletest标志,并删除所有可识别的标志。这允许用户通过各种标志来控制测试程序的行为,我们将在AdvancedGuide中介绍这些标志。在调用RUN_ALL_TESTS()之前必须调用此函数,否则将无法正确初始化标志。

在Windows上,InitGoogleTest()也适用于宽字符串,因此它也可以用于以UNICODE模式编译的程序。

但也许你认为编写所有这些主要函数工作量太大了?我们完全同意你的观点,这就是为什么谷歌测试提供了main()的基本实现。如果它符合您的需求,那么只需将您的测试与gtest_main库链接即可。

注意:ParseGUnitFlags()已被弃用,取而代之的是InitGoogleTest()。

已知限制:谷歌测试旨在实现线程安全。在pthreads库可用的系统上,实现是线程安全的。目前,在其他系统(如Windows)上同时使用来自两个线程的Google Test断言是不安全的。在大多数测试中,这不是一个问题,因为断言通常是在主线程中完成的。如果您想提供帮助,可以自愿在gtestport.h中为您的平台实现必要的同步原语。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
GTestGoogle Test的简称,是一个功能强大的C++单元测试框架。它提供了丰富的断言和测试工具,可以方便地编写、运行和管理测试用例。 首先,我们需要下载并安装GTest框架GTest可以从官方网站下载并编译安装,也可以使用包管理工具进行安装。安装完成后,我们就可以在自己的项目中使用GTest进行单元测试了。 在编写测试用例时,我们需要在一个类中定义多个测试函数。每个测试函数都应该以"TEST"宏开始,并且应该在测试函数中使用多个断言来验证被测试代码的行为。例如,我们可以使用"EXPECT_EQ"断言来验证两个值是否相等。当测试函数执行完毕时,我们可以使用"ASSERT_"宏来检查测试是否通过。 GTest还提供了一些高级功能,例如测试夹具(Test Fixture)和参数化测试(Parameterized Test)等。测试夹具可以帮助我们在测试函数之前和之后执行一些共享的设置和清理操作。参数化测试可以使得我们在一组测试数据上运行相同的测试代码,以验证被测试代码在不同输入条件下的行为。 在运行测试时,我们可以使用GTest提供的命令行工具来执行测试用例。它会输出每个测试函数的执行结果以及总体的测试统计信息。我们也可以在IDE中集成GTest,并通过点击运行按钮来执行测试。 总之,GTest是一个非常强大和方便的单元测试框架,可以帮助我们编写高质量的测试用例并验证被测试代码的正确性。通过充分利用GTest提供的功能,我们可以玩转Google单元测试框架,提升软件开发的质量和效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Godchar

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值