嵌入式软件单元测试培训大纲
一、单元测试的理论基础
1.1 软件测试基础知识
软件测试定义:对软件测试的一个定义。
软件测试内容:用V模型展示出软件开发各阶段与软件测试阶段的对应关系。
对软件测试按级别分类:单元测试、部件测试、配置项测试、系统测试。
对软件测试按类型分类:功能测试、性能测试、接口测试、安全性测试、边界测试等
对软件测试方法的分类。
1.2 单元测试的定义
单元:软件设计的最小单位,通常是一个函数或一个类
单元测试:对软件基本组成单元进行的测试
模块的特点:
具有唯一标识(名字)
明确的功能
拥有局部数据
实现特定功能的算法
与其它模块存在数据联系
可被其它模块调用
1.3 为什么要进行单元测试
尽早测试原则:在开发早期发现缺陷
全面的测试:可以在单元层面进行较为全面的检验
便于定位错误:单元粒度小,易于隔离和定位
支持并行测试:多个模块可以同时进行测试
1.4 单元测试的内容与任务
|
测试内容 |
说明 |
|
功能测试 |
验证单元功能是否符合设计 |
|
性能测试 |
精度、时间、容量等指标 |
|
接口测试 |
参数匹配、全局变量一致性、文件操作等 |
|
独立路径测试 |
覆盖程序中的基本路径 |
|
边界条件测试 |
边界值、极限值、循环边界 |
|
局部数据结构 |
变量初始化、数据类型、溢出等 |
|
错误处理 |
异常情况的处理措施 |
|
内存使用 |
动态内存分配、指针使用、内存泄漏 |
1.5 静态测试与动态测试
静态测试:不执行代码的评估(人工检查、自动静态分析)
动态测试:执行代码的测试(白盒测试、黑盒测试)
静态分析的三种方式:
基于模式(规则)的静态分析
数据流静态分析
代码度量计算
二、测试方法
2.1 白盒测试方法
2.1.1 逻辑覆盖法
|
覆盖方法 |
定义 |
特点 |
|
语句覆盖 |
每条语句至少执行一次 |
最弱,可能遗漏分支错误 |
|
判定覆盖(分支覆盖) |
每个判断的真假分支至少执行一次 |
比语句覆盖强 |
|
条件覆盖 |
每个条件的可能取值至少执行一次 |
可能遗漏组合 |
|
判定-条件覆盖 |
同时满足判定覆盖和条件覆盖 |
仍可能遗漏组合 |
|
条件组合覆盖 |
所有条件取值组合至少执行一次 |
覆盖充分但用例多 |
|
MC/DC |
每个条件独立影响判定结果 |
高安全等级强制要求 |
|
路径覆盖 |
覆盖所有可能执行路径 |
最强但可能不可行 |
|
过程/函数调用覆盖 |
实际调用数与总数之比 |
验证调用关系 |
2.1.2 基本路径测试法
步骤:
导出程序控制流图
计算圈复杂度 V(G) = 边数 - 节点数 + 2
确定基本路径集
准备测试用例
2.2 静态分析方法
2.2.1 基于规则(模式)的静态分析
目的:检查代码是否符合编码标准(MISRA、JSF等)
价值:预防已知缺陷类型,约70%的安全性问题可通过此方法发现
规则创建:从历史缺陷中提炼(如爱国者导弹浮点误差案例)
2.2.2 数据流静态分析
原理:静态模拟程序执行路径,发现运行时错误
可发现的问题:资源泄漏、空指针异常、SQL注入、安全漏洞
优势:在早期发现需要数周才能定位的严重缺陷
2.2.3 代码度量
|
度量项 |
推荐值 |
|
模块代码行 |
< 100行 |
|
可执行语句 |
< 50行 |
|
注释比例 |
20%-30% |
|
GOTO语句 |
不允许 |
|
McCabe圈复杂度 |
< 10 |
2.3 测试用例设计方法
等价类划分:将输入数据分成有效和无效等价类
边界值分析:测试边界附近的值
错误推测:基于经验和直觉
2.4 单元测试环境
驱动模块(Driver):调用被测单元,传递参数
桩模块(Stub):模拟被调用模块,返回预设值
2.5 嵌入式环境中的单元测试
策略:主机环境生成测试用例 → 交叉编译 → 目标机执行 → 报告回传
优势:无需硬件即可开始测试,复杂系统可分解独立测试
三、实际案例
案例1:浮点数比较错误(爱国者导弹)
背景:爱国者导弹因浮点数精度误差导致拦截失败,28人死亡
问题:0.1在二进制中是无穷循环,24位近似导致累积误差
教训:不能直接比较浮点数,不能用浮点数作为循环控制变量
MISRA规则:禁止浮点数相等比较,禁止浮点数在循环表达式中
案例2:C语言优先级陷阱
问题:运算符优先级理解错误导致逻辑错误
教训:明确使用括号,避免依赖默认优先级
案例3:switch缺少break
问题:缺少break导致多个case连续执行
教训:每个case后必须有break或明确注释
案例4:赋值与等于混淆
问题:赋值操作被误用为比较
教训:将常量放在左侧 if(0 == x) 可避免此类错误
案例5:局部变量与全局变量同名
问题:同名变量导致作用域混淆
教训:避免局部与全局变量同名
案例6:数组越界
问题:数组索引从0开始,A[10]不存在
教训:循环边界要准确
案例7:多任务代码中的死锁风险
问题:持锁延时可能导致死锁或性能问题
教训:临界区代码应尽量短,避免持锁延时
案例8:ISR中使用浮点运算
问题:中断服务程序中使用浮点运算可能导致不可预测行为
教训:ISR应保持简洁,避免浮点、复杂运算
案例9:宏定义陷阱
问题:宏名后的空格改变了含义
教训:宏定义中不能有空格
案例10:条件判断顺序
问题:C语言中&&和||保证短路求值,但顺序很重要
教训:将可能出错的条件放在前面
四、公司级推广与流程
4.1 单元测试的四个关键问题
|
问题 |
建议 |
|
什么时候做? |
编码阶段,尽早进行 |
|
谁来做? |
开发人员(熟悉代码,便于维护) |
|
用什么来做? |
自动化测试工具(提高效率) |
|
嵌入式怎么做? |
主机测试 + 目标机测试结合 |
4.2 单元测试流程
详细设计阶段完成单元测试计划
建立测试环境,完成测试设计和开发
执行测试用例,记录结果
判定测试结果(预期 vs 实际)
修正错误后进行回归测试
完成单元测试报告
4.3 通过准则
功能与设计需求一致
接口与设计需求一致
能正确处理输入和运行中的错误
发现的问题已修改并通过回归测试
达到规定的测试覆盖率
完成单元测试报告
4.4 静态分析的最佳实践
持续进行:不同阶段逐步递增实施
自动化扫描:日常扫描源码库,结果发送到IDE
团队策略:基于规则的静态分析作为团队规范
流程优先:流程比工具更重要
4.5 自动化流程建议
扫描源码库,保证代码同步
最新修改的代码被静态分析工具检查
每项规则冲突匹配到开发人员
开发者在开发工具中导入违规代码,快速修复
4.6 单元测试与相关活动的关系
|
关系 |
说明 |
|
与静态分析 |
静态分析是单元测试的重要内容和技术 |
|
与覆盖率 |
覆盖率衡量测试充分性,但不是唯一准则 |
|
与算法测试 |
算法测试是单元测试的重要内容 |
|
与性能测试 |
嵌入式单元测试应重视性能测试 |
4.7 常见误区与应对
|
误区 |
应对策略 |
|
覆盖率越高越好 |
70%以上后维护成本高,应关注有效用例 |
|
单元测试能发现所有错误 |
结合功能测试,结构测试无法发现逻辑错误 |
|
测试环境复杂 |
利用自动化工具,建立标准化模板 |
|
时间成本高 |
从核心模块开始,逐步推广 |