• GTest从入门到入门


    GTest从入门到入门

    1 GTest简介

    GTest是Google的开源库,是一个功能强大的跨平台C++测试功能库。对于非测试工程师的开发人员来说,学习GTest有利于进行代码的单元测试。

    GTest的功能不止于单元测试,事实上,GTest可以适用于各种测试。

    GTest的文档中如是说。但小白只是个初级程序员,所以能做好单元测试已经非常满足了。

    2 GTest 1.8.1 VS2013+CMake 编译

    GTest作为一个开源项目,在github上有完整源码并还在维护中
    GTest传送门
    截至小白写这篇博客为止,GTest的最新版本是v1.11.0,但是很可惜,这个最新版本不支持VS2013,必须要VS2015以上才能使用。鉴于小白工作团队使用的工具所限,小白只能选择最后一个支持VS2013的GTest版本,即v1.8.1。
    从github上把v1.8.1版本的源码下载下来,然后用CMake进行编译。
    这个编译过程非常简单,比之GLOG的少了不少坑。
    在这里插入图片描述

    默认的配置就好,不需要更改任何勾选。
    打开工程之后,直接“生成”->“批生成”->ALL_BUILD和INSTALL的Release x64生成。
    然后掉进第一个坑:
    在这里插入图片描述

    这里的两个bug不是什么大事,只需要修改一下gmock和gmock_main两个项目下的属性:
    在这里插入图片描述

    在C/C++ -> 常规 -> “将警告视为错误”选成“否”。
    再次尝试,有可能遇上以下bug:
    在这里插入图片描述

    这个问题需要在INSTALL项目的属性下,修改“配置属性”-> “生成事件”-> “后期生成事件”,把“命令行”一行内的代码删除。这行命令需要有管理员权限,小白这里没有。简单粗暴,咋就不进行相关操作了,毕竟我们是通过第三方库的方式调用,并不需要注册到系统。
    在这里插入图片描述

    如果一切顺利的话,应该会有7个项目编译成功:
    在这里插入图片描述

    至此编译应该是成功了,我们再建一个工程testGtest目录如下:

    ├─3rdparty
    │  └─gtest
    │      ├─include
    │      │  └─gtest
    │      │      └─internal
    │      │          └─custom
    │      └─lib
    ├─build
    │  └─x64
    │      └─Release
    ├─source
    └─windows
        └─testGtest
            └─x64
                └─Release
                    └─testGtest.tlog
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这里只需要特别注意一下3rdparty目录下的结构:
    gtest的include下一定要按照gtest的源路径关系设置,具体可参考源码路径下的
    googletest-release-1.8.1\googletest\include\gtest
    lib中只需要刚刚编译得到的gtest.lib和gtest_mock.lib,这两个文件从刚刚编译的build路径下的
    build\googlemock\gtest\Release去寻找即可。(这里有一点点坑,编译得到库的位置在googlemock目录下,而没有直接的gtest或googletest目录)
    到这里,我们的准备工作就做完了。(不得不感叹,还是比GLOG编译时遇到的坑要少很多)。

    3. GTest基础

    GTest的文档翔实,地址在GoogleTest Primer
    鉴于我们的目的是以实用为主,所以只需要把一些基本的内容了解一遍:

    3.1 几个概念的辨析

    testtest casetest suite
    test case是测试领域常用的概念,即测试用例。但在GTest中,test case指的是一组测试,而不是通常概念里的单个用例。GTest中所说的单个测试用例则是用名词test。为了统一,GTest在最近发布的版本中有意识地使用test suite来替换原先的test case,防止发生混淆。
    GTest的测试基本上是以组为单位的,要为“组”提供一个命名。接下来的TEST()宏中将会详细介绍。

    3.2 TEST() ASSERT和EXPECT宏

    GTest使用的断言其实是类似于函数调用的宏。可以通过断言类或函数的行为来进行测试。当断言失败时,GTest将会打印断言的源文件及行号位置,以及一条失败信息。GTest还允许提供一条自定义的失败信息,此信息可以添加到断言附加的信息当中去。
    断言是成对出现的,ASSERT_*EXPECT_*这两种断言类的宏都可以实现测试,所不同的是,ASSERT_*类型的断言一旦证伪,则会直接中止测试程序,而若EXPECT_*类型的断言被证伪,则不会立即中止程序 ,而是会继续执行程序。
    因此更加推荐的是EXPECT_*类型的断言。
    下面是一个用来判断x和y是否长度相等,并找出其中不同元素的实例:

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    任何可以流式传输到 ostream 的内容都可以流式传输到断言宏,尤其是 C 字符串和字符串对象。如果将宽字符串(wchar_t*、Windows 上 UNICODE 模式下的 TCHAR*std::wstring)流式传输到断言,则在打印时将转换为 UTF-8。

    创建测试:

    • 使用 TEST()宏定义和命名测试函数。这些是普通的C++无返回值的函数。
    • 在此函数中,以及要包含的任何有效的C++语句,请使用各种 googletest 断言来检查值。
    • 测试的结果由断言决定;如果测试中的任何断言失败(致命或非致命),或者测试崩溃,则整个测试失败。否则,它将成功。
    TEST(TestSuiteName, TestName) {
      ... test body ...
    }
    
    • 1
    • 2
    • 3

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

    例如,一个普通的整型返回值函数

    int Factorial(int n);  // 返回n的阶乘
    
    • 1

    一个测试套件test suite可能如下所示:

    // 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    googletest按测试套件对测试结果进行分组,因此逻辑上相关的测试应该位于同一测试套件中;换句话说,他们的 TEST() 的第一个参数应该是相同的。在上面的示例中,我们有两个测试,HandlesZeroInputHandlesPositiveInput,它们属于同一个测试套件FactorialTest

    3.3 RUN_ALL_TESTS()

    定义测试后,可以使用 RUN_ALL_TESTS() 运行它们,如果所有测试都成功,则返回 0,否则返回 1。请注意,RUN_ALL_TESTS() 运行链接单元中的所有测试 - 它们可以来自不同的测试套件,甚至可以来自不同的源文件。
    可以不精确地概括为:RUN_ALL_TESTS()是整个测试程序的统一入口和统一出口。
    调用时,RUN_ALL_TESTS() 宏:

    • 保存所有谷歌测试标志的状态。
    • 为第一个测试创建测试夹具对象(有关夹具的内容,请参阅TEST_F宏,本文未涉及)。
    • 通过 SetUp() 对其进行初始化。
    • 对夹具对象运行测试。
    • 通过 TearDown() 清理夹具。
    • 删除夹具。
    • 恢复所有谷歌测试标志的状态。
    • 对下一个测试重复上述步骤,直到所有测试都已运行。
    • 如果发生致命故障,将跳过后续步骤。

    重要说明:不能忽略 RUN_ALL_TESTS() 的返回值,否则将收到编译器错误。此设计的基本原理是,自动测试服务根据其退出代码(而不是 stdout/stderr 输出)确定测试是否已通过;因此,main() 函数必须返回 RUN_ALL_TESTS() 的值。
    另外,RUN_ALL_TESTS()只允许调用一次。调用它不止一次与某些高级googletest功能(例如,线程安全的死亡测试)冲突,因此不受支持。

    3.4 ::testing::InitGoogleTest(&argc, argv) 和 main()

    大多数用户不需要编写自己的 main 函数,而是与 gtest_main(而不是 gtest)链接,这定义了一个合适的入口点。本节的其余部分应仅在您需要在测试运行之前执行自定义操作时才适用,这些自定义内容无法在夹具和测试套件的框架内表示。
    示例代码长这样:

    #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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    ::testing::InitGoogleTest()函数解析 googletest 标志的命令行,并删除所有识别的标志。这允许用户通过各种标志来控制测试程序的行为,我们将在高级指南中介绍这些标志。在调用RUN_ALL_TESTS() 之前,必须调用此函数,否则标志将无法正确初始化。
    在Windows上,InitGoogleTest()也适用于宽字符串,因此它也可以在UNICODE模式下编译的程序中使用。

    3.5 小白的示例

    #include <gtest/gtest.h>
    #include <iostream>
    
    using namespace std;
    
    int add(int a, int b)
    {
    	return a + b;
    }
    
    TEST(AddTest, Positive)
    {
    	EXPECT_EQ(add(2, 3), 5);
    	EXPECT_EQ(add(2, 4), 6);
    }
    
    TEST(AddTest, Negative)
    {
    	EXPECT_EQ(add(-2, -3), -5);
    	EXPECT_EQ(add(-2, -1), -3);
    }
    
    int main(int argc, char* argv[])
    {
    	testing::InitGoogleTest(&argc, argv);
    	return RUN_ALL_TESTS();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    使用cmd工具运行并查看结果:
    在这里插入图片描述

    可以看到这里的测试均通过了,满眼的绿灯。
    为了看看错误的结果,我们故意修改了代码。

    TEST(AddTest, Negative)
    {
    	EXPECT_EQ(add(-2, -3), -5);
    	EXPECT_EQ(add(-2, -1), -4); // 强行改变“正确答案”
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    接下来我们就得到了有红灯的测试结果:
    在这里插入图片描述

    我们可以看到所有的测试均进行完毕,并且给出了或passed或failed的结果。

    3.6 进阶之TEST_F()宏

    这部分内容大致浏览了一下,其主要用法和TEST()宏接近一致,但其的最大作用是可以重复使用测试数据进行多次测试。
    由于这篇文章只是“从入门到入门”,这里的内容就留待以后有深入使用研究时再做学习。
    【水平所限,难免错漏】
    【创作不易,轻喷勿骂】

    在这里插入图片描述

  • 相关阅读:
    电脑重装系统word从第二页开始有页眉页脚如何设置
    开发者,微服务架构到底是什么?
    前端页面渲染方式CSR、SSR、SSG
    电影售票系统遇到的问题
    speech studio-神经网络定制自己的声音
    Apache Dubbo 首个 Node.js 3.0-alpha 版本正式发布
    Boosting以及代表算法(Adaboost、GBDT)介绍
    cocos2dx创建工程并在androidstudio平台编译
    下一代龙蜥操作系统 Anolis OS 23 公测版正式发布|2022云栖龙蜥实录
    华为机试 - 勾股数元组
  • 原文地址:https://blog.csdn.net/horsee/article/details/125419749