Import Custom Code for Unit Testing Using API Commands
This example shows how to use the API to import custom C code for a heat pump controller into Simulink for unit testing. Unit tests test one or more functions in isolation from the custom code library. For unit tests, the Simulink Test code importer generates a test sandbox and a library containing a C Caller block from the specified custom code.
Heat Pump Controller Custom Code Files
The complete code for the heat pump controller is in these C code source and header files:
The source files are in the src
directory:
tempController.c
utils.c
The header files are in the include
directory:
tempController.h
utils.h
controllerTypes.h
The tempController.c
file contains the algorithm for the custom C code for a heat pump unit. The heatpumpController
function in that file uses the room temperature (Troom_in
) and the set temperature (Tset
) as inputs. The output is pump_control_bus
type structure with signals that control the fan, heat pump, and the direction of the heat pump (heat or cool). The pump_control_bus
structure has these fields: fan_cmd
, pump_cmd
, and pump_dir
. The pump_control_bus
structure type is defined in the controllerTypes.h
file. The output of the heatpumpController
function is:
The heatpumpController
function uses two utility functions, absoluteTempDifference
and pumpDirection
, which are defined in the utils.c
file. The absoluteTempDifference
function returns the absolute difference between Tset
and Troom_in
as a double. The pumpDirection
function returns one of these PumpDirection
type enum values:
The PumpDirection
enum type
is defined in the controllerTypes.h
file.
Import Heat Pump Controller Code and Automatically Create Stubs
This example uses only tempController.c
to create and import a test sandbox into Simulink. You use the sandbox for performing unit testing only on the heatpumpController
function and not on the complete code. Generating the sandbox automatically creates stubs for the utility functions used by the heatpumpController
function, absoluteTempDifference
and pumpDirection
. Since the utility functions are not defined in the tempController.c
file and the utils.c
and utils.h
files are not included, the code importer creates stubs so the code does not error.
Set Up the CodeImporter
Object
Create an instance of a CodeImporter
object for the heat pump controller custom code. Setting the OutputFolder
property to $pwd$
evaluates the string in between the $ symbols as a MATLAB expression. Set OutputFolder
to $pwd$
to specify the current folder as the output folder. Set the SourceFiles
property to the tempController.c
file in the src
directory. Use the $ symbols to specify the file location for the CustomCode
property, too.
obj = sltest.CodeImporter('heatpumpController'); obj.OutputFolder = "$pwd$"; obj.CustomCode.SourceFiles = "$fullfile('src','tempController.c')$"; obj.CustomCode.IncludePaths = fullfile('include'); obj.CustomCode.GlobalVariableInterface = true;
Create the Test Sandbox
Configure the CodeImporter
object with the desired test type and sandbox settings.
To create a test sandbox for the specified heatpumpController
function in the custom code, set the TestType
property UnitTest
. For this example, use the GenerateAggregatedHeader
sandbox mode. For information about the different sandbox modes, see sltest.CodeImporter.SandboxSettings
.
Setting SandboxSettings.CopySourceFiles
to true
copies the specified source file into the test sandbox.
Note that you can use GenerateAggregatedHeader
sandbox mode only with a single source file.
obj.TestType = "UnitTest"; obj.SandboxSettings.Mode = "GenerateAggregatedHeader"; obj.SandboxSettings.CopySourceFiles = true;
Create the sandbox. This test sandbox is isolated from the original custom code library. Setting Overwrite
to on
overwrites the existing test sandbox, if one exists. By default, Overwrite
is off
.
obj.createSandbox('Overwrite','on');
The createSandbox
method creates the heatpumpController_sandbox
directory in the specified output folder, which in this example is the current working folder.
The sandbox directory contains the following subdirectories:
src: This directory contains the copied source file,
tempController.c
.include
: This directory contains the required include files to compiletempController.c
in the sandboxsrc
directory. This directory also contains theaggregatedHeader.h
file, which contains all the required symbols to compiletempController.c
.autostub
: This directory contains theauto_stub.c
andauto_stub.h
files, which hold the automatically generated stubs forabsoluteTempDifference
andpumpDirection
utility functions.
manualstub
: This directory contains theman_stub.c
andman_stub.h
files, which define any manually specified stubs. By default, these files do not define any functions.
Import the Test Sandbox
Import the sandbox code into Simulink.
obj.import('Functions','heatpumpController');
The import
function creates the sandbox. It also creates a library that contains a C Caller block called heatpumpController
, which contains an internal test harness that you can use to perform unit testing on the heatpumpController
. The library is attached to a Simulink Data Dictionary that defines pump_control_bus
and PumpDirection
as a Simulink.Bus
object and a Simulink enumeration signal, respectively.
The C Caller block is attached with an internal test harness for unit testing the heatpumpController
function.
Input and Output Ports on the C Caller Block
Because you set CustomCode.GlobalVariableInterface
to true
before importing, the import
function creates stubs for the absoluteTempDifference
and pumpDirection
global variables in auto_stub.c
and creates ports for them. For information, see Automatically infer global variables as function interfaces.
This is the C Caller block, heatpumpController
, generated from the heatpumpController
function:
These are the automatically generated global variables in the auto_stub.c
file for absoluteTempDifference
and 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; }
In the automatically generated stubs for the absoluteTempDifference
function, the global variables SLStubIn_absoluteTempDifference_p1
and SLStubIn_absoluteTempDifference_p2
save the input arguments of the function. The function returns the value stored in SLStubOut_absoluteTempDifference
. Similarly, pumpDirection
saves the input arguments and returns SLStubOut_pumpDirection
.
To use the test harness created using automatically created stubs, refer to the next figure. Add buses for the inputs and outputs. To enable connecting a Simulink signal for simulation, connect inputs for Tset
, Troom_in
and the expected outputs from the global variables, SLStubOut_absoluteTempDifference
and SLStubOut_pumpDirection
Likewise, connect outputs as shown in the figure. You can use the inputs and outputs to observe the internal values passed by heatpumpController
to the absoluteTempDifference
and pumpDirecton
subfunction calls.
Change Automatically Created Stubs to Manual Stubs
In cases where, for example, you want to generate the intended output of the stubs as input to the automatically generated ports, you can substitute manual stubs for the stubs automatically generated when the sandbox was created. After you switch to using manual stubs, you update the existing sandbox and import it again.
Manually Modify an Automatically Generated Stub Function
You can manually provide definitions for the automatically generated stub functions by updating the man_stub.c
and man_stub.h
files in the manualstub
directory.
manualstubpath = fullfile([obj.LibraryFileName.char '_sandbox'],'manualstub'); helperFunctionToUpdateManualStubs(manualstubpath);
The helperFunctionToUpdateManualStubs
function updates the manual stub files in the test sandbox.
The updated function definition of absoluteTempDifference
is:
double absoluteTempDifference(double Tset, double Troom_in){ return (double)fabs(Tset - Troom_in); }
The updated function definition of pumpDirection
is:
PumpDirection pumpDirection(double Tset, double Troom_in){ return Tset > Troom_in ? HEATING : COOLING; }
Update the Existing Test Sandbox
To use the manual stub functions, update the sandbox to reflect your changes. Setting the Overwrite
option to off
preserves the changes made to the manualstub
directory in the test sandbox.
obj.createSandbox('Overwrite','off');
After updating the test sandbox, the autostub directory is empty because you defined all of the undefined symbols in the specified custom code.
Import the Updated Test Sandbox
After updating the test sandbox, import the sandbox code into Simulink.
obj.import('Functions','heatpumpController');
The library contains a C Caller block called heatpumpController
with updated ports, and The internal test harness attached to the C Caller block is also updated.
The import function updates the existing library and imports the heatpumpController
function. Like when you used automatically generated stubs, the library is attached to a Simulink Data Dictionary that defines pump_control_bus
and PumpDirection
as a Simulink.Bus
object and a Simulink enumeration signal, respectively.
The C Caller block ports reflect the changes made to the stub files. Because the manual implementation of the absoluteTempDifference
and pumpDirection
functions does not use any global variables, the C Caller block only has ports for the input arguments and return argument of the heatpumpController
function. The internal test harness attached to the C Caller block is also updated.
You can use the created MLDATX test file to test the code. See Conduct Unit Testing on Imported Custom Code by Using the Wizard for a testing example.
See Also
sltest.CodeImporter
| sltest.CodeImporter.SandboxSettings
| createSandbox
| Simulink.CodeImporter.CustomCode
| import
| Simulink.CodeImporter
| Simulink.CodeImporter.ParseInfo
| Simulink.CodeImporter.Options