第四章 测试脚本入门:基于MATLAB脚本的自动化测试、回调函数的使用

说实话,很多工程师做MIL测试,都是手动点来点去。

信号改一下,跑一下,看一眼波形,再改一下……

一次两次还行。等你测到第50个测试用例的时候,手都麻了。

我刚开始带项目那会儿,就吃过这个亏。一个紧急项目,客户要求三天内完成200个测试用例的回归验证。手动测?那得测到猴年马月去。后来我花了一天时间写了一套自动化测试脚本,剩下两天全在喝咖啡。嗯,从那以后,我再也没做过纯手工的MIL测试。

4.1 为什么需要自动化测试脚本

你想想看,MIL测试的本质是什么?

就是给模型灌入不同的输入信号,观察输出是否符合预期。

手动测试的流程通常是:打开Simulink → 修改参数 → 运行仿真 → 看Scope → 记录结果 → 下一个用例。

这里面每一步都可以用脚本替代。说白了,自动化测试就是把你的操作步骤翻译成MATLAB代码,让电脑替你干活。

自动化测试的核心价值:

  • 可重复性:同样的测试,跑一百次结果都一样
  • 效率:一次编写,无限次执行
  • 可追溯:测试过程、结果全部留痕
  • 回归能力:模型改了,一键重跑所有用例

我个人习惯,每个项目从一开始就搭建自动化测试框架。哪怕只有10个测试用例,也值得写脚本。因为后面模型迭代的时候,你会发现这10个用例要反复跑几十遍。

4.2 MATLAB脚本基础:从手动到自动

先看一个最简单的例子。假设我们要测试一个PID控制器模型,输入是阶跃信号,输出要稳定在目标值。

手动操作是这样的:

  • 打开模型文件
  • 设置仿真时间
  • 运行仿真
  • 查看输出波形

用脚本怎么写?

% 加载模型
load_system('PID_Controller.slx');

% 设置仿真参数
set_param('PID_Controller', 'StopTime', '10');

% 运行仿真
sim('PID_Controller');

% 获取输出数据
output_data = simout.signals.values;

% 检查是否稳定在目标值
target_value = 1.0;
tolerance = 0.05;
if abs(output_data(end) - target_value) < tolerance
    disp('测试通过:输出稳定在目标值');
else
    disp('测试失败:输出未达到目标值');
end

你看,就这么几行代码,就把手动操作全包了。

我在项目中遇到过一个问题:有些同事写的脚本,跑完仿真后不清理工作区。结果下次再跑,变量冲突,报错一堆。所以我的脚本开头一定会加一句 clear all; close all; clc;,养成习惯。

4.3 回调函数:让模型自己汇报结果

回调函数是什么?

说白了,就是在特定事件发生时,自动执行的一段代码。

Simulink里有很多回调事件:

回调事件 触发时机 典型用途
PreLoadFcn 模型加载前 初始化工作区变量
PostLoadFcn 模型加载后 显示提示信息
StartFcn 仿真开始前 重置计数器、记录时间戳
StopFcn 仿真结束后 自动保存结果、生成报告
InitFcn 模型初始化时 设置参数、加载数据

我最常用的是 StopFcn。每次仿真跑完,自动把结果存到指定文件夹,文件名带上时间戳,这样永远不会覆盖之前的测试数据。

小技巧:在模型的File > Model Properties > Callbacks里,可以直接编辑回调函数。我个人习惯把回调函数写在单独的.m文件里,然后在回调里调用它。这样代码更清晰,也方便复用。

4.4 实战:搭建一个完整的自动化测试脚本

好了,理论说完了,咱们来点实际的。

假设我们要测试一个车速控制模块。输入是目标车速和实际车速,输出是油门开度。测试用例有5个:

  1. 匀速工况:目标车速60km/h,实际车速60km/h
  2. 加速工况:目标车速从60升到80km/h
  3. 减速工况:目标车速从80降到60km/h
  4. 急加速工况:目标车速从60瞬间跳到120km/h
  5. 极限工况:目标车速0km/h(刹车)

手动测这5个工况,至少半小时。用脚本呢?

% 自动化测试主脚本
clear all; close all; clc;

% 定义测试用例
test_cases = {
    '匀速工况',  60, 60, 0;
    '加速工况',  60, 80, 5;
    '减速工况',  80, 60, 5;
    '急加速工况', 60, 120, 1;
    '极限工况',  0,  0,  0
};

% 遍历所有测试用例
for i = 1:size(test_cases, 1)
    case_name = test_cases{i, 1};
    target_speed = test_cases{i, 2};
    actual_speed = test_cases{i, 3};
    ramp_time = test_cases{i, 4};
    
    % 设置模型参数
    set_param('SpeedController/TargetSpeed', 'Value', num2str(target_speed));
    set_param('SpeedController/ActualSpeed', 'Value', num2str(actual_speed));
    set_param('SpeedController/RampTime', 'Value', num2str(ramp_time));
    
    % 运行仿真
    sim('SpeedController');
    
    % 检查结果
    throttle_output = simout.signals.values;
    max_throttle = max(throttle_output);
    min_throttle = min(throttle_output);
    
    % 记录结果
    results(i).name = case_name;
    results(i).max_throttle = max_throttle;
    results(i).min_throttle = min_throttle;
    results(i).status = 'PASS';
    
    % 判断是否通过
    if max_throttle > 100 || min_throttle < 0
        results(i).status = 'FAIL';
        warning(['测试失败:', case_name, ' 油门超出范围']);
    end
    
    disp(['完成测试:', case_name, ' - ', results(i).status]);
end

% 生成测试报告
save('test_results.mat', 'results');
disp('所有测试完成,结果已保存。');

注意:脚本里用到了 set_param 来修改模型参数。这里有个坑——参数名必须和模型里的完全一致,大小写都不能错。我曾经因为一个字母大小写的问题,排查了整整两个小时。建议先用 get_param 把参数名打印出来确认一下。

4.5 回调函数的高级用法:自动生成测试报告

测试跑完了,结果怎么给别人看?

你不能说「我跑过了,没问题」就完事了。项目评审、客户审计,都需要正式的测试报告。

我习惯在模型的 StopFcn 里写一段自动生成报告的逻辑:

% 在StopFcn中调用
function generate_test_report()
    % 获取仿真时间
    sim_time = get_param(gcs, 'SimulationTime');
    
    % 获取输出数据
    output_data = simout.signals.values;
    
    % 计算统计指标
    mean_value = mean(output_data);
    max_value = max(output_data);
    min_value = min(output_data);
    
    % 写入报告文件
    fid = fopen('test_report.txt', 'a');
    fprintf(fid, '========================================\n');
    fprintf(fid, '测试时间:%s\n', datestr(now));
    fprintf(fid, '仿真时长:%.2f秒\n', sim_time);
    fprintf(fid, '输出均值:%.4f\n', mean_value);
    fprintf(fid, '输出最大值:%.4f\n', max_value);
    fprintf(fid, '输出最小值:%.4f\n', min_value);
    fprintf(fid, '========================================\n\n');
    fclose(fid);
    
    disp('测试报告已生成:test_report.txt');
end

这样每次仿真跑完,报告自动追加。你只管跑测试,报告自己就生成了。

4.6 避坑指南

做自动化测试这几年,我踩过的坑不少。分享几个印象深刻的:

  • 路径问题:脚本里用了相对路径,换个电脑就跑不了。我现在一律用 fullfilefileparts 处理路径,绝对路径和相对路径都兼容。
  • 变量污染:多个测试用例共用工作区变量,容易互相干扰。每个用例跑完后,用 clear 清理掉临时变量。
  • 仿真时间不一致:不同用例的仿真时间可能不同。我习惯在脚本里统一设置 StopTime,或者在回调函数里动态调整。
  • 异常处理:仿真过程中可能报错(比如除零、代数环)。用 try-catch 包起来,不要让一个用例的失败导致整个测试中断。

我的建议:刚开始写自动化测试脚本,不要追求一步到位。先写一个能跑通的版本,然后逐步完善。比如先实现单个用例的自动化,再扩展到批量执行,最后加上报告生成和异常处理。迭代开发,稳扎稳打。

好了,这一章的内容就这些。脚本这东西,光看没用,得动手写。找个你手头的模型,试着把它的测试流程自动化。相信我,一旦你尝到了自动化的甜头,就再也回不去手动测试了。