文章402
标签224
分类53

C++中使用GoogleTest进行单元测试

GoogleTest是Google开源的一个测试框架,使用这个框架我们可以很方便的对我们的项目进行测试;

本文讲述了GoogleTest的基本使用;

源代码:


C++中使用GoogleTest进行单元测试

安装并配置GoogleTest

得益于 vcpkg,我们可以非常简单的安装和配置GoogleTest库;

vcpkg install gtest

注:

  • GoogleTest的名称为 gtest
  • 你可能需要安装的是 x64的版本;

安装完成之后,根据提示,在我们的CMake项目中增加配置,并为我们的可执行文件添加链接库即可:

add_executable(main_test main_test.cc)

find_package(GTest CONFIG REQUIRED)
target_link_libraries(main_test PRIVATE GTest::gmock GTest::gtest GTest::gmock_main GTest::gtest_main)

至此,配置完成;

关于如何配置 vcpkg 默认安装64位:

GoogleTest官方文档:


第一个GoogleTest例子

下面我们创建一个单测文件;

main_test.cc

#include <iostream>
#include "gtest/gtest.h"

TEST(HelloTest, PrintHello) {
    std::string str{"Hello, World!"};
    ASSERT_EQ(str, "Hello, World!");
    ASSERT_EQ(str.size(), 13);
}

int main(int argc, char **argv) {
    printf("Running main() from %s\n", __FILE__);
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

代码首先引入了头文件 gtest/gtest.h,该头文件中包含了 GoogleTest 中核心的宏等;

随后我们使用了TEST()宏创建了一个单元测试用例;

TEST()宏的使用方法如下:

TEST(test_suite_name, test_case_name) {
    // test body ...
}

第一个参数为整个测试组的名称,第二个参数为测试组中具体某个用例的名称;

最后在 main 函数中:

首先输出了单元测试的文件位置,随后使用启动测试时指定的参数初始化测试,最后调用RUN_ALL_TESTS();开启测试;

启动测试后结果如下:

/Users/jasonkayzk/self-workspace/cpp-learn/cmake-build-debug/main_test
Running main() from /Users/jasonkayzk/self-workspace/cpp-learn/main_test.cc
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from HelloTest
[ RUN      ] HelloTest.PrintHello
[       OK ] HelloTest.PrintHello (0 ms)
[----------] 1 test from HelloTest (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[  PASSED  ] 1 test.

执行成功!


断言

gtest 提供了大量的测试断言函数,大体上分为了两类:

  • ASSERT_*:执行失败,退出当前的测试函数立即返回(注意:并非退出当前案例);
  • EXPECT_*:执行失败,并不会退出当前测试函数,继续向下执行;

下面给出了两个例子:

TEST(ExpectAndAssert, ExpectTest) {
    auto add = [](const int x, const int y) { return x + y; };

    EXPECT_EQ(add(1, 2), 4);
    EXPECT_EQ(add(1, 2), 3);
}

TEST(ExpectAndAssert, AssertTest) {
    auto subtract = [](const int x, const int y) { return x - y; };

    ASSERT_EQ(subtract(3, 1), 3);
    ASSERT_EQ(subtract(3, 1), 2);
}

执行后输出:

[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from ExpectAndAssert
[ RUN      ] ExpectAndAssert.ExpectTest
/Users/kylinkzhang/self-workspace/cpp-learn/main_test.cc:15: Failure
Expected equality of these values:
  add(1, 2)
    Which is: 3
  4
[  FAILED  ] ExpectAndAssert.ExpectTest (0 ms)
[ RUN      ] ExpectAndAssert.AssertTest
/Users/kylinkzhang/self-workspace/cpp-learn/main_test.cc:22: Failure
Expected equality of these values:
  subtract(3, 1)
    Which is: 2
  3
[  FAILED  ] ExpectAndAssert.AssertTest (0 ms)
[----------] 2 tests from ExpectAndAssert (0 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (0 ms total)
[  PASSED  ] 0 tests.
[  FAILED  ] 2 tests, listed below:
[  FAILED  ] ExpectAndAssert.ExpectTest
[  FAILED  ] ExpectAndAssert.AssertTest

 2 FAILED TESTS

一些比较常用的断言有:

1、布尔值检查:

Fatal assertion Nonfatal assertion Verifies
ASSERT_TRUE(condition); EXPECT_TRUE(condition); condition is true
ASSERT_FALSE(condition); EXPECT_FALSE(condition); condition is false

2、数值型数据检查:

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

3、字符串比较:

Fatal assertion Nonfatal assertion Verifies
ASSERT_STREQ(expected_str, actual_str); EXPECT_STREQ(expected_str, actual_str); 两个C字符串有相同的内容
ASSERT_STRNE(str1, str2); EXPECT_STRNE(str1, str2); 两个C字符串有不同的内容
ASSERT_STRCASEEQ(expected_str, actual_str); EXPECT_STRCASEEQ(expected_str, actual_str); 两个C字符串有相同的内容,忽略大小写
ASSERT_STRCASENE(str1, str2); EXPECT_STRCASENE(str1, str2); 两个C字符串有不同的内容,忽略大小写

4、异常检查:

Fatal assertion Nonfatal assertion Verifies
ASSERT_THROW(statement, exception_type); EXPECT_THROW(statement, exception_type); statement throws an exception of the given type
ASSERT_ANY_THROW(statement); EXPECT_ANY_THROW(statement); statement throws an exception of any type
ASSERT_NO_THROW(statement); EXPECT_NO_THROW(statement); statement doesn’t throw any exception

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

6、类型对比断言:

该类断言只有一个::testing::StaticAssertTypeEq<T, T>()

  • 当类型相同时,它不会执行任何内容;
  • 如果不同则会引起编译错误;

需要注意的是:要使代码触发编译器推导类型,否则也会发生编译错误;

如:

template <typename T> class Foo {
 public:
  void Bar() { ::testing::StaticAssertTypeEq<int, T>(); }
};

如下的代码就不会引起编译冲突:

void Test1() { Foo<bool> foo; }

但是下面的代码由于引发了编译器的类型推导,所以会触发编译错误:

void Test2() { Foo<bool> foo; foo.Bar(); }

7、几个特殊的断言:

  • SUCCEED()宏:直接标记断言成功;
  • FAIL()宏:标记致命错误(同ASSERT_*);
  • ADD_FAILURE()宏:标记非致命错误(同EXPECT_*);

更多断言见官方文档:


自定义错误信息

有的时候,我们可能会对默认情况下的错误的输出不满意:

例如:

Failure
Expected equality of these values:
  1+2
    Which is: 3
  4

此时,我们还可以使用 << 操作符来自定义输出信息;

TEST(TestMessage, Message) {
    int result = 4;
    EXPECT_EQ(1 + 2, result) << "1+2 should equals to: " << result;
}

此时输出:

Failure
Expected equality of these values:
  1 + 2
    Which is: 3
  result
    Which is: 4
1+2 should equals to: 4

从输出中,我们可以很明显的看到,此时 result 为 4!


事件机制和TEST_F

使用过 JUnit 的小伙伴,应该对 @Before@After 注解都不陌生;

他们允许我们在开始用例前、用例结束分别进行一些操作;

gtest 也提供了这样的事件,并且分为了多种类型:

  • 全局事件;
  • TestSuite事件;
  • TestCase事件;

下面我们一一来看;

全局事件

要实现全局事件,必须写一个类来继承 testing::Environment,并实现里面的 SetUpTearDown 方法;

此后:

  • SetUp()方法:在所有案例执行前执行;
  • TearDown()方法:在所有案例执行后执行;

同时,还需要告诉 gtest 添加这个全局事件:

需要在main函数中通过 testing::AddGlobalTestEnvironment 方法将事件挂进来;

这也意味着,我们可以写很多个这样的类,然后将他们的事件都挂上去;


TestSuite事件

我们需要写一个类,继承 testing::Test,然后实现两个静态方法:

  • SetUpTestCase()方法:在第一个TestCase之前执行;
  • TearDownTestCase()方法:在最后一个TestCase之后执行;

在编写测试案例时,我们需要使用 TEST_F 这个宏,第一个参数必须是我们上面类的名字,代表一个TestSuite;


TestCase事件

TestCase事件是挂在每个案例执行前后的,实现方式和上面的几乎一样,不过需要实现的是SetUp方法和TearDown方法:

  • SetUp()方法:在每个TestCase之前执行;
  • TearDown()方法:在每个TestCase之后执行;

事件机制可以很好的帮助我们简化测试,例如:

我们可以使用事件机制来在测试函数之间共享数据;

下面提供了一个使用各种事件的例子:

class GlobalEvent : public testing::Environment {
public:
    void SetUp() override {
        std::cout << "Before any case, Global" << std::endl;
    }
    void TearDown() override {
        std::cout << "After all cases done, Global" << std::endl;
    }
};

class VectorTest : public ::testing::Test {
protected:
    // set resources before test
    void SetUp() override {
        vec.push_back(1);
        vec.push_back(2);
        vec.push_back(3);
    }

    // clean up resources after test
    void TearDown() override {
        vec.clear();
    }

    static void SetUpTestCase() {
        std::cout << "SetUpTestCase()" << std::endl;
    }

    static void TearDownTestCase() {
        std::cout << "TearDownTestCase()" << std::endl;
    }

    std::vector<int> vec;
};

// Here we are using TEST_F, not TEST
TEST_F(VectorTest, PushBack) {
    // We changed vec here, but this is invisible to other test cases
    vec.push_back(4);
    EXPECT_EQ(vec.size(), 4);
    EXPECT_EQ(vec.back(), 4);
}

TEST_F(VectorTest, Size) {
    ASSERT_EQ(vec.size(), 3);
}

int main(int argc, char **argv) {
    printf("Running main() from %s\n", __FILE__);
    ::testing::AddGlobalTestEnvironment(new GlobalEvent); // add env
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

首先,我们定义了 GlobalEvent 类,它继承了 testing::Environment,用于定义在整个测试开始之前、之后的操作;

随后,我们定义了 VectorTest 类,它继承了 testing::Test,用于定义在此测试组以及单个测试集合开始之前、之后的操作;

接着,我们使用 TEST_F 定义了两组个测试用例;

最后,我们在 main 函数中注册了我们之前定义的环境变量:::testing::AddGlobalTestEnvironment(new GlobalEvent);

执行用例,我们得到下面的输出:

Running main() from /Users/jasonkayzk/self-workspace/cpp-learn/main_test.cc
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
Before any case, Global
[----------] 2 tests from VectorTest
SetUpTestCase()
[ RUN      ] VectorTest.PushBack
[       OK ] VectorTest.PushBack (0 ms)
[ RUN      ] VectorTest.Size
[       OK ] VectorTest.Size (0 ms)
TearDownTestCase()
[----------] 2 tests from VectorTest (0 ms total)

[----------] Global test environment tear-down
After all cases done, Global
[==========] 2 tests from 1 test suite ran. (0 ms total)
[  PASSED  ] 2 tests.

需要注意的是:在上面的两个测试中:

// Here we are using TEST_F, not TEST
TEST_F(VectorTest, PushBack) {
    // We changed vec here, but this is invisible to other test cases
    vec.push_back(4);
    EXPECT_EQ(vec.size(), 4);
    EXPECT_EQ(vec.back(), 4);
}

TEST_F(VectorTest, Size) {
    ASSERT_EQ(vec.size(), 3);
}

在一个测试函数中修改数据,并不会影响到其它测试函数;

这是因为,每个单独的测试用例都会单独调用我们重载过的 SetUpTearDown 函数!


Appendix

参考文章:

源代码:



本文作者:Jasonkay
本文链接:https://jasonkayzk.github.io/2022/05/09/C++中使用GoogleTest进行单元测试/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可