6

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

 1 year ago
source link: https://jasonkayzk.github.io/2022/05/09/C++%E4%B8%AD%E4%BD%BF%E7%94%A8GoogleTest%E8%BF%9B%E8%A1%8C%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

参考文章:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK