适用人群:单元测试写了几个,但每次都是"手动在 PC 上跑一下看看",改了十处代码才想起来"好像该跑一下测试",结果早崩了都不知道的同学。这是
16_嵌入式测试软件篇的收口文。
上篇测试_04解决了"测依赖硬件的代码",这篇解决"怎么让测试自动、持续地跑",把前面 02/03/04 串成一条流水线。
读完你能得到:
① 为什么手工测不靠谱,CI 是什么;
② 用 GitHub Actions / 本地脚本,提交即跑测试;
③ 静态检查(测试_03)怎么挂进同一条流水线;
④ 代码覆盖率 gcov/lcov 白话版,以及它的陷阱;
⑤ 嵌入式怎么做:PC 测逻辑 + 板子只跑冒烟。
一、手工测的痛
假设你现在的状态:本地有一套 Unity 测试,平时这么用——
gcc utils.c test_utils.c unity.c-otest_utils&&./test_utils看起来没问题。但真实情况是:
- 忘了跑:改完代码直接提交,测试压根没跑,bug 跟着进了仓库。
- 只在自己机器跑过:你的环境能过,同事的机器编译器版本不同,一拉就红。
- 没标准:什么叫"测过了"?你心里有数,但流水线不知道。
CI(持续集成,Continuous Integration)就是来解决这个的:你一提交代码,服务器自动拉下来、编译、跑测试、跑静态检查,红了当场告诉你。
一句话:手工测靠自觉,CI 靠流程。自觉会忘,流程不会。
二、CI 长什么样(以 GitHub Actions 为例)
最精简的 CI 配置(.github/workflows/test.yml):
name:embedded-testson:[push,pull_request]jobs:test:runs-on:ubuntu-lateststeps:-uses:actions/checkout@v4-name:编译并跑单元测试run:|gcc utils.c test_utils.c unity.c -o test_utils ./test_utils-name:静态分析run:|cppcheck --error-exitcode=1 --enable=all --std=c99 src/这段代码的意思是:每次 push 或开 PR,GitHub 的服务器就自动做这两件事。任何一步非零退出,整个 CI 标红,提交被拦下。
没有 GitHub 也没关系。本地用个
Makefile+ 一句make test也行,关键是"一键跑全部检查",而不是手动敲三条命令。下面给个最小 Makefile:
CC = gcc SRC = utils.c test_utils.c unity.c test: $(CC) $(SRC) -o test_utils && ./test_utils static: cppcheck --error-exitcode=1 --enable=all --std=c99 src/ check: test staticmake check一条命令,单元测试 + 静态分析全跑。把"测试"变成肌肉记忆。
三、把静态检查挂进来(呼应测试_03)
测试_03讲的-Wall -Werror+ cppcheck,正好作为 CI 的第一、二关:
# 第一关:警告清零(任何 warning 都编不过)gcc-Wall-Wextra-Werror-csrc/*.c# 第二关:静态分析(发现潜在 bug / 违反规范)cppcheck --error-exitcode=1--enable=all--std=c99 src/# 第三关:单元测试(验证行为正确)./test_utils三关全过,代码才算"健康"。这就是把"代码质量"从靠人盯变成靠机器卡。
面试时这句特别好使:"我们工程的 CI 三关——编译零警告、cppcheck 静态分析、Unity 单元测试,任何一关红都合不进去。"这比"我写完了会 review"专业太多。
四、代码覆盖率:你的测试到底覆盖了多少
覆盖率工具回答一个问题:“你写的测试,跑到了多少行代码?”GCC 用 gcov、配合 lcov 出报告:
# 编译时插桩gcc--coverageutils.c test_utils.c unity.c-otest_utils# 跑测试./test_utils# 生成报告gcov utils.c# 命令行看每行是否被覆盖lcov--capture--directory.--output-file cov.info genhtml cov.info --output-directory cov_html# 生成网页报告报告会告诉你:哪些函数 100% 覆盖,哪行if分支从来没被测到。
💡 覆盖率是个"下限指标":低覆盖率一定说明测少了,但高覆盖率不代表测对了。见过有人为了冲 100% 写
TEST_ASSERT_TRUE(1)这种无效断言——跑通了但什么都没验证。别这么干。
覆盖率的陷阱
| 陷阱 | 说明 |
|---|---|
| 追求 100% | 边际成本高、收益低,核心逻辑覆盖到就行 |
| 无效断言冲覆盖率 | TEST_ASSERT_TRUE(1)跑通但没验证行为 |
| 只看行覆盖不看分支 | 一行代码但if/else只测了一半,分支没覆盖 |
五、嵌入式怎么做:PC 测逻辑,板子只跑冒烟
回看测试_01说的"把硬件无关逻辑抽到 PC 上测"。落到 CI 上就是两类测试分开:
CI 自动跑(每次提交): ├─ PC 单元测试:算法/协议/状态机(Unity,秒级) └─ 静态分析:cppcheck + 编译零警告 板端手动/定期跑(烧录后): └─ 冒烟测试:上电能起、关键功能能用(见 `测试_06~10`)MCU 上跑不了庞大的测试框架,也跑不了 gcov,所以逻辑全在 PC 的 CI 里验,板子只做"能不能起来"的冒烟。这是嵌入式测试最务实的分工。
这套分工直接对应前面几篇:
测试_02/04的逻辑测试在 PC,测试_06~10的板级/系统测试在硬件上。
六、新手必踩的坑
| # | 坑 | 后果 | 正确做法 |
|---|---|---|---|
| 1 | CI 挂了没人看 | 等于没挂 | 红了就挡合入,强制修 |
| 2 | 只在自己机器测过 | 别人一拉就红 | 用 CI 在干净环境跑 |
| 3 | 覆盖率凑数 | 假安全 | 断言要真验证行为 |
| 4 | 把所有测试塞板子 | 慢、难维护 | PC 测逻辑、板子只冒烟 |
| 5 | 测试不独立 | 顺序一变就挂 | 每个测试 setUp/tearDown 复位 |
七、总结(软件测试 5 篇收口)
| 篇 | 主题 | 一句话 |
|---|---|---|
| 01 | 总览 | 测试是预防,调试是救火 |
| 02 | 单元测试 | Unity 在 PC 上测纯函数 |
| 03 | 静态分析 | 不运行也能找 bug,MISRA 兜底 |
| 04 | 桩/模拟 | 用接口抽象 + 桩顶掉硬件依赖 |
| 05 | 自动化/CI | 提交即跑,覆盖率看覆盖 |
一句话总结:手工测靠自觉会忘,CI 把"编译零警告 + 静态分析 + 单元测试"串成流水线,红了就挡合入;嵌入式是"PC 测逻辑、板子只冒烟",覆盖率看下限不迷信 100%。
CI 平台不限于 GitHub Actions,GitLab CI、本地 Makefile 思路一致;gcov/lcov 是 GCC 配套工具,搜 “gcov lcov 教程” 即可上手。覆盖率数据以你项目的实际配置为准。
本站相关:测试_01~04(软件测试前四篇)、测试_03(静态分析)、测试_06(硬件测试起手)。
下一篇:测试_06_上电与电源测试——电压电流纹波功耗怎么量