文章目录
GoogleTest 入门 GoogleTest 入门 GoogleTest 入门
GoogleTest 是 Google 开源的 C++ 测试框架。
基本概念
-
test suite:一系列相关的测试
-
test 或者 test case:
-
test fixture(测试夹具):测试夹具是用于定义多个测试用例所需的公共数据和辅助函数的类,从而实现测试代码的复用。比如在交易系统当中为了测试订单簿,则可以写一个
OrderBookTest测试夹具,在其中撰写添加初始订单的辅助函数。则在每个测试用例中只需要一开始调用这个辅助函数就能得到一个可以继续其它测试的订单簿。 -
test program:包含并运行多个 test suite 的可执行程序。有两种构建 Test Program 的方式:1. 自己编写
main();2. 使用gtest_main()
Q:设计 test suite 的原则是什么,即应该把怎样的测试归到一个 test suite 里面
A:把针对同一个功能模块或同一类行为的测试组织到同一个 Test Suite 当中。比如在交易系统当中,订单簿、风控模块可以各自构成一个 test suite:OrderBookTest 和 RiskControlTest。
两种级别的断言:ASSERT_* 和 EXPECT_*
提供两种级别的断言:
ASSERT_*:如果不满足断言,则测试会立刻 abort。用于较为致命的错误,继续运行测试没意义的情况EXPECT_*:如果不满足断言,测试还会继续运行。用于希望一口气运行完整个测试(收集所有错误信息),不在中途停下来的情况。
可以使用 << 来写自定义的测试失败消息,任何可以被放到 stream 里的东西都可以被放到 assertion macro 中
TEST()
每一个测试用例都是一个 TEST() 宏。严格来说,这不是函数,但是内部可以当作普通函数体来写。这个宏当中的重点是撰写 GoogleTest 提供的 EXPECT_* 和 ASSERT_* 断言。GoogleTest 的设计很巧妙:开发者只需要编写 TEST() (或者 TEST_F()) 代码狂,框架会自动完成类生成、测试注册、对象创建等工作。
TEST(TestSuiteName, TestName) {
// test body
}
比如我当前正在测试订单簿,有一个 test suite 叫 OrderBookTest;此时我想要测试订单簿能否正确添加订单,因此把当前 test case 叫 AddOrder,则可以写:
TEST(OrderBookTest, AddOrder) {
OrderBook book;
book.addOrder(10, 100);
EXPECT_EQ(book.orderCount(), 1) << "addOrder() failed";
}
从写好 TEST() 到测试运行之间发生了什么:
- 编译阶段:GoogleTest 的
TEST()宏会为每一个测试生成独立的 C++ 类和对象 - 程序启动阶段:在
main()执行之前将测试注册到内部管理结构,结构大致如下:
UnitTest // 这个类管理整个 Test Program
│
├── TestSuite: OrderBookTest // Test Suite 负责管理同一组测试
│ ├── TestInfo: AddOrder // Test Info 保存单个测试的元信息、执行结果
│ └── TestInfo: RemoveOrder
│
└── TestSuite: TradingSignalTest
├── TestInfo: GenerateSignal
└── TestInfo: ValidateSignal
- 运行阶段:
RUN_ALL_TESTS()会遍历需要运行的 Test Suites,用户无需 enumerate 所有测试来运行它们
Test Fixture 测试夹具
测试夹具是用于定义多个测试用例所需的公共数据和辅助函数的类,从而实现测试代码的复用。比如在交易系统当中为了测试订单簿,则可以写一个 OrderBookTest 测试夹具,在其中撰写添加初始订单的辅助函数。则在每个测试用例中只需要一开始调用这个辅助函数就能得到一个可以继续其它测试的订单簿。
测试夹具类需要从 testing::Test 类派生。测试夹具类的成员需要是 protected,因为后续 GoogleTest 会为每一个 TEST_F() 自动生成一个继承自测试夹具类的类,这样派生出的类可以访问这里定义的成员。每个 TEST_F() 都会创建独立的测试夹具类的对象,因此每个 TEST_F() 内部对于测试夹具对象的修改不会互相影响。
撰写使用测试夹具的测试的时候,使用 TEST_F() 而不是 TEST()。在 TEST_F() 当中,第一个参数应该是测试夹具类的名字。测试夹具类的名字同时也是 Test Suite 的名字。
TEST_F(TestFixtureClassName, TestName) {
// test body
}
测试夹具除了可以写可复用的业务操作方法,还可以写两个特殊的方法:
SetUp()(注意U要大写)每次运行完构造函数就会运行它。可以在其中写 GoogleTest 的断言来验证构造函数的初始化是否运行成功。TearDown()在析构函数执行之前执行。可以在其中写 GoogleTest 的断言来验证测试结束后的系统状态。而把 RAII 的逻辑留给析构函数。
以下是在订单簿测试中使用测试夹具的例子:
#include <gtest/gtest.h>
class OrderBookTest : public ::testing::Test {
protected:
void SetUp() override {
book.addOrder(100, 10);
}
voud TearDown() override {
book.clear();
}
OrderBook book;
};
TEST_F(OrderBookTest, AddOrder) {
EXPECT_EQ(book.orderCount(), 1);
book.addOrder(101, 20);
EXPECT_EQ(book.orderCount(), 2);
}
运行测试
写好测试用例之后,要一件运行这些用例,有如下的办法。
手写 main() 来运行 GoogleTest 测试
假设分别在项目的 test/unit 和 test/integration 下写了若干单元测试和集成测试,则可以在 test/unit/ 和 test/integration/ 下分别创建一个 main.cpp。两个 main.cpp 可以使用相同的代码,作为测试运行的入口。
#include <gtest/gtest.h>
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
RUN_ALL_TESTS() 会执行当前 test_program 当中所有符合过滤条件的测试,并返回一个整数:
- 0:所有执行的测试都通过
- 非 0:存在测试失败
这个返回值包含测试是否成功的信息,因此不应该丢弃,最好把它作为 main() 的返回值,如此当某个测试失败时(返回值非 0),CTest, CI/CD 等外部工具可能误认为测试成功。
此时在 CMakeLists.txt 当中的 target_link_libraries() 当中应该写 GTest::gtest。因为我们手写了 main()。下面还会讲使用 GTest::gtest_main 从而不用手写 main() 的方案。
使用 CMake 链接 gtest_main 库来运行 GoogleTest 测试
由于 main() 都是一些 boilerplate 代码,GoogleTest 可以让我们专注于测试逻辑,无需关注 main() 的逻辑。只需要在 CMake 当中链接 gtest_main 库。最后生成单元测试和继承测试两个独立的 Test Program,这样就可以一键运行单元测试/集成测试。示例 CMakeLists.txt 如下:
find_package(GTest REQUIRED)
add_executable(unit_tests
test/unit/order_book_test.cpp
test/unit/strategy_test.cpp
)
target_link_libraries(unit_tests PRIVATE
GTest::gtest_main
my_project_lib
)
add_executable(integration_tests
test/integration/trading_system_test.cpp
)
target_link_libraries(integration_tests PRIVATE
GTest::gtest_main
my_project_lib
)
每次写完新的测试文件,就要在 CMakeLists.txt 的 add_executable 当中新增新的测试文件,随后运行 cmake --build build --target unit_tests 。最终就会生成 unit_tests (和 integration_tests) 的 executable,可以一键运行所有的单元测试(和集成)测试。
通过 CTest 运行 GoogleTest 测试
上面是直接使用 GoogleTest 的方式。但是还有一种方式是通过 CTest 来运行 GoogleTest 的测试。CTest 是 CMake 工具链的一部分,负责测试的注册、执行和结果汇总。
需要在 CMakeLists.txt 当中:
enable_testing()启用 CTest 测试功能- GoogleTest 的测试代码本质上仍然是 C++ 代码,需要编译、链接、生成可执行程序
- 因此需要
add_executable()定义一个名为 unit_tests 的可执行目标 target_link_libraries将 GoogleTest 库链接到 unit_testsinclude(GoogleTest)加载 CMake 提供 GoogleTest 的集成支持gtest_discover_tests(unit_tests)使用集成支持当中的辅助函数,自动发现 unit_tests 当中的 GoogleTest 测试,并将它们注册到 CTest
enable_testing()
add_executable(unit_tests
test/unit/order_book_test.cpp
)
target_link_libraries(unit_tests PRIVATE GTest::gtest_main)
include(GoogleTest)
gtest_discover_tests(unit_tests)
随后可以运行
cmake --build build --target unit_tests
ctest --test-dir build --output-on-failure
由 CTest 启动测试程序并收集和汇总测试结果
CTest 相比直接运行测试的优势:
- 更方便 CI/CD 集成
- 方便替换测试框架
- 可以并行执行
gtest_discover_tests() 是 CMake 官方提供的 GoogleTest 模块中的函数。CMake 提供 GoogleTest 的集成支持,即 CMake 内置了专门用于对接 GoogleTest 的辅助模块,使得开发者能够方便地将 GoogleTest 测试加入 CMake 的构建和测试流程。可以把这个集成支持理解为一个适配层。