2. 嵌入式软件测试基础

静态分析,说白了就是让工具替你读代码,找出那些编译器睁一只眼闭一只眼放过去的毛病。我刚开始带测试团队那会儿,最头疼的就是代码合进来之后,跑功能测试跑着跑着就崩了,查半天发现是个很蠢的指针问题。后来我定了个规矩——代码进CI之前,先过静态分析。这一章,咱们就把Cppcheck和PC-Lint这两个老伙计请进工具链。

2.1 为什么需要静态分析工具?

你想想看,编译器只检查语法对不对,它不管你的变量有没有初始化、数组会不会越界、有没有内存泄漏。这些运行时才能暴露的问题,等跑到测试用例里再抓,成本就高了。静态分析就是在编译阶段之前,把代码从头到尾扫一遍,找出潜在的逻辑缺陷和编码规范违规。

核心价值:左移测试。越早发现问题,修复成本越低。我见过一个项目,静态分析在代码提交阶段就拦住了80%的空指针解引用,测试周期直接缩短了40%。

2.2 Cppcheck:开源轻量级选手

Cppcheck是开源工具里我最常用的。它轻量、跨平台、支持C/C++,而且命令行调用非常干净。咱们先把它装起来。

2.2.1 安装Cppcheck

Windows下直接去官网下载安装包,或者用包管理器:

# Ubuntu / Debian
sudo apt-get install cppcheck

# macOS
brew install cppcheck

# Windows (Chocolatey)
choco install cppcheck

装完之后验证一下:

cppcheck --version

能看到版本号,说明装好了。嗯,这一步没什么坑。

2.2.2 命令行调用基础

Cppcheck的命令行设计得很直观。最基本的用法:

cppcheck --enable=all --suppress=missingIncludeSystem ./src/

解释一下参数:

参数 含义 我的建议
--enable=all 启用所有检查项 刚开始用建议全开,后面再按需关闭
--suppress=missingIncludeSystem 忽略系统头文件缺失警告 嵌入式项目经常交叉编译,这个必须加
--xml 输出XML格式报告 集成Jenkins时用这个
--output-file=report.xml 指定输出文件 方便后续解析

我曾经在一个项目里忘了加--suppress=missingIncludeSystem,结果报告里全是系统头文件的警告,真正的代码问题反而被淹没了。所以这个参数,我建议你直接写进脚本里。

2.2.3 输出格式与报告解析

Cppcheck支持多种输出格式。我一般用XML,因为好解析:

cppcheck --enable=all --xml --output-file=cppcheck_report.xml ./src/

生成的XML结构大致是这样:

<?xml version="1.0" encoding="UTF-8"?>
<results>
  <errors>
    <error id="uninitvar" severity="error" msg="Uninitialized variable: p">
      <location file="main.c" line="42"/>
    </error>
  </errors>
</results>

每个<error>节点都包含了问题ID、严重级别、描述和位置。Jenkins的插件可以直接解析这个格式。

小技巧:如果你只想看错误级别的报告,可以加--suppress=warning。我一般先跑全量,再根据团队规范逐步收紧规则。

2.3 PC-Lint:商业级老牌工具

PC-Lint(现在叫PC-lint Plus)是商业工具里的老大哥。它检查得比Cppcheck更细,规则也更严格。我接触PC-Lint是在一个汽车电子项目上,客户强制要求MISRA C规范,Cppcheck搞不定那么细的规则,只能上PC-Lint。

2.3.1 安装与许可配置

PC-Lint的安装比Cppcheck麻烦一点。你需要:

  1. 从官网下载安装包
  2. 运行安装程序,选择安装路径
  3. 配置许可证文件(通常是.lic文件)
  4. 设置环境变量LINT指向安装目录

环境变量配置示例(Windows):

set LINT=C:\lint
set PATH=%PATH%;%LINT%\lint-nt

验证安装:

lint-nt -v

能看到版本和许可信息,就说明装好了。嗯,这里有个坑——许可证文件路径不能有中文,我吃过这个亏。

2.3.2 配置文件(.lnt)的编写

PC-Lint的核心是配置文件。它不像Cppcheck那样靠命令行参数搞定一切,而是通过.lnt文件来组织规则。一个典型的配置文件长这样:

// std.lnt - 标准配置
// 包含基础规则
include(lnt\au-misra3.lnt)   // MISRA C:2012规则
include(lnt\co-msc60.lnt)    // 编译器配置

// 自定义选项
-wlib(1)                     // 库文件警告级别
-w(4)                        // 一般警告级别
-esym(534,printf,scanf)      // 忽略某些函数的特定警告

// 输出格式
-format=msc                  // Visual Studio兼容格式

调用时直接指定配置文件:

lint-nt std.lnt src\*.c

我曾经帮一个团队迁移PC-Lint配置,他们原来的.lnt文件有2000多行,里面塞满了各种-e(抑制警告)的规则。说白了,就是之前的人遇到一个警告就加一条抑制,最后配置变成了一个巨大的补丁。我建议你反过来——先从一个干净的基线开始,按需放开规则,而不是按需关闭。

2.3.3 命令行调用与输出

PC-Lint的输出格式可以自定义。我习惯用MSC格式,因为Jenkins的插件能直接解析:

lint-nt std.lnt src\*.c > lint_report.txt

输出示例:

WARNING: main.c(42): 信息 534 -- 忽略返回值: 'printf' 的返回值
ERROR:   main.c(88): 错误 1764 -- 指针运算可能导致越界

每一行都包含了文件、行号、严重级别和描述。解析起来非常方便。

注意:PC-Lint的警告编号体系很庞大,从1到3000+。刚开始用的时候,建议先跑一遍,把那些你暂时不关心的警告用-e抑制掉,但一定要在注释里写明原因。我曾经见过一个项目,-e了100多个警告,没人知道为什么,最后只能全部重配。

2.4 与Jenkins的初步集成

工具装好了,命令行能跑了,下一步就是把它塞进CI流水线。Jenkins是咱们的老搭档,集成思路很简单:在构建步骤里加一个静态分析步骤,然后把报告收上来。

2.4.1 集成思路

我画了一张图,帮你理清整个流程:

静态分析工具与Jenkins集成流程 代码提交 Jenkins触发构建 Cppcheck扫描 PC-Lint扫描 报告生成 结果发布 邮件通知 说明: Cppcheck和PC-Lint可以并行执行,互不依赖。 报告合并后统一发布,减少Jenkins任务数量。

2.4.2 Jenkins插件选择

Jenkins生态里有两个插件跟咱们相关:

插件名称 适用工具 我的评价
Warnings Next Generation Cppcheck、PC-Lint等 推荐。支持多种解析器,界面友好
Static Analysis Utilities 通用 老牌插件,但维护不如前者积极

我个人习惯用Warnings Next Generation,因为它能同时解析Cppcheck的XML和PC-Lint的MSC格式,一个插件搞定两个工具。

2.4.3 在Jenkins中配置Cppcheck

在Jenkins的构建步骤里,添加一个"Execute shell"步骤:

# 创建报告目录
mkdir -p reports

# 运行Cppcheck
cppcheck --enable=all \
         --suppress=missingIncludeSystem \
         --xml \
         --output-file=reports/cppcheck_report.xml \
         ./src/

# 检查退出码(可选)
if [ $? -ne 0 ]; then
    echo "Cppcheck found issues"
fi

然后在"Post-build Actions"里添加"Publish Warnings Next Generation",选择解析器为"Cppcheck",指定报告文件路径为reports/cppcheck_report.xml。

2.4.4 在Jenkins中配置PC-Lint

PC-Lint的配置类似,但要注意许可证路径:

# 设置环境变量
export LINT=/opt/lint
export PATH=$PATH:$LINT/lint-nt

# 运行PC-Lint
lint-nt std.lnt src/*.c > reports/lint_report.txt

# 检查退出码
if [ $? -ne 0 ]; then
    echo "PC-Lint found issues"
fi

在Warnings Next Generation插件里,选择解析器为"PC-Lint",指定报告文件路径为reports/lint_report.txt。

小技巧:我建议把Cppcheck和PC-Lint放在同一个构建步骤里并行执行,用&和wait控制。这样能节省流水线时间。但要注意,PC-Lint的许可证可能限制并发实例数,别开太多。

2.5 避坑指南

集成过程中有几个坑,我帮你提前踩了:

  • 路径问题:Jenkins的工作空间路径可能包含空格或特殊字符。Cppcheck和PC-Lint对路径中的空格处理不一样,建议用--relative-paths参数让Cppcheck输出相对路径。
  • 退出码:Cppcheck默认找到问题也会返回0。如果你想在发现问题时让构建失败,加--error-exitcode=1。PC-Lint默认返回0,需要加-errorLimit参数控制。
  • 报告解析:Warnings Next Generation插件对报告格式很敏感。如果解析失败,先手动跑一遍工具,看看输出格式是不是标准格式。我遇到过PC-Lint输出里混了乱码,导致插件解析失败的情况。

重要:静态分析工具不是万能的。Cppcheck查不出逻辑错误,PC-Lint也查不出需求遗漏。它们只是帮你把低级问题挡在门外。别指望工具能替代代码评审,工具和人工是互补关系。