资讯详情

CodeBlocks中配置GoogleTest:C++单元测试完整指南

📅 2026/10/20 17:07:38 | 华诺云谱 👁 阅读
CodeBlocks中配置GoogleTest:C++单元测试完整指南
如果你经常写C应该和我有同感写功能代码一时爽等想补测试的时候光环境这一关就能劝退不少人。CodeBlocks是我一直觉得上手很快的轻量IDE但想在它里面用GoogleTest——大家通常简称GTest——做单元测试默认配置是真的帮不上忙。网上能找到的教程大部分是Visual Studio或者纯命令行专门讲CodeBlocks的又往往只给结论不给排查思路。这篇文章就是把我这次从零配通的过程完整记录下来包括为什么要这么配、库怎么编译、工程怎么设置以及最后怎么把这一套固化下来以后新建测试工程不用再折腾。适合看这篇文章的人我理解主要有两类一类是学校或工作里习惯用CodeBlocks写C/C但被老师或代码规范要求补单测的同学另一类是手里有些旧工程不想为了单元测试换IDE只想在现有CodeBlocks环境里老老实实把GTest跑起来的人。无论哪种下面这套流程都是可以直接照着抄作业的。1. 为什么要在CodeBlocks里配GoogleTest先想清楚要解决什么问题1.1 单元测试到底在测什么很多同学觉得单元测试是“额外的工作”其实不是。单元测试的核心是把你写的最小组件——一个函数、一个类——从整个程序里拎出来单独验证输入输出是否符合预期。它能解决的最实际问题有三类第一类是回归。你改了一个工具函数旁边十几个地方都用它改完心里没底跑一遍测试就知道有没有把别人的逻辑搞坏。第二类是边界条件。自己手写测试样例往往只覆盖正常路径测试框架能强迫你把空字符串、负数、超大值、溢出这些边界情况都显式写出来。第三类是设计反馈。一个函数如果难写测试通常意味着它的依赖太深、职责太多这时候测试反而会逼你写出更干净的代码。GTest作为C领域最常用的测试框架提供了基于宏的用例声明、丰富的断言体系、测试夹具Fixture、参数化测试、死亡测试等能力。它不需要打到主程序里去而是作为独立的测试程序运行这正好和CodeBlocks这种偏向教学和中小项目的IDE搭配起来被测代码放一个工程测试代码放另一个工程互不污染。1.2 CodeBlocks在测试这件事上的天然短板CodeBlocks本身是个优秀的IDE支持多编译器、多工程写普通控制台程序和图形程序都很顺手。但在单元测试这件事上它的默认体验几乎是零。新建工程向导里只有Console、GTK、GLUT、SDL这些模板没有任何测试框架的集成选项。这也意味着网上那些“新建项目一键开启GoogleTest支持”的方便路子在CodeBlocks里根本不存在。更现实的问题在于GTest官方不提供Windows下的预编译二进制你必须自己用同一套编译器把源码编译成静态库然后告诉CodeBlocks头文件在哪、静态库在哪、要不要加链接参数。很多人在这一步就卡住了因为CodeBlocks的Build Options不是写一次就行的Debug和Release两个目标要分别确认编译器搜索路径和链接器搜索路径是两个不同的入口填错位置比不填还隐蔽。把这事拆开看其实就三件事拿到GTest源码用本地编译器编译出静态库在CodeBlocks工程里正确引用。下面按这个顺序一步步来。2. 获取与编译GoogleTest库动手前的准备工作2.1 源码从哪拿、版本怎么选GTest的源码在Google托管的代码仓库里能找到也可以直接在仓库的Releases页面下载zip包。找源码这件事本身不难难的是版本选择。我的建议是不要用master分支上的最新代码也不要追特别老的经典版本。最新代码的问题在于可能处于快速变动期接口或者构建脚本和旧教程对不上太老的版本在较新的MinGW编译器下又经常出现C标准兼容问题比如老版本默认用C11特性新编译器虽然支持但某些宏定义的行为已经变了。稳妥的做法是挑一个已经发布了一两年、社区讨论多的release版本这种版本经过大量人验证遇到问题找答案也容易。我自己一般习惯用中段版本既能得到新框架特性又不会踩到刚换构建系统的坑。还有一个容易忽略的点如果是在Windows下从Releases页面下载zip包解压后目录名通常会带上版本号建议解压后把目录名改成一个简洁固定的路径比如C:\Libs\GTest。后面配置CodeBlocks要反复引用这个路径路径里没有空格、没有版本号能省掉很多莫名其妙的麻烦。2.2 用CMake编译出静态库GTest官方推荐用CMake构建。Windows下常见的组合是CMake加MinGW的gcc。这里要注意一个细节你系统里可能还装了Visual Studio的MSVC编译器CMake默认自动探测时可能选错工具链导致后面编译出来的库和CodeBlocks里的MinGW编译器不兼容。为了避免这个坑Windows下建议显式指定生成器。确认MinGW的bin目录已经加入了系统PATH然后打开命令行进入解压后的GTest源码根目录执行cmake -S . -B build -G MinGW Makefiles -DCMAKE_BUILD_TYPERelease cmake --build build --config Release-S .表示当前目录是源码根目录-B build表示构建产物放在build子目录。-G MinGW Makefiles是关键的生成器参数它让CMake去生成能被mingw32-make执行的Makefile而不是Visual Studio的解决方案。-DCMAKE_BUILD_TYPERelease告诉编译过程采用优化和去调试信息的配置。我第一次编译时就把这步想简单了直接执行cmake ..结果生成了Visual Studio格式的工程命令行编译半天报错。后来加上-G参数后一次通过。编译完成后在build目录下找libgtest.a和libgtest_main.a两个静态库文件。不同版本里这两个文件可能直接躺在build根目录也可能放在build/lib那种更深的子目录里用搜索功能找一下就行。如果你是在Linux环境下操作事情更简单cmake -S . -B build -DCMAKE_BUILD_TYPERelease cmake --build build --target gtest gtest_mainLinux下CMake默认就能正确探测到系统编译器不需要指定生成器。编译产物就是libgtest.a和libgtest_main.a。2.3 静态库产物里两个文件是什么关系看到这里你可能会问为什么编译出来两个库libgtest.a是框架本体所有测试宏、断言、运行器都在这libgtest_main.a特别小里面只放了一个main()函数这个函数帮你初始化GTest并调用RUN_ALL_TESTS()。这个设计的价值在于如果你不想自己维护main()链接gtest_main就能直接开跑如果你想完全掌控入口在自己写的main()里做额外的初始化、配置命令行参数、设置测试过滤条件那只要链接libgtest.a就行。所以在配置里有一个高频坑有的人觉得自己写了main()就不需要管了结果把gtest_main也链接进来运行时报“重复定义main函数”。反过来有的人想用自动生成的main()却只链接了libgtest.a编译报“找不到main”。后面配置Linker的时候这俩文件加不加、加哪个要看自己代码里是否写了入口函数二选一别贪心。3. CodeBlocks测试工程配置全流程3.1 创建工程并规划目录结构编译好库之后回到CodeBlocks新建一个测试工程。记住一个前提测试程序是需要输出控制台日志的所以新建工程时必须选Console application而不是GUI。工程名我建议用xxx_tests这类一眼能看懂的名字例如被测模块叫core测试工程就叫core_tests。保存工程时尽量把被测代码和测试代码放在比较近的目录层级方便管理。比较常见的布局是这样项目根目录src放被测的源码文件include放被测模块的头文件tests放测试工程包含所有测试用例和测试入口libs放第三方静态库比如我们刚编译好的GTest我实际开发时会把被测代码原样复制到测试工程里或者直接用相对路径引用。前者更适合快速验证后者更适合长期维护。从长期维护角度说更好的是把被测功能编译成一个静态库测试工程链接这个库和GTest。不过这一步对大多数中小项目来说属于过度设计先跑通再说。新建工程后第一件事是把工程默认生成的main.cpp看清楚。CodeBlocks的Console模板会自动生成一个Hello World的main()函数。如果你后面打算用gtest_main这个文件可以直接删掉如果你打算自己写测试入口那就把里面的内容替换成GTest的初始化代码。3.2 Compiler、Linker与C标准三处关键设置这是整个配置过程的重头戏也是大部分人填错位置的地方。右键点击测试工程名选择“Build options...”会看到一个包含多个标签页的窗口。我们需要动三处。第一处是编译器搜索路径。在左侧选中整个项目根节点这样Debug和Release都会继承切到“Search directories”标签页下的“Compiler”子标签点击Add填入GTest源码里include目录的路径。注意填的是上一级目录而不是include/gtest这一层。因为代码里写的是#include gtest/gtest.h编译器需要在搜索路径下找到gtest这个文件夹所以搜索路径必须指向包含gtest目录的那一层。第二处是链接器搜索路径。还在同一个“Search directories”里这次切到“Linker”子标签点击Add填入存放libgtest.a和libgtest_main.a的目录。也就是说编译器搜索路径负责找头文件链接器搜索路径负责找静态库这是两条独立的路漏掉哪一个都会在编译或链接阶段报错。第三处是链接参数。切到“Linker settings”标签页在“Other linker options”里填写-l gtest -l gtest_main -pthread如果你的代码自己写了main()函数就不需要gtest_main只留-l gtest。-pthread在Windows MinGW环境下通常不是必须的但加上也不会坏事在Linux下不加这个参数链接阶段会报一堆线程相关的undefined reference所以保留它保险。如果填了-l gtest和-l gtest_main后仍然提示找不到库文件另一个更省心的办法是直接在“Other linker options”里写完整的库文件路径例如C:\Libs\GTest\build\libgtest.a C:\Libs\GTest\build\libgtest_main.a虽然不够优雅但遇到奇奇怪怪的路径解析问题时完整路径是最可靠的排查手段。还有一个容易忽略的设置C标准版本。新版GTest要求编译器支持C14或更高老一点的可能只要C11。CodeBlocks默认的GNU C标准往往偏老。切到“Compiler settings”标签页下的“Compiler flags”找到类似“Have g follow the C11 ISO language standard”这种选项至少要勾上对应C11以上的版本。更稳妥的做法是在“Other compiler options”里直接写-stdc14这样不管默认标准是什么都会被强制提升到C14。3.3 写第一个测试用例并成功运行配置完成后在测试工程里新建一个源文件比如test_demo.cpp录入下面内容#include gtest/gtest.h int Add(int a, int b) { return a b; } TEST(AddTest, HandlesPositiveNumbers) { EXPECT_EQ(Add(1, 2), 3); EXPECT_EQ(Add(2, 3), 5); } TEST(AddTest, HandlesNegativeNumbers) { EXPECT_EQ(Add(-1, -2), -3); }如果你没有链接gtest_main还需要单独准备一个test_main.cpp#include gtest/gtest.h int main(int argc, char** argv) { ::testing::InitGoogleTest(argc, argv); return RUN_ALL_TESTS(); }如果已经链接了gtest_main这个入口文件就不用写。两种方式跑出来的结果一样区别只是自己控制入口的能力。我见过不少初学者在这个地方被绕晕其实记住一点RUN_ALL_TESTS()才是真正执行所有用例的函数InitGoogleTest负责解析命令行参数包括后面要讲的--gtest_filter这类内置参数。编译运行后控制台会输出类似下面的结果[] Running 2 tests from 1 test suite. [----------] Global test environment set-up. [----------] 2 tests from AddTest [ RUN ] AddTest.HandlesPositiveNumbers [ OK ] AddTest.HandlesPositiveNumbers (0 ms) [ RUN ] AddTest.HandlesNegativeNumbers [ OK ] AddTest.HandlesNegativeNumbers (0 ms) [----------] 2 tests from AddTest (0 ms total) [] 2 tests from 1 test suite ran. (0 ms total) [ PASSED ] 2 tests.看到[ PASSED ]就说明整个链路已经通了。值得注意的是执行完所有测试后如果测试全部通过程序退出码是0只要有测试失败退出码非0。这个特性对后续接持续集成很重要CodeBlocks里也可以通过脚本直接判断结果。4. 常见问题与排查技巧实录4.1 链接错误undefined reference系列这是配置GTest时最常遇到的错误典型表现是编译能过链接时报一堆undefined reference to ...比如undefined reference totesting::Test::SetUp()这类。出现这种问题原因基本锁定在三个方面第一链接器搜索路径里根本没找到libgtest.a所以库等于没链接第二-l gtest的参数写错了或者大小写不对导致搜不到第三库的搜索路径找到了但那个库是用别的编译器编译出来的比如用MSVC编译出来的是.lib格式MinGW根本读不了。我的排查顺序很简单。先用最笨的办法把Other linker options里的-l gtest换成libgtest.a的完整路径重新编译。如果这一步能通过说明是搜索路径或参数形式的问题如果换成完整路径后依然报错说明库本身和编译器不匹配需要回到第2章重新用MinGW编译。还有一种隐蔽情况就是头文件和库的版本不一致。比如你下载了新版源码但include搜索路径被CodeBlocks解析成了另一个项目里的老版本头文件这种情况下头文件里的符号声明和库里的实际符号对不上也会报undefined reference。遇到这种问题检查一下Search directories里填的路径到底是哪一套源码别让它解析到了系统目录或另一个工程的include。4.2 编译器版本不匹配与库的架构问题CodeBlocks本身只是个壳真正干活的是后面的编译器。同样叫MinGW也有32位、64位之分还有基于DWARF还是SEH异常处理的差异。如果CodeBlocks用的是64位的MinGW-W64编译器但你拿到的GTest库是用32位编译器编出来的链接时会报“file not recognized: file format not recognized”这类错误或者更含糊的“cannot find -lgtest”。这类问题没有捷径只能保证编译库和编译测试工程用的是同一套工具链。判断方法也很直接在CodeBlocks里通过“Settings - Compiler”查看当前选中的编译器路径然后在命令行去同一个目录下执行gcc --version确保编译GTest库时使用的确实是这个路径下的编译器。如果实在不放心还有一个比较稳的办法不预先编译GTest成库而是把GTest的源码文件直接加入到CodeBlocks测试工程里参与编译。这样头文件、源码和测试代码全部由同一个编译器处理彻底绕开库不匹配的问题。缺点是工程里会多出一堆GTest内部源文件编译速度略微变慢但这个方案对新手来说成功率几乎是百分之百。我陪别人排查时遇到底层工具链乱七八糟的环境直接推荐这个办法。4.3 老版本GTest在Windows下遇到的pthread问题如果你拿到的GTest源码版本比较老在Windows MinGW下编译时可能会看到和pthread相关的错误。这是因为老版本GTest在检测到Windows环境时会默认找pthread库来提供线程支持但MinGW环境下的pthread处理和新版本不太一样。解决办法是在CMake配置时手动关闭pthread依赖显式声明cmake -S . -B build -G MinGW Makefiles -DCMAKE_BUILD_TYPERelease -DGTEST_HAS_PTHREAD0这个宏的意思是告诉GTest当前环境没有pthread内部不要依赖它。新版本GTest已经能自动检测Windows环境并做相应处理基本不用手动干预。所以如果你用的是新版本源码还遇到这个问题优先检查CMake缓存是不是旧版本残留的删掉build目录重新配置一次往往就好了。4.4 其他高频小问题速查表踩坑多了以后我整理了一张表把这些年在CodeBlocks里用GTest遇到的问题按“现象-原因-处理”列了出来基本覆盖了大多数求助帖的问题现象可能原因处理方法编译时报main重复定义同时链接了gtest_main又写了main()二选一建议自写入口时删掉gtest_main链接时报WINMAIN相关错误工程创建成了GUI类型新建工程必须选Console application编译通过但运行直接崩溃GTest库是Release配置测试工程是Debug配置或反过来统一两者配置都调成Release或都调成Debug找不到gtest/gtest.h编译器搜索路径填成了include/gtest这一层搜索路径必须指向包含gtest目录的那一层找不到libgtest.a链接器搜索路径没填或库和工程不在同一目录检查Linker搜索路径必要时直接写库文件完整路径测试全过但退出码非0程序里RUN_ALL_TESTS()之外还有其它非零返回值确认main()最后返回的是RUN_ALL_TESTS()的结果控制台中文乱码Windows控制台编码和CodeBlocks编译文件编码不一致把源文件统一保存为UTF-8或调整控制台代码页5. 从“能跑”到“好用”进阶用法与个人心得5.1 用测试夹具TEST_F组织有状态的测试跑通基础用例之后你会发现很多测试需要不止一对输入输出而是需要一套准备数据的代码每个用例开始时都要执行一遍。最简单的办法是把准备代码复制到每个测试函数里但这样既啰嗦又难维护。GTest里解决这个问题的机制叫测试夹具用TEST_F宏声明。写法是定义一个继承自testing::Test的类把准备数据的代码放在SetUp()函数里清理代码放在TearDown()里然后把共同数据声明为类的成员。GTest会保证每个测试用例执行前都创建夹具对象并调用SetUp()执行后调用TearDown()互不干扰。举个例子#include gtest/gtest.h #include stack class StackTest : public ::testing::Test { protected: void SetUp() override { data_.push(1); data_.push(2); } std::stackint data_; }; TEST_F(StackTest, PopOrderMatchesPushOrder) { ASSERT_EQ(data_.top(), 2); data_.pop(); ASSERT_EQ(data_.top(), 1); data_.pop(); EXPECT_TRUE(data_.empty()); }注意这里用的是TEST_F而不是TEST第一个参数必须和夹具类名一致。凡是要访问data_的用例都要放进这个类对应的TEST_F里。这种做法的好处是如果以后要增加前置准备逻辑只需要改SetUp()所有测试都会跟着变。5.2 断言选择与命令行筛选策略GTest的断言家族分两大体系EXPECT_*和ASSERT_*。区别很关键EXPECT失败时只记录错误当前测试函数继续往下跑ASSERT失败时立刻终止当前测试函数。这意味着如果一个测试里有多个相互独立的断言用EXPECT更合适可以看到所有失败点如果一个断言失败后后续步骤的数据无法正常工作那么用ASSERT更合适避免在坏数据上继续执行产生一堆无关错误。在CodeBlocks里运行测试时默认是跑当前测试工程里的所有用例。但有时候你只改了一个模块想单独看某个测试套件的结果可以在运行参数里传--gtest_filter。CodeBlocks里右键工程名选择“Properties - Target Properties”在“Program arguments”里填--gtest_filterStackTest.*命令行里就变成了测试程序后面带上这个参数GTest只运行StackTest套件下的所有测试。想跑单个用例就写--gtest_filterStackTest.PopOrderMatchesPushOrder。多个用例用冒号隔开排除某些用例用负号前缀。这个参数在调试阶段特别好用能把一堆测试快速缩小到真正关心的那一个。5.3 把配置固化成项目模板一次性配置长期复用配置一次GTest不难难的是以后每次新建测试工程都要再配一遍。这个问题我后来用CodeBlocks自带的模板功能解决了。在测试工程配置全部完成后右键工程名选择“Save as template”或者通过“File - Save project as template”把当前工程保存成一个用户模板。CodeBlocks会让你填模板名称和描述之后在新建工程向导里就会多出一个自定义模板选项新建出来的工程自动带着我对好的编译器路径、链接器参数、C标准设置和目录结构。我第一次干这事的时候比较兴奋连续保存了好几个不同场景的模板后来发现其实维护两三个就够用一个基础版只带GTest和空的测试入口用于普通模块测试一个稍复杂的带好了某个常用库的链接配置用于需要三方依赖的项目一个专门用于Windows控制台测试把代码页和编码都调好。这样不管哪个新项目新建完模板后只需要把被测代码放进去就能开始写测试。这个习惯带来的收益很快就能看到配置环境的时间从每次半小时缩短到了几乎为零团队里的同学也没有再遇到那些经典的链接错误、路径错误、编译器不匹配问题。相比于把配置步骤写一篇又长又细的文档发给大家我更推荐直接提供一个配置好的模板工程让别人在新工程上直接开写。就我个人实际经验来说能够把GTest在CodeBlocks里稳定跑起来最大的收获不是测试覆盖率数字本身而是我终于可以在写代码的过程中随手把边界情况记录下来形成可复现的回归测试。以前我总觉得“写测试很麻烦”现在回头看真正麻烦的只是第一次配置环境这一关过了这道坎之后每写一个函数顺手补几个断言反而变成了最自然的动作。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑