Main Content

Conduct Unit Testing on Imported Custom Code by Using the Wizard

This example shows how to use the Code Importer wizard 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 wizard generates a test sandbox and a library containing a C Caller block for each specified function from the custom code. For more information see Importing and Testing Custom C/C++ Code.

This example uses only tempController.c file to create and import a test sandbox into Simulink. You use the sandbox for performing unit testing only on the heatpumpController function in the tempController.c file 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.

Heat Pump Controller Custom Code

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 the pump_control_bus type structure with signals that control the fan, heat pump, and the direction of the heat pump (heat or cool). The fields in the pump_control_bus structure are 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 output of the heatpumpController algorithm is summarized in the following table:

Temperature ConditionSystem StateFan CommandPump CommandPump Direction|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.

Open the Code Importer Wizard

To open the Simulink Test C/C++ Code Importer wizard, first open the Simulink Test Manager.

sltest.testmanager.view

Then, select New > Test for C/C++ code.

Specify the Simulink Library and Testing Method

The wizard opens and displays the Welcome tab. Click Start to begin the import process.

On the Settings tab:

  1. Enter the Simulink library file name. The generated Simulink library, test sandbox directory, and test file (MLDATX) use this name. Enter heatpumpController.

  2. Specify the Output folder in which to save the generated artifacts. Enter the name of a writable folder.

  3. Select C Code Unit Testing as the testing method.

The C Code Unit Testing method tests one C file or a subset of your custom code in isolation. This method creates a test sandbox from the specified source file or files, and imports the test sandbox into Simulink. Because the test sandbox only contains a subset of the C files, the Code Importer wizard automatically creates stubs for all of the undefined symbols used in the C files. This method supports testing C code only. For information on Integration Testing, see the TestType property of sltest.CodeImporter.

Click Next.

Specify the Custom Code to Import

On the Specify Custom Code tab:

  1. In Source files, specify the source file that contains the function to import for unit testing. Enter .\src\tempController.c.

  2. In Include directories, specify the directories on which the specified source files depend. Enter .\include.

  3. Defines specifies the compiler-specific defines. For this example, leave this field blank.

Click Next.

Specify the Test Sandbox Settings

On the Analyze tab, specify the sandbox settings.

Select the output test sandbox mode

Each of the three test sandbox modes has settings that determine how the Code Importer wizard generates the sandbox and the sandbox artifacts.

Select Generate aggregated header. This test sandbox mode generates a minimal aggregated header file for the specified source file. The aggregated header file contains all the declarations of the symbols used by the specified source file.

Select the output test sandbox settings

Select only Copy source files. For this option, the Code Importer wizard copies the specified source file to the sandbox src directory.

For information on the other output test sandbox modes and settings, see the properties of sltest.CodeImporter.SandboxSettings.

Click Next to create the test sandbox with the specified settings.

Create the Test Sandbox

The specified library file name determines the name of the generated sandbox. For this example, the sandbox name is heatpumpController_sandbox.

This example uses tempController.c and performs unit testing only on the heatpumpController function. Generating the sandbox automatically creates stubs for the absoluteTempDifference and pumpDirection utility functions used by the heatpumpController function, Since the utility functions are not defined in the tempController.c file, the stubs prevent the code from erroring.

When the sandbox is created, the confirmation screen displays.

The output sandbox folder contains the following subfolders:

  • src: This folder contains the copied source file or files, which for this example is tempController.c.

  • include: Depending on the test sandbox mode, this folder contains either the aggregatedHeader.h or interfaceHeader.h file. This example uses the aggregatedHeader.h file. The folder also contains and copies of the other header files required for the compilation of the specified source file or files. The aggregatedHeader.h file contains all the declarations of the symbols used by the specified source file. The interfaceHeader.h contains the declarations for functions, global variables, and types used by the specified source files. Simulink uses the generated interface header file during the import process.

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

  • manualstub: This folder 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.

NOTE: Do not modify the files in the src, include, or autosub folders of the generated sandbox.

After the sandbox has been created, you can view the created files:

  • Click Auto stub source and Auto stub header to open the auto_stub.c and auto_stub.h files, respectively. In auto_stub.c, you can find the stubs for the utility functions used by heatpumpController, absoluteTempDifference and pumpDirection. In auto_stub.h, you can find the extern declarations of the stubbed functions.

  • Click Manual stub header and Manual stub source to open the man_stub.h and man_stub.c files, respectively. Because you you selected Generate aggregated header for the sandbox mode, sandbox, man_stub.h includes the aggregatedHeader.h file. By default, the manual stub files do not have any function definitions.

Click Next.

Specify Import Settings

Specify Using Global Variables as Function Interfaces

When the wizard detects global variables in the sandbox or custom code, you have the option to use those variables as input or outputs for the function and ports on the created C Caller block. For more information, see Enable global variables as function interfaces.

Select Enable global variables as function interface option.

Click Next.

Select Functions to Import

On the Import page, select the heatpumpController function to import into the Simulink library.

Click Next.

Set the Block Port Specifications

For the function you selected on the previous page, the wizard generates a function port specification. The selected ports are used for the generated C Caller block.

In this example, the port specification table lists the formal arguments, Tset, Troom_in, and out, for the heatpumpController function, and six more from the automatically generated stubs. Because you selected Enable global variables as function interface, the wizard creates ports that are generated from the stubs for the absoluteTempDifference and pumpDirection functions. The stubs are in auto_stub.c.

These are the automatically generated global variables in the auto_stub.c file:

/*************************************************************************/
/* 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 absoluteTempDifference, the global variables SLStubIn_absoluteTempDifference_p1 and SLStubIn_absoluteTempDifference_p2 save the input arguments. The function returns the value stored in SLStubOut_absoluteTempDifference as its output. Similarly, pumpDirection saves the input arguments and returns SLStubOut_pumpDirection.

In this example, absoluteTempDifference, SLStubIn_absoluteTempDifference_p1 and SLStubIn_absoluteTempDifference_p2 are outputs. The global variable SLStubOut_absoluteTempDifference is an input.

Do not make any changes to the port specification. Click Next.

Specify Types to Import

Select the types to import into Simulink. Because the pump_control_bus and PumpDirection types are required by the heatpumpController function, they are selected and dimmed. The wizard creates a Simulink data dictionary containing these types and links the dictionary to the generated library.

Click Next to display a summary of the generated library. Click Next again to continue.

Create a Test Harness

Select Automatically create test harness for all imported functions.

Click Next to generate the Simulink library.

After the code imports, the wizard creates a library 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 created in the Simulink library is:

Click in the lower right corner of the block to open its internal test harness:

On the Code Import Successful page, click Change the MATLAB folder to the output folder.

Do not click Finish. Continue to the following section to manually update the stubs for undefined symbols.

Update Test Sandbox with Manual Stubbing

To manually create the stub files for absoluteTempDifference and pumpDirection, click the Analyze tab in the wizard toolbar and then click Next twice to display this page:

The Code Importer detects the sandbox you created. Select Overwrite and then, click Next.

Outside of the wizard, but leaving the wizard open, copy the files in the updated_manualstub directory of the sandbox and paste them into the heatpumpController/manualstub directory.

In the man_stub.c file, edit the function definition of absoluteTempDifference:

double absoluteTempDifference(double Tset, double Troom_in){ 
    return (double)fabs(Tset - Troom_in);
}

and the function definition of pumpDirection:

PumpDirection pumpDirection(double Tset, double Troom_in){
    return Tset > Troom_in ? HEATING : COOLING;
}

Return to the Code Importer wizard.

After modifying the manual stub files, return to the wizard and click Update Sandbox.

Because you manually defined the definitions for the absoluteTempDifference and pumpDirection functions, there are no symbols to automatically stub, no artifacts are generated for the autostub directory in the sandbox and it is empty.

Click Next until you reach the block port specification page:

Because the manual implementation of absoluteTempDifference and pumpDirection does not have any global variables, only the formal arguments and the return argument appear as ports.

Click Next.

On the Create Simulink Library tab, select Overwrite.

Click Next through the remaining pages until you reach the Code Import Successful page.

Then click Finish. If desired, click Yes in the dialog box that opens to save the current import settings as a JSON file, which you use to reload the settings into another Code Importer wizard session.

Test the Imported Code

In the Test Manager, in the Test Browser pane expand the heatpumpController test file and heatpumpController/heatpumpController test suite. Then select the heatpumpController_Harness1 test case.

Specify SImulation Stop Time

In the System Under Test section, expand the Simulation Setting and Release Overrides section and set the Stop Time to 200.

Add Inputs

In the Inputs section, at the bottom of the External Inputs table, click Add to open the Add Input dialog box.

In the Input File Specification, enter heatpumpControllerHarnessInput.xlsx.

Under Input Mapping, select Block Name as the Mapping Mode and click Map Inputs. After the inputs appear in the Mapping Status table, click OK.

Add Assessments

In the Logical and Temporal Assessments section, add assessments for each temperature condition:

For information on assessments, see Assess Temporal Logic by Using Temporal Assessments.

In the Symbols pane, add the symbols definitions:

Run the Test and View Results

Click Run to run the test.

When the test completes, in the Results and Artifacts pane, expand the Results. All of the assessments pass.

To view the coverage results, select heatpumpController_Harness1 under Results and expand the Coverage Results section. The coverage is 100% for both the tempController.c and man_stub.c files.

Related Topics