这个文档主要用于记录学习如何使用GTest + glog来对我们一个系统里面各个模块的各个层类进行单元测试,现在发现如果手搓整个项目的话,不进行单元测试,比较麻烦。
GTest:
glog
第三方库,用于打印调试信息log+断言检查CHECK
GTest 这是一个第三方库。
整个项目说明 目前,我是基于kuipa的项目来学习的,所以,我先介绍一下,整个项目的CMakeLists来了解一下整个项目架构
kuipa项目结构如下
(base) liangji@ubun:~/AI_INFRA/projects/teacher/KuiperLLama$ l -l total 60 drwxrwxr-x 5 liangji liangji 4096 Jun 28 11:29 build/ drwxrwxr-x 2 liangji liangji 4096 Jun 27 17:13 cmake/ -rw-rw-r-- 1 liangji liangji 3521 Jun 28 11:11 CMakeLists.txt drwxrwxr-x 4 liangji liangji 4096 Jun 1 23:56 kuiper/ drwxrwxr-x 2 liangji liangji 4096 Jun 29 00:54 lib/ drwxrwxr-x 2 liangji liangji 4096 Jun 2 00:05 models/ drwxrwxr-x 2 liangji liangji 4096 Jun 2 00:09 demo/ drwxrwxr-x 7 liangji liangji 4096 Jun 1 23:56 test / drwxrwxr-x 2 liangji liangji 4096 Jun 1 23:56 hf_infer/ drwxrwxr-x 2 liangji liangji 4096 Jun 1 23:56 imgs/ drwxrwxr-x 2 liangji liangji 4096 Jun 1 23:56 tmp/ drwxrwxr-x 3 liangji liangji 4096 Jun 1 23:56 tools/ -rw-rw-r-- 1 liangji liangji 2766 Jun 1 23:56 dockerfile -rw-rw-r-- 1 liangji liangji 5060 Jun 1 23:56 readme.md
我们的推理框架的主要代码在kuiper/下,顶层的CMakeLists将这个kuiper/下编译成推理框架llamalib.so
之后我们具体编译出可执行目标的是
我们的GTest, glog,主要是针对test/而言的,
在test/下的CMakeLists里面,
include(../cmake/cuda.cmake) find_package(GTest REQUIRED) find_package(glog REQUIRED) set (link_ext_lib glog::glog GTest::gtest)aux_source_directory(../test DIR_TEST) aux_source_directory(../test/test_cu DIR_TEST_CU) aux_source_directory(../test/test_op DIR_TEST_OP) aux_source_directory(../test/test_model DIR_TEST_MODEL) aux_source_directory(../test/test_tensor DIR_TEST_TENSOR) aux_source_directory(../test/optimized DIR_TEST_OPTIMIZED) add_executable(test_llm ${DIR_TEST} ${DIR_TEST_CU} ${DIR_TEST_OP} ${DIR_TEST_OPTIMIZED} ${DIR_TEST_TENSOR} ${DIR_TEST_MODEL} ) target_link_libraries(test_llm ${link_ext_lib} ) target_include_directories(test_llm PUBLIC ${glog_INCLUDE_DIR} ) target_include_directories(test_llm PUBLIC ${GTest_INCLUDE_DIR} ) target_include_directories(test_llm PUBLIC ../kuiper/include) target_link_directories(test_llm PUBLIC ${PROJECT_SOURCE_DIR} /lib) if (LLAMA3_SUPPORT OR QWEN2_SUPPORT OR QWEN3_SUPPORT) message(STATUS "LINK LLAMA3 SUPPORT" ) find_package(absl REQUIRED) find_package(re2 REQUIRED) find_package(nlohmann_json REQUIRED) target_link_libraries(llama absl::base re2::re2 nlohmann_json::nlohmann_json) endif () target_link_libraries(test_llm llama) set_target_properties(test_llm PROPERTIES WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) set_target_properties(test_llm PROPERTIES CUDA_SEPARABLE_COMPILATION ON)
可以看到,我们的编译目标是test_llm , 然后链接了GTest, glog这两个第三方库。
所以这个test_llm就是我们的单元测试的主程序。
单元测试的框架如下,主程序的main, 写在了test_main.cpp里面
主程序 下面看一下单元测试test_llm这个可执行文件的主程序main
#include <glog/logging.h> #include <gtest/gtest.h> int main (int argc, char * argv[]) { testing::InitGoogleTest(&argc, argv); google::InitGoogleLogging("Kuiper" ); FLAGS_log_dir = "./log/" ; FLAGS_alsologtostderr = true ; LOG(INFO) << "Start Test...\n" ; return RUN_ALL_TESTS(); }
运行后日志为:
(base) liangji@ubun:~/AI_INFRA/projects/teacher/KuiperLLama/build/test$ ./test_llm --gtest_filter=test_buffer.* I20260703 13:25:19.062956 138410764066816 test_main.cpp:10] Start Test... Note: Google Test filter = test_buffer.* [==========] Running 7 tests from 1 test suite. [----------] Global test environment set-up. [----------] 7 tests from test_buffer [ RUN ] test_buffer.use_external1 [ OK ] test_buffer.use_external1 (156 ms)
测试单元 主程序的最后一句return RUN_ALL_TESTS();
调用RUN_ALL_TESTS(), 这个就是GTest里面的函数,跳转到第三方库去执行所有注册了TEST()宏的测试用例
因为是宏函数,所以也不需要提前创建申请。GTest那边调用所有TEST宏函数
而我们注册的TEST(组名,用例名),由于组名,用例名不同,刚好不冲突
#include <gtest/gtest.h> 所以,每个测试组,只要include这个头文件,就能注册TEST宏函数了? 我记得宏函数不得#define TEST()这样来定义宏函数吗?他这个怎么这样就能实现注册?****
那么每个注册的TEST测试单元,要如何来判断该用例是否通过?
不是靠 TEST 函数的返回值(它返回 void),而是靠断言宏在失败时往测试结果对象里写记录 。
static TestResult* current_test_result;#define ASSERT_EQ(a, b) \ if ((a) != (b)) { \ current_test_result->AddFailure("Expected " #a " == " #b); \ return;
测试结果判断 分两大类:
ASSERT_失败立即终止当前测试
断言
含义
ASSERT_TRUE(cond)
cond 为 true
ASSERT_FALSE(cond)
cond 为 false
ASSERT_EQ(a, b)
a == b
ASSERT_NE(a, b)
a != b
ASSERT_LT(a, b)
a < b
ASSERT_LE(a, b)
a <= b
ASSERT_GT(a, b)
a > b
ASSERT_GE(a, b)
a >= b
ASSERT_STREQ(s1, s2)
两个 C 字符串相等
ASSERT_FLOAT_EQ(a, b)
浮点数近似相等
EXPECT_ 失败继续执行 EXPECT_TRUE、EXPECT_EQ、EXPECT_NE……和上面一一对应,只是失败后不 return,继续往下跑。
选择原则
一个测试里只需要检查一件事 → ASSERT_*
一个测试里需要检查多个独立条件,想看所有失败项 → EXPECT_*
项目中只用 ASSERT_*,因为每个测试都很短,一个断言失败后面的预期也没意义了。