使用 API 命令导入单元测试的自定义代码
此示例演示了如何使用 API 将自定义 C 代码导入 Simulink 进行单元测试。单元测试独立于自定义代码库测试一个或多个函数。对于单元测试,Simulink Test 代码导入器从指定的自定义代码生成一个测试沙箱和一个包含 C Caller 模块的库。
热泵控制器自定义代码文件
热泵控制器的完整代码位于以下 C 代码源和头文件中:
源文件位于 src 目录中:
tempController.cutils.c
头文件位于 include 目录中:
tempController.hutils.hcontrollerTypes.h
tempController.c 文件包含热泵机组的自定义 C 代码的算法。该文件中的 heatpumpController 函数使用室温 (Troom_in) 和设定温度 (Tset) 作为输入。输出是 pump_control_bus 类型结构体,其信号控制风扇、热泵以及热泵的方向(加热或冷却)。pump_control_bus 结构体具有以下字段:fan_cmd、pump_cmd 和 pump_dir。pump_control_bus 结构体类型在 controllerTypes.h 文件中定义。heatpumpController 函数的输出是:
heatpumpController 函数使用了两个工具函数 absoluteTempDifference 和 pumpDirection,这两个函数在 utils.c 文件中定义。absoluteTempDifference 函数返回 Tset 和 Troom_in 之间的绝对差值,结果为双精度浮点数.pumpDirection 函数返回以下 PumpDirection 类型枚举值之一:
PumpDirection 枚举 type 在 controllerTypes.h 文件中定义。
导入热泵控制器代码并自动创建桩件
此示例仅使用 tempController.c 创建测试沙箱并将其导入 Simulink。您使用沙箱仅对 heatpumpController 函数执行单元测试,而不是对完整代码执行单元测试。生成沙盒会自动为 heatpumpController 函数、absoluteTempDifference 和 pumpDirection 使用的工具函数创建桩件。由于工具函数未在 tempController.c 文件中定义,且未包含 utils.c 和 utils.h 文件,因此代码导入器会创建桩件,因此代码不会出错。
设置 CodeImporter 对象
为热泵控制器自定义代码创建一个 CodeImporter 对象的实例。将 OutputFolder 属性设置为 $pwd$ 会将 $ 符号之间的字符串评估为 MATLAB 表达式。将 OutputFolder 设置为 $pwd$ 以指定当前文件夹作为输出文件夹。将 SourceFiles 属性设置为 tempController.c 目录中的 src 文件。也使用 $ 符号来指定 CustomCode 属性的文件位置。
obj = sltest.CodeImporter('heatpumpController'); obj.OutputFolder = "$pwd$"; obj.CustomCode.SourceFiles = "$fullfile('src','tempController.c')$"; obj.CustomCode.IncludePaths = fullfile('include'); obj.CustomCode.GlobalVariableInterface = true;
创建测试沙盒
使用所需的测试类型和沙盒设置配置 CodeImporter 对象。
要为自定义代码中的指定 heatpumpController 函数创建测试沙盒,请设置 TestType 属性 UnitTest。对于此示例,使用 GenerateAggregatedHeader 沙盒模式。有关不同沙盒模式的信息,请参阅 sltest.CodeImporter.SandboxSettings。
将 SandboxSettings.CopySourceFiles 设置为 true 会将指定的源文件复制到测试沙箱中。
请注意,您只能对单个源文件使用 GenerateAggregatedHeader 沙盒模式。
obj.TestType = "UnitTest"; obj.SandboxSettings.Mode = "GenerateAggregatedHeader"; obj.SandboxSettings.CopySourceFiles = true;
创建沙盒。此测试沙箱与原有的自定义代码库是隔离的。如果存在,将 Overwrite 设置为 on 会覆盖现有的测试沙盒。默认情况下,Overwrite 是 off。
obj.createSandbox('Overwrite','on');
createSandbox 方法在指定的输出文件夹中创建 heatpumpController_sandbox 目录,在此示例中该文件夹为当前工作文件夹。
沙盒目录包含以下子目录:
src:该目录包含复制的源文件
tempController.c。include:该目录包含在沙箱tempController.c目录中编译src所需的包含文件。该目录还包含aggregatedHeader.h文件,其中包含编译tempController.c所需的所有符号。autostub:此目录包含auto_stub.c和auto_stub.h文件,其中保存了absoluteTempDifference和pumpDirection工具函数.的自动生成的桩件manualstub:该目录包含man_stub.c和man_stub.h文件,它们定义任何手动指定的桩件。默认情况下,这些文件没有定义任何函数。
导入测试沙盒
将沙盒代码导入 Simulink。
obj.import('Functions','heatpumpController');
import 函数创建沙盒。它还创建一个包含名为 heatpumpController 的 C Caller 模块的库,该库包含一个内部测试框架,您可以使用它对 heatpumpController 执行单元测试。该库附加到 Simulink 数据字典,该字典分别将 pump_control_bus 和 PumpDirection 定义为 Simulink.Bus 对象和 Simulink 枚举信号。
C Caller 模块附带一个内部测试框架,用于对 heatpumpController 函数进行单元测试。
C Caller 模块上的输入和输出端口
因为在导入之前将 CustomCode.GlobalVariableInterface 设置为 true,所以 import 函数会为 absoluteTempDifference 中的 pumpDirection 和 auto_stub.c 全局变量创建桩件并为它们创建端口。有关信息,请参阅 Automatically infer global variables as function interfaces。
这是从 heatpumpController 函数生成的 C Caller 模块,heatpumpController:

这些是 auto_stub.c 文件中为 absoluteTempDifference 和 pumpDirection: 自动生成的全局变量
/*************************************************************************/
/* Generated Global Variables for Stubbed Functions Interface */
/*************************************************************************/
double SLStubIn_absoluteTempDifference_p1;
double SLStubIn_absoluteTempDifference_p2;
double SLStubOut_absoluteTempDifference;
double SLStubIn_pumpDirection_p1;
double SLStubIn_pumpDirection_p2;
PumpDirection SLStubOut_pumpDirection;
double absoluteTempDifference( double absoluteTempDifference_p1, double absoluteTempDifference_p2)
{
SLStubIn_absoluteTempDifference_p1 = absoluteTempDifference_p1;
SLStubIn_absoluteTempDifference_p2 = absoluteTempDifference_p2;
return SLStubOut_absoluteTempDifference;
}
PumpDirection pumpDirection( double pumpDirection_p1, double pumpDirection_p2)
{
SLStubIn_pumpDirection_p1 = pumpDirection_p1;
SLStubIn_pumpDirection_p2 = pumpDirection_p2;
return SLStubOut_pumpDirection;
}
在 absoluteTempDifference 函数自动生成的桩件中,全局变量 SLStubIn_absoluteTempDifference_p1 和 SLStubIn_absoluteTempDifference_p2 保存了该函数的输入参量。该函数返回存储在 SLStubOut_absoluteTempDifference 中的值。类似地,pumpDirection 保存输入参量并返回 SLStubOut_pumpDirection。
要使用通过自动创建的桩件创建的测试框架,请参阅下图。为输入和输出添加总线。为了能够连接 Simulink 信号进行仿真,请连接 Tset、Troom_in 的输入以及全局变量 SLStubOut_absoluteTempDifference 和 SLStubOut_pumpDirection 的预期输出,同样,按照图中所示连接输出。您可以使用输入和输出来观测 heatpumpController 传递给 absoluteTempDifference 和 pumpDirecton 子函数调用的内部值。

将自动创建的桩件更改为手动桩件
例如,如果您想生成桩件的预期输出作为自动生成端口的输入,您可以用手动桩件替代创建沙箱时自动生成的桩件。切换到使用手动桩件后,您可以更新现有的沙箱并再次导入它。
手动修改自动生成的桩件函数
您可以通过更新 man_stub.c 目录中的 man_stub.h 和 manualstub 文件来手动为自动生成的桩件函数提供定义。
manualstubpath = fullfile([obj.LibraryFileName.char '_sandbox'],'manualstub'); helperFunctionToUpdateManualStubs(manualstubpath);
helperFunctionToUpdateManualStubs 函数更新测试沙箱中的手动桩件文件。
更新后的 absoluteTempDifference 函数定义如下:
double absoluteTempDifference(double Tset, double Troom_in){
return (double)fabs(Tset - Troom_in);
}
更新后的 pumpDirection 函数定义如下:
PumpDirection pumpDirection(double Tset, double Troom_in){
return Tset > Troom_in ? HEATING : COOLING;
}
更新现有的测试沙盒
要使用手动桩件函数,请更新沙箱以反映您的更改。将 Overwrite 选项设置为 off 会保留对测试沙箱中 manualstub 目录所做的更改。
obj.createSandbox('Overwrite','off');
更新测试沙箱后,自动桩件目录为空,因为您在指定的自定义代码中定义了所有未定义的符号。
导入更新的测试沙盒
更新测试沙盒后,将沙盒代码导入 Simulink。
obj.import('Functions','heatpumpController');
该库包含一个名为 heatpumpController 的 C Caller 模块,其端口已更新,并且附加到 C Caller 模块的内部测试框架也已更新。
导入函数更新现有库并导入 heatpumpController 函数。就像使用自动生成的桩件时一样,该库附加到 Simulink 数据字典,该字典分别将 pump_control_bus 和 PumpDirection 定义为 Simulink.Bus 对象和 Simulink 枚举信号。
C Caller 模块端口反映了对桩件文件所做的更改。由于 absoluteTempDifference 和 pumpDirection 函数的手动实现没有使用任何全局变量,因此 C Caller 模块仅具有 heatpumpController 函数的输入参量和返回参量的端口。连接到 C Caller 模块的内部测试框架也已更新。

您可以使用创建的 MLDATX 测试文件来测试代码。请参阅使用代码导入向导对导入的自定义代码进行单元测试了解测试示例。
另请参阅
sltest.CodeImporter | sltest.CodeImporter.SandboxSettings | createSandbox | Simulink.CodeImporter.CustomCode | import | Simulink.CodeImporter | Simulink.CodeImporter.ParseInfo | Simulink.CodeImporter.Options