在当今复杂的嵌入式系统开发中,模型测试已成为确保产品质量和安全性的关键环节。特别是在汽车、轨道交通等安全关键领域,系统化的模型测试不仅是技术要求,更是法规合规的必要条件。
本文将为您详细介绍模型测试的核心概念、方法和最佳实践,帮助您建立一套完整的测试体系。
备注:团队的学习笔记分享,如果需要相关模型测试的工具请联系我们(support@softor.com.cn)文章中如果写的有错误,请批评指正。
在高安全要求的行业,模型测试是满足功能安全标准的必要条件:
| 标准 | 适用领域 | 说明 |
|---|---|---|
| IEC 61508 | 工业自动化 | 功能安全通用标准 |
| EN 50128 | 轨道交通 | 铁路应用软件开发标准 |
| ISO 25119 | 农业机械 | 农业和林业机械安全标准 |
| IEC 62304 | 医疗设备 | 医疗器械软件生命周期标准 |
| ISO 26262 | 汽车电子 | 道路车辆功能安全标准(最新版支持最高工具置信度水平 TCL 3,可达到最高安全级别 ASIL D) |
| ASPICE | 汽车软件 | 汽车软件过程改进和能力评定 |
模型和代码的功能测试
基于需求的测试就是根据产品的功能需求来设计测试用例,确保模型和代码能够正确实现所有要求的功能。简单来说,就是”按要求测试”,确保系统做了它应该做的事情。
业务场景:在高速公路上,ACC系统需要根据前方车辆的速度自动调整本车速度,以保持安全距离。
需求:当前方车辆减速时,ACC系统应自动减速以保持安全距离(安全距离 = 速度 × 0.3秒)
测试用例:
代码示例(简化的ACC控制逻辑):
void acc_control(float target_speed, float front_vehicle_speed, float distance)
{
float safe_distance = target_speed * 0.3; // 安全距离计算公式
if (distance < safe_distance) {
// 距离过近,需要减速
if (front_vehicle_speed < target_speed) {
set_vehicle_speed(front_vehicle_speed);
} else {
set_vehicle_speed(target_speed);
}
} else {
// 距离合适,保持目标速度
set_vehicle_speed(target_speed);
}
}
测试覆盖:确保减速、加速、停止等所有功能需求都被完全测试覆盖
虚拟集成测试(Virtual Integration Testing)是指在虚拟环境中对嵌入式系统进行测试的方法,通过模拟真实环境来验证系统的功能和性能。它不需要实际的硬件,可以在开发早期就进行测试,大大降低了测试成本和风险。
在嵌入式软件测试中,根据测试执行环境的不同,分为以下四个阶段:
| 测试阶段 | 英文全称 | 说明 | 特点 | 测试环境 |
|---|---|---|---|---|
| MiL | Model-in-the-Loop | 模型在环测试 | 在模型层面进行仿真测试,验证模型逻辑正确性 | Simulink/Stateflow等建模环境 |
| SiL | Software-in-the-Loop | 软件在环测试 | 在宿主机上运行生成的代码,验证代码功能 | PC/工作站(Windows/Linux) |
| PiL | Processor-in-the-Loop | 处理器在环测试 | 在目标处理器上运行代码,验证处理器兼容性 | 目标ECU或仿真器 |
| HiL | Hardware-in-the-Loop | 硬件在环测试 | 在实际硬件环境中测试,验证系统集成 | 实际ECU + 传感器/执行器 |
1. MiL(Model-in-the-Loop)- 模型在环测试
% 在Simulink中测试ACC控制模型
sim('acc_control_model.slx');
% 分析仿真结果,验证控制逻辑2. SiL(Software-in-the-Loop)- 软件在环测试
// 在PC上编译并运行生成的C代码
#include "acc_control.h"
int main() {
acc_control_init();
acc_control_run(100.0, 80.0, 10.0); // 测试ACC控制
return 0;
}3. PiL(Processor-in-the-Loop)- 处理器在环测试
// 在目标ECU上运行代码
// 通过串口或CAN总线与PC通信
void main() {
acc_control_init();
while(1) {
float speed = read_speed_sensor();
float target_speed = read_target_speed();
acc_control_run(speed, target_speed, 0.0);
}
}4. HiL(Hardware-in-the-Loop)- 硬件在环测试
// 在HiL台架上测试
// HiL系统模拟车辆动力学、传感器信号等
// ECU接收模拟信号,输出控制信号
// 测试各种驾驶场景和故障情况需求分析 → 模型设计 → MiL测试 → 代码生成 → SiL测试 → PiL测试 → HiL测试 → 实车测试
汽车电子领域:
工业自动化领域:
航空航天领域:
全自动等效性测试
背靠背测试就像是让两个学生做同一份试卷,然后对比他们的答案是否一致。在模型测试中,就是让模型和生成的代码执行相同的测试用例,然后对比它们的输出结果,确保代码生成过程没有引入错误。
业务场景:发动机控制系统需要根据转速、负载等参数精确计算燃油喷射量,以确保发动机高效运行。
模型与代码:
测试用例:
| 测试场景 | 转速(rpm) | 负载(%) | 预期燃油喷射量(ms) |
|---|---|---|---|
| 怠速工况 | 800 | 10 | 2.5 |
| 部分负荷 | 2000 | 50 | 4.8 |
| 全负荷 | 4000 | 100 | 8.2 |
| 急加速 | 3000→4500 | 80→100 | 6.5→9.0 |
测试过程:
代码示例(自动生成的燃油喷射控制代码):
float calculate_fuel_injection(float rpm, float load)
{
// 基于转速和负载的燃油喷射量计算
float base_injection = 0.001 * rpm + 0.05 * load;
float correction = 0.0;
// 温度修正
if (engine_temp < 60.0) {
correction = 0.5;
}
return base_injection + correction;
}
代码及模型覆盖率统计(死代码检测)
覆盖率分析就像是检查我们的测试用例是否覆盖了所有的代码路径,确保没有遗漏任何重要的功能。它可以帮助我们发现哪些代码没有被测试到,哪些代码可能是多余的(死代码)。
| 覆盖类型 | 要求 | 适用场景 |
|---|---|---|
| 语句覆盖 | 100% | 基础要求 |
| 分支覆盖 | 100% | 安全关键功能 |
| MC/DC覆盖 | 100% | 最高安全等级(ASIL D) |
什么是圈复杂度?
圈复杂度(Cyclomatic Complexity)是由Thomas McCabe提出的一种软件度量指标,用于评估代码的复杂度和可测试性。它反映了程序中独立路径的数量。
计算公式:
圈复杂度 = E - N + 2P
其中:
简化计算方法:
圈复杂度 = 判定节点数量 + 1
判定节点包括:if语句、while循环、for循环、case语句等。
圈复杂度标准:
| 圈复杂度 | 代码质量 | 建议 |
|---|---|---|
| 1-10 | 低风险 | 代码简单,易于测试和维护 |
| 11-20 | 中等风险 | 代码复杂度适中,需要关注 |
| 21-50 | 高风险 | 代码复杂,建议重构 |
| >50 | 极高风险 | 代码过于复杂,必须重构 |
实际例子:
// 圈复杂度 = 4(3个if + 1)
void abs_control(float speed, float brake_pressure, float wheel_speed)
{
float slip_ratio = (speed - wheel_speed) / speed;
if (speed > 100 && brake_pressure > 50 && slip_ratio > 0.2) { // 判定1
apply_abs_pulse();
} else if (speed > 50 && slip_ratio > 0.15) { // 判定2
apply_abs_regular();
} else if (brake_pressure > 70) { // 判定3
apply_regular_brake();
} else {
apply_light_brake();
}
}
圈复杂度在测试中的作用:
降低圈复杂度的方法:
降低圈复杂度示例:
// 原始代码:圈复杂度 = 5
void process_order(Order* order) {
if (order != NULL) {
if (order->status == PENDING) {
if (order->amount > 1000) {
if (order->customer->vip_level > 2) {
apply_discount(order, 0.2);
} else {
apply_discount(order, 0.1);
}
} else {
process_normal(order);
}
} else {
reject_order(order);
}
}
}
// 重构后:圈复杂度 = 2
void process_order(Order* order) {
if (order == NULL) return;
if (order->status != PENDING) {
reject_order(order);
return;
}
if (order->amount > 1000) {
apply_vip_discount(order);
} else {
process_normal(order);
}
}
void apply_vip_discount(Order* order) {
if (order->customer->vip_level > 2) {
apply_discount(order, 0.2);
} else {
apply_discount(order, 0.1);
}
}
业务场景:ABS系统需要根据车速和制动压力自动调整制动模式,以防止车轮抱死。
代码示例:
void abs_control(float speed, float brake_pressure, float wheel_speed)
{
// 计算滑移率
float slip_ratio = (speed - wheel_speed) / speed;
if (speed > 100 && brake_pressure > 50 && slip_ratio > 0.2) {
// 高速高压力下的ABS控制
apply_abs_pulse();
} else if (speed > 50 && slip_ratio > 0.15) {
// 中速下的ABS控制
apply_abs_regular();
} else if (brake_pressure > 70) {
// 高压力下的常规制动
apply_regular_brake();
} else {
// 低速或低压力下的轻度制动
apply_light_brake();
}
}
覆盖率测试:
1. 语句覆盖测试用例:
2. 分支覆盖测试用例:
3. MC/DC覆盖测试用例:
4. 死代码检测:
覆盖率报告示例:
| 覆盖类型 | 目标 | 实际 | 状态 |
|---|---|---|---|
| 语句覆盖 | 100% | 100% | ✅ |
| 分支覆盖 | 100% | 100% | ✅ |
| MC/DC覆盖 | 100% | 100% | ✅ |
| 死代码 | 0 | 0 | ✅ |
需求形式化
形式化规格说明就是用数学语言来精确描述需求,消除自然语言的歧义,确保需求的准确性和一致性。简单来说,就是把模糊的文字描述变成精确的数学表达式。
业务场景:安全气囊系统需要在发生碰撞时,根据碰撞严重程度决定是否弹出气囊,以保护乘客安全。
自然语言需求:当汽车发生碰撞时,安全气囊应该在合适的时机弹出
形式化规格说明:
安全气囊弹出条件 = 碰撞加速度 > 30g 且 碰撞持续时间 > 10ms 且 车辆速度 > 20km/h
安全气囊不弹出条件 = 碰撞加速度 ≤ 30g 或 碰撞持续时间 ≤ 10ms 或 车辆速度 ≤ 20km/h
形式化语言表示(使用Z语言):
z
气囊弹出?: bool
气囊弹出? = (碰撞加速度 > 30) ∧ (碰撞持续时间 > 10) ∧ (车辆速度 > 20)
z
可追溯性:
通过形式化描述,我们消除了”合适的时机”这样的模糊表达,明确了安全气囊弹出的具体条件,为后续的验证奠定了基础。
形式验证
形式验证就是使用数学方法来证明系统的正确性,通过穷举所有可能的执行路径,确保系统在所有情况下都能正确运行。这是一种最高级别的验证方法,可以提供数学上的保证。
业务场景:火车信号系统需要控制不同火车在轨道网络中的运行,确保不会发生碰撞。
系统需求:确保在任何情况下,同一轨道上不会有两列火车同时行驶
形式化模型:
形式验证过程:
AG (∀s ∈ 轨道, ∀t1,t2 ∈ 火车, t1 ≠ t2 → ¬(在轨道s上(t1) ∧ 在轨道s上(t2)))代码示例(信号控制逻辑):
bool is_track_free(int track_id)
{
for (int i = 0; i < MAX_TRAINS; i++) {
if (trains[i].current_track == track_id && trains[i].status == RUNNING) {
return false;
}
}
return true;
}
void control_signal(int track_id, int signal_id)
{
if (is_track_free(track_id)) {
set_signal_green(signal_id);
} else {
set_signal_red(signal_id);
}
}
验证结果:
通过形式验证,我们可以确保火车信号系统的安全性,避免碰撞事故的发生。
模型开发的测试流程包括以下7个关键阶段:
需求分析 → 测试设计 → 功能测试 → 等效性测试 → 覆盖率分析 → 形式化验证 → 测试报告与认证
| 开发阶段 | 对应测试方法 | 测试目标 |
|---|---|---|
| 需求分析 | Requirements-based Testing | 确保测试覆盖所有需求 |
| 模型设计 | 功能测试、形式化规格说明 | 验证模型功能正确性 |
| 代码生成 | Back-to-Back Test | 确保代码与模型行为一致 |
| 集成测试 | 覆盖率分析 | 确保代码被充分测试 |
| 系统测试 | 形式化验证 | 确保系统安全性 |
| 测试方法 | 执行环境 | 适用阶段 |
|---|---|---|
| 功能测试 | MiL (Model-in-the-Loop) | 模型设计阶段 |
| 等效性测试 | MiL → SiL → PiL | 代码生成阶段 |
| 覆盖率分析 | SiL, PiL | 集成测试阶段 |
| 形式化验证 | 模型级、代码级 | 全生命周期 |
| 系统验证 | HiL (Hardware-in-the-Loop) | 系统测试阶段 |
根据ISO 26262-6标准,软件单元测试需要采用以下方法:
| 方法 | ASIL A | ASIL B | ASIL C | ASIL D | 说明 |
|---|---|---|---|---|---|
| 1a 基于需求的测试 | ++ | ++ | ++ | ++ | 根据需求规格设计测试用例 |
| 1b 接口测试 | ++ | ++ | ++ | ++ | 验证模块间接口正确性 |
| 1c 故障注入测试 | + | + | + | ++ | 通过注入故障验证容错能力 |
| 1d 资源使用测试 | + | + | + | ++ | 验证内存、CPU等资源使用 |
| 1e 背靠背对比测试 | + | + | ++ | ++ | 对比模型与代码输出一致性 |
| 方法 | ASIL A | ASIL B | ASIL C | ASIL D | 说明 |
|---|---|---|---|---|---|
| 1a 需求分析 | ++ | ++ | ++ | ++ | 分析需求以确定测试范围 |
| 1b 等价类生成与分析 | + | ++ | ++ | ++ | 将输入划分为等价类进行测试 |
| 1c 边界值分析 | + | ++ | ++ | ++ | 测试边界条件下的系统行为 |
| 1d 错误推测 | + | + | + | + | 基于经验推测可能的错误 |
符号说明:
- •
++强烈推荐- •
+推荐
就像是一个测试管理员
主要功能:
实际例子:
假设我们开发一个汽车空调控制系统:
就像是一个严格的考官
主要功能:
实际例子:
开发一个发动机燃油喷射控制模型:
就像是一个翻译官
主要功能:
实际例子:
处理一个汽车安全系统的需求:
就像是一个数学证明专家
主要功能:
实际例子:
验证一个火车信号控制系统:
高质量测试确保按时交付优质产品:
一次测试部署,赋能全流程:
可以无缝集成到现有的工作流程和工具链中:
无需编程知识即可轻松应用于整个测试流程中:
模型测试是确保模型质量和安全性的关键环节。通过遵循功能安全标准(如ISO 26262、IEC 61508等),采用系统化的测试方法(Requirements-based Testing、Back-to-Back Test、Coverage Analysis等),可以有效地:
在高安全要求的领域,选择合适的测试工具类型并建立完善的测试流程,是项目成功的关键保障。
**如果需要进一步的帮助或有其他问题,请随时联系我们。
support@softor.com.cn
tianpengbo@softor.com.cn
**
希望对您的项目有所帮助!