预处理

在《Google Test(GTest)使用方法和源码解析——概况》最后一部分,我们介绍了GTest的预处理特性。现在我们就详细介绍该特性的使用和相关源码。(转载请指明出于breaksoftware的csdn博客)

测试特例级别预处理

Test Fixtures是建立一个固定/已知的环境状态以确保测试可重复并且按照预期方式运行的装置。通过它,我们可以实现测试特例级别和之后介绍的测试用例级别的预处理逻辑。

举一个比较常见的例子:我们要测试向数据库插入(id,name,location)这样的三个数据,那要先构建一个基础数据(0,Fang,Beijing)。我们第一个测试特例可能需要关注于id这个字段,于是它要在基础数据上做出修改,将(1,Fang,Beijing)插入数据库。第二个测试特例可能需要关注于name字段,于是它要在基础数据上做出修改,将(0,Wang,Beijing)插入数据库。第三个测试特例可能需要关注于location字段,于是它要修改基础数据,将(0,Fang,Nanjing)插入数据库。如果做得鲁莽点,我们在每个测试特例前,先将所有数据填充好,再去操作。但是如果我们将其提炼一下,其实我们发现我们只要在每个特例执行前,获取一份基础数据,然后修改其中本次测试关心的一项就可以了。同时这份基础数据不可以在每个测试特例中被修改——即本次测试特例获取的基础数据不会受之前测试特例对基础数据修改而影响——获取的是一个恒定的数据。

我们看下Test Fixtures类定义及使用规则:

  1. Test Fixtures类继承于::testing::Test类。
  2. 在类内部使用public或者protected描述其成员,为了保证实际执行的测试子类可以使用其成员变量(这个我们后面会分析下)
  3. 在构造函数或者继承于::testing::Test类中的SetUp方法中,可以实现我们需要构造的数据。
  4. 在析构函数或者继承于::testing::Test类中的TearDown方法中,可以实现一些资源释放的代码(在3中申请的资源)。
  5. 使用TEST_F宏定义测试特例,其第一个参数要求是1中定义的类名;第二个参数是测试特例名。

其中4这步并不是必须的,因为我们的数据可能不是申请来的数据,不需要释放。还有就是“构造函数/析构函数”和“SetUp/TearDown”的选择,对于什么时候选择哪对,本文就不做详细分析了,大家可以参看https://github.com/google/googletest/blob/master/googletest/docs/FAQ.md#should-i-use-the-constructordestructor-of-the-test-fixture-or-the-set-uptear-down-function。一般来说就是构造/析构函数里忌讳做什么就不要在里面做,比如抛出异常等。

我们以一个例子来讲解

class TestFixtures : public ::testing::Test {
public:TestFixtures() {printf("\nTestFixtures\n");};~TestFixtures() {printf("\n~TestFixtures\n");}
protected:void SetUp() {printf("\nSetUp\n");data = 0;};void TearDown() {printf("\nTearDown\n");}
protected:int data;
};TEST_F(TestFixtures, First) {EXPECT_EQ(data, 0);data =  1;EXPECT_EQ(data, 1);
}TEST_F(TestFixtures, Second) {EXPECT_EQ(data, 0);data =  1;EXPECT_EQ(data, 1);
}

First测试特例中,我们修改了data的数据(23行),第24行验证了修改的有效性和正确性。在second的测试特例中,一开始就检测了data数据(第28行),如果First特例中修改data(23行)影响了基础数据,则本次检测将失败。我们将First和Second测试特例的实现定义成一样的逻辑,可以避免编译器造成的执行顺序不确定从而影响测试结果。我们看下测试输出

[----------] 2 tests from TestFixtures
[ RUN      ] TestFixtures.First
TestFixtures
SetUp
TearDown
~TestFixtures
[       OK ] TestFixtures.First (9877 ms)
[ RUN      ] TestFixtures.Second
TestFixtures
SetUp
TearDown
~TestFixtures
[       OK ] TestFixtures.Second (21848 ms)
[----------] 2 tests from TestFixtures (37632 ms total)

可以见得,所有局部测试都是正确的,验证了Test Fixtures类中数据的恒定性。我们从输出应该可以看出来,每个测试特例都是要新建一个新的Test Fixtures对象,并在该测试特例结束时销毁它。这样可以保证数据的干净。
        我们来看下其实现的源码,首先我们看下TEST_F的实现

#define TEST_F(test_fixture, test_name)\GTEST_TEST_(test_fixture, test_name, test_fixture, \::testing::internal::GetTypeId<test_fixture>())

我们再回顾下在《Google Test(GTest)使用方法和源码解析——自动调度机制分析》中分析的TEST宏的实现

#define GTEST_TEST(test_case_name, test_name)\GTEST_TEST_(test_case_name, test_name, \::testing::Test, ::testing::internal::GetTestTypeId())

可以见得它们的区别就是声明的测试特例类继承于不同的父类。同时使用的是public继承方式,所以子类可以使用父类的public和protected成员。这也是我们在介绍Test Fixtures类编写规则时说的,让使用到的变量置于protected域之下的原因。

#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 {\

我们再看下Test Fixtures类对象在框架中是怎么创建、使用和销毁的。

在TestInfo::Run()函数中有Test Fixtures对象和销毁的代码

  // Creates the test object.Test* const test = internal::HandleExceptionsInMethodIfSupported(factory_, &internal::TestFactoryBase::CreateTest,"the test fixture's constructor");// Runs the test only if the test object was created and its// constructor didn't generate a fatal failure.if ((test != NULL) && !Test::HasFatalFailure()) {// This doesn't throw as all user code that can throw are wrapped into// exception handling code.test->Run();}// Deletes the test object.impl->os_stack_trace_getter()->UponLeavingGTest();internal::HandleExceptionsInMethodIfSupported(test, &Test::DeleteSelf_, "the test fixture's destructor");

因为测试特例类继承于Test Fixtures类,Test Fixtures类继承于Test类,所以我们可以通过厂类生成一个Test类对象的指针,这就是它创建的过程。在测试特例运行结束后,第16~17行将销毁该对象。

在Test类的Run方法中,除了调用了子类定义的虚方法,还执行了SetUp和TearDown方法

  internal::HandleExceptionsInMethodIfSupported(this, &Test::SetUp, "SetUp()");// We will run the test only if SetUp() was successful.if (!HasFatalFailure()) {impl->os_stack_trace_getter()->UponLeavingGTest();internal::HandleExceptionsInMethodIfSupported(this, &Test::TestBody, "the test body");}// However, we want to clean up as much as possible.  Hence we will// always call TearDown(), even if SetUp() or the test body has// failed.impl->os_stack_trace_getter()->UponLeavingGTest();internal::HandleExceptionsInMethodIfSupported(this, &Test::TearDown, "TearDown()");

测试用例级别预处理

这种预处理方式也是要使用Test Fixtures。不同的是,我们需要定义几个静态成员:

  1. 静态成员变量,用于指向数据。
  2. 静态方法SetUpTestCase()
  3. 静态方法TearDownTestCase()

举个例子,我们需要自定义测试用例开始和结束时的行为

  • 测试开始时输出Start Test Case
  • 测试结束时统计结果
class TestFixturesS : public ::testing::Test {
public:TestFixturesS() {printf("\nTestFixturesS\n");};~TestFixturesS() {printf("\n~TestFixturesS\n");}
protected:void SetUp() {};void TearDown() {};static void SetUpTestCase() {UnitTest& unit_test = *UnitTest::GetInstance();const TestCase& test_case = *unit_test.current_test_case();printf("Start Test Case %s \n", test_case.name());};static void TearDownTestCase() {UnitTest& unit_test = *UnitTest::GetInstance();const TestCase& test_case = *unit_test.current_test_case();int failed_tests = 0;int suc_tests = 0;for (int j = 0; j < test_case.total_test_count(); ++j) {const TestInfo& test_info = *test_case.GetTestInfo(j);if (test_info.result()->Failed()) {failed_tests++;}else {suc_tests++;}}printf("End Test Case %s. Suc : %d, Failed: %d\n", test_case.name(), suc_tests, failed_tests);};};TEST_F(TestFixturesS, SUC) {EXPECT_EQ(1,1);
}TEST_F(TestFixturesS, FAI) {EXPECT_EQ(1,2);
}

测试用例中,我们分别测试一个成功结果和一个错误的结果。然后输出如下

[----------] 2 tests from TestFixturesS
Start Test Case TestFixturesS
[ RUN      ] TestFixturesS.SUC
TestFixturesS
~TestFixturesS
[       OK ] TestFixturesS.SUC (2 ms)
[ RUN      ] TestFixturesS.FAI
TestFixturesS
..\test\gtest_unittest.cc(126): error:       Expected: 1
To be equal to: 2
~TestFixturesS
[  FAILED  ] TestFixturesS.FAI (5 ms)
End Test Case TestFixturesS. Suc : 1, Failed: 1
[----------] 2 tests from TestFixturesS (12 ms total)

从输出上看,SetUpTestCase在测试用例一开始时就被执行了,TearDownTestCase在测试用例结束前被执行了。我们看下源码中怎么实现的

// Runs every test in this TestCase.
void TestCase::Run() {
......internal::HandleExceptionsInMethodIfSupported(this, &TestCase::RunSetUpTestCase, "SetUpTestCase()");
......for (int i = 0; i < total_test_count(); i++) {GetMutableTestInfo(i)->Run();}
......internal::HandleExceptionsInMethodIfSupported(this, &TestCase::RunTearDownTestCase, "TearDownTestCase()");
......
}

代码之前了无秘密,以上节选的内容可以说明其执行的先后关系以及执行的区域。

全局级别预处理

顾名思义,它是在测试用例之上的一层初始化逻辑。如果我们要使用该特性,则要声明一个继承于::testing::Environment的类,并实现其SetUp/TearDown方法。这两个方法的关系和之前介绍Test Fixtures类是一样的。

我们看一个例子,我们例子中的预处理

  • 测试开始时输出Start Test
  • 测试结束时统计结果
namespace testing {
namespace internal {
class EnvironmentTest : public ::testing::Environment {
public:EnvironmentTest() {printf("\nEnvironmentTest\n");};~EnvironmentTest() {printf("\n~EnvironmentTest\n");}
public:void SetUp() {printf("\n~Start Test\n");};void TearDown() {UnitTest& unit_test = *UnitTest::GetInstance();for (int i = 0; i < unit_test.total_test_case_count(); ++i) {int failed_tests = 0;int suc_tests = 0;const TestCase& test_case = *unit_test.GetTestCase(i);for (int j = 0; j < test_case.total_test_count(); ++j) {const TestInfo& test_info = *test_case.GetTestInfo(j);// Counts failed tests that were not meant to fail (those without// 'Fails' in the name).if (test_info.result()->Failed()) {failed_tests++;}else {suc_tests++;}}printf("End Test Case %s. Suc : %d, Failed: %d\n", test_case.name(), suc_tests, failed_tests);}};
};
}
}GTEST_API_ int main(int argc, char **argv) {printf("Running main() from gtest_main.cc\n");::testing::AddGlobalTestEnvironment(new testing::internal::EnvironmentTest);testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}

EnvironmentTest的代码我们就不讲解了,我们可以关注下::testing::AddGlobalTestEnvironment(new testing::internal::EnvironmentTest);这句,我们要在调用RUN_ALL_TESTS之前,使用该函数将全局初始化对象加入到框架中。通过这种方式,可以猜测出,我们可以加入多个对象到框架中。我们看下源码中对它们的调度

bool UnitTestImpl::RunAllTests() {
........ForEach(environments_, SetUpEnvironment);
........// Runs the tests only if there was no fatal failure during global// set-up.if (!Test::HasFatalFailure()) {for (int test_index = 0; test_index < total_test_case_count();test_index++) {GetMutableTestCase(test_index)->Run();}}
........std::for_each(environments_.rbegin(), environments_.rend(),TearDownEnvironment);
........
}
static void SetUpEnvironment(Environment* env) { env->SetUp(); }
static void TearDownEnvironment(Environment* env) { env->TearDown(); }

截取的源码已经解释的很清楚了。我们看到environments_是个容器,这也印证了我们对于框架中可以有多个Environment的预期。

Google Test(GTest)使用方法和源码解析——预处理技术分析和应用相关推荐

  1. Google Test(GTest)使用方法和源码解析——Listener技术分析和应用

    在<Google Test(GTest)使用方法和源码解析--结果统计机制分析>文中,我么分析了GTest如何对测试结果进行统计的.本文我们将解析其结果输出所使用到的Listener机制. ...

  2. Google Test(GTest)使用方法和源码解析——私有属性代码测试技术分析

    有些时候,我们不仅要测试类暴露出来的公有方法,还要测试其受保护的或者私有方法.GTest测试框架提供了一种方法,让我们可以测试类的私有方法.但是这是一种侵入式的,会破坏原来代码的结构,所以我觉得还是谨 ...

  3. Google Test(GTest)使用方法和源码解析——自动调度机制分析

    在<Google Test(GTest)使用方法和源码解析--概况 >一文中,我们简单介绍了下GTest的使用和特性.从这篇博文开始,我们将深入代码,研究这些特性的实现.(转载请指明出于b ...

  4. Google Test(GTest)使用方法和源码解析——模板类测试技术分析和应用

    写C++难免会遇到模板问题,如果要针对一个模板类进行测试,似乎之前博文中介绍的方式只能傻乎乎的一个一个特化类型后再进行测试.其实GTest提供了两种测试模板类的方法,本文我们将介绍方法的使用,并分析其 ...

  5. Google Test(GTest)使用方法和源码解析——参数自动填充技术分析和应用

    在我们设计测试用例时,我们需要考虑很多场景.每个场景都可能要细致地考虑到到各个参数的选择.比如我们希望使用函数IsPrime检测10000以内字的数字,难道我们要写一万行代码么?(转载请指明出于bre ...

  6. Google Test(GTest)使用方法和源码解析——断言的使用方法和解析

    在之前博文的基础上,我们将介绍部分断言的使用,同时穿插一些源码.(转载请指明出于breaksoftware的csdn博客) 断言(Assertions) 断言是GTest局部测试中最简单的使用方法,我 ...

  7. Google Test(GTest)使用方法和源码解析——结果统计机制分析

    在分析源码之前,我们先看一个例子.以<Google Test(GTest)使用方法和源码解析--概况 >一文中最后一个实例代码为基准,修改最后一个"局部测试"结果为错误 ...

  8. Google Test(GTest)使用方法和源码解析——死亡测试技术分析和应用

    死亡测试是为了判断一段逻辑是否会导致进程退出而设计的.这种场景并不常见,但是GTest依然为我们设计了这个功能.我们先看下其应用实例.(转载请指明出于breaksoftware的csdn博客) 死亡测 ...

  9. Google Test(GTest)使用方法和源码解析——概况

    GTest是很多开源工程的测试框架.虽然介绍它的博文非常多,但是我觉得可以深入到源码层来解析它的实现原理以及使用方法.这样我们不仅可以在开源工程中学习到实用知识,还能学习到一些思想和技巧.我觉得有时候 ...

最新文章

  1. 第一家线下场景大数据平台Anchor-Point诞生
  2. 保监会:《保险公司信息系统安全管理指引(试行)》
  3. window系统查看端口被哪个进程占用了,并将它结束
  4. tf.sequence_mask
  5. Python实战从入门到精通第六讲——数据结构与算法4之过滤序列元素
  6. OSError: [Errno 22] Invalid argument: ‘\u202aC:\\Windows\\Fonts\\方正粗黑宋简体.ttf‘解决方案
  7. centos7 django mysql_安装和部署centos7 python3。X Django MySQL,centos7python3Xdjangomysql
  8. Solidworks常用技巧
  9. 计算机二级函数lookup函数,Lookup函数“0/”结构的详细剖析
  10. 选修课计算机应用基础学什么,网络选修课-计算机应用基础 -期末考.docx
  11. 一些Mac OS X技巧
  12. matlab treeplot,matlab creats phylogenetic tree
  13. 转行程序员日记---2020-09-18【,勿忘国耻】【回忆青春】
  14. Director类的使用
  15. 吴恩达又双叒叕刷屏了_你准备入坑了吗?
  16. 能上QQ但打不开网页
  17. OpenGL ES EGL 简介
  18. PySpark ERROR: Python in worker has different version 3.9 than that in driver 3.8
  19. 抖音评论采集接口_抖音接口
  20. python:通过python脚本快速执行 bash 命令

热门文章

  1. Windows下命令行及Java+Tesseract-OCR对图像进行(字母+数字+中文)识别,亲测可行
  2. Reading Paper
  3. 视频监控系统供电方式及选择方法
  4. ros web_video_server的使用及Python获取实时画面
  5. 数据结构与算法(3-1)栈(顺序栈、两栈共享空间、链栈、栈的计算器)
  6. 合作交流的好处_孩子都喜欢玩“过家家”,这种儿科游戏,对孩子来说好处多多...
  7. 机器阅读理解(MRC)零基础入门级综述(一)
  8. OpenGL如何处理多个纹理
  9. big endian little endian
  10. 栈 -- 顺序栈、链式栈的实现 及其应用(函数栈,表达式求值,括号匹配)