Main Content

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:

TemperatureSystemFanPumpPumpConditionStateCommandCommandDirection|Troom_in - Tset| < DeltaT_fanIdle00IDLEDelatT_fan <= |Troom_in - Tset| < DeltaT_pumpFan only10IDLE|Troom_in - Tset| >= DeltaT_pump and Tset < Troom_inCooling11COOLING|Troom_in - Tset| >= DeltaT_pump and Tset > Troom_inHeating11HEATING

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:

Temperature ConditionPump DirectionTset < Troom_inCOOLINGTset > Troom_inHEATING

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 utilities.c file is 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 sandox, 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 compile tempController.c in the sandbox src directory. This directory also contains the aggregatedHeader.h file, which contains all the required symbols to compile tempController.c.

  • autostub: This directory contains the auto_stub.c and auto_stub.h files, which hold the automatically generated stubs for absoluteTempDifference and pumpDirection utility functions.

  • manualstub: This directory contains the man_stub.c and man_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 enumertion 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 Enable 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 enumertion 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

| | | | | | |

Related Topics