Write Parameterized Tests for C/C++ Functions Using Polyspace Test xUnit API
You can test a function for several combinations of input values using the Polyspace® Test™ xUnit API. Instead of calling the function under test several times with different combinations of input values, you can define a test data array that enumerates all combinations and call the function under test once with the test data array as argument. The test results show each test with a given input combination, allowing you to identify input combinations that fail the test.
This example shows a simple parameterized test.
Prerequisites
This topic describes test authoring using the Polyspace Test xUnit API. To compile these tests, you are required to know some file paths in advance. For your convenience, you can define environment variables to stand for the file paths, or otherwise include the file paths in your build. For more information, see Set Up C/C++ Testing and Code Profiling Using Self-Managed Builds.
Workflow
You can write a parameterized test following these steps:
Define one or more parameters with the
PST_PARAM_WITH_VALUESmacro. The macro has this format:Where:PST_PARAM_WITH_VALUES (parameterName,parameterType,displayFunction,parameterValues,numValues)is the name of the parameter.parameterNameis the data type of the parameter.parameterTypeYou cannot use array parameters such as
int[2]directly for. Instead, you can define a new type that maps to the array type, for instance:parameterTypeAnd use the new type, in this casetypedef int[3] threeElemIntArr;
threeElemIntArr, for.parameterTypeis a display function that can customize the display of parameter values.displayFunctionIf you use parameters with fundamental types such as
int, enter a display function name of the formpst_format_param_, for example,typenamepst_format_param_int. For aggregate types, define and use your own display function. To align with the display function name for fundamental types, name your custom display function using the formatpst_format_param_.typenameis an array that supplies values for the parameter.parameterValuesis the number of the values that the parameter can take.numValues
Add the parameters in the test configuration with the
PST_ADD_PARAMmacro. For example, for a simple test, the addition of two parameters with namesandexpectedValueParamcan look like this:actualValueParamPST_SIMPLE_TEST_CONFIG(NewTestCase) { PST_ADD_PARAM(expectedValueParam); PST_ADD_PARAM(actualValueParam); }When calling the assessment macros, instead of calling a variable, call a parameter with the dereference of the
PST_PARAM_PTRpointer-like macro.For easier test authoring, before parameterizing a test, first write assessments using regular variables, for example,
andexpectedValuein this example:actualValueOnce the tests are working, parameterize the assessments using test parameters instead of regular variables, for example,PST_VERIFY_EQ_INT(expectedValue, actualValue);
andexpectedValueParamin this example:actualValueParamPST_VERIFY_EQ_INT(*PST_PARAM_PTR(expectedValueParam), *PST_PARAM_PTR(actualValueParam));
If the test parameter
has structured type, to access a field, sayparam, use the syntax:paramFieldPST_PARAM_PTR(param)->paramField
Example
Example Files
Copy code from steps 1 and 2 into .c files.
Alternatively, find the files for this tutorial in the folder . Copy these files to a writable location and continue the tutorial. Here, polyspaceroot\polyspace\examples\doc_pstest\parametrized_tests is the Polyspace installation folder, for example, polyspacerootC:\Program Files\Polyspace\R2026a.
Inspect Function Under Test and Requirements
The function saturate_add_int adds two unsigned integers and saturates their sum at UINT_MAX.
#include <limits.h> /* For UINT_MAX = 4294967295*/
unsigned saturate_add_uint(unsigned x, unsigned y)
{
if (y > UINT_MAX - x) {
return UINT_MAX;
}
return x + y;
}
Save this function in a file example.c.
Suppose you have to test the function for these values of inputs x and y:
x=0, x=(UINT_MAX/2 - 1), x=(UINT_MAX/2), x=(UINT_MAX/2 + 1), x=UINT_MAX y=0, y=(UINT_MAX/2 - 1), y=(UINT_MAX/2), y=(UINT_MAX/2 + 1), y=UINT_MAX
UINT_MAX, the function must return the sum. Otherwise, the function must return UINT_MAX.Suppose that you want to follow an exhaustive testing strategy. That is, for each value of x in the list, you want to run the test with all possible values of y from the list.
Write Test
The following test code enumerates all combinations of inputs along with the expected returns from the saturate_add_int function. For greater readability, the code defines each combination of inputs and the associated return value as members of a structure test_param_t. The test_param_t array then enumerates all such combinations.
#include <pstunit.h>
unsigned saturate_add_uint(unsigned x, unsigned y);
struct test_param_t{
unsigned input1;
unsigned input2;
unsigned expected_result;
};
void pst_format_param_test_param_t(char* buff, const pst_size_t param_size, const void* param);
struct test_param_t test_data[] =
/* input1 input2 expected_result */
{{ 0, 0, 0 },
{ 0, UINT_MAX, UINT_MAX },
{ (UINT_MAX/2 - 1), UINT_MAX/2, (UINT_MAX - 1) },
{ (UINT_MAX/2 - 1), (UINT_MAX/2 + 1), UINT_MAX },
{ (UINT_MAX/2 - 1), UINT_MAX, UINT_MAX },
{ UINT_MAX/2, UINT_MAX, UINT_MAX },
{ (UINT_MAX/2 + 1), (UINT_MAX/2 - 1), UINT_MAX },
{ (UINT_MAX/2 + 1), UINT_MAX/2, UINT_MAX },
{ UINT_MAX, UINT_MAX, UINT_MAX }
};
PST_PARAM_WITH_VALUES(test_param, struct test_param_t, pst_format_param_test_param_t,
test_data, 9);
void pst_format_param_test_param_t(char* buff, const pst_size_t param_size, const void* param) {
pst_format(buff, param_size, "in1:%u, in2:%u, res:%u",
(*PST_STATIC_CAST(struct test_param_t*, param)).input1,
(*PST_STATIC_CAST(struct test_param_t*, param)).input2,
(*PST_STATIC_CAST(struct test_param_t*, param)).expected_result);
}
PST_SIMPLE_TEST_CONFIG(test_saturate_add_uint) {
PST_ADD_PARAM(test_param);
}
PST_SIMPLE_TEST_BODY(test_saturate_add_uint) {
PST_ASSERT_EQ_UINT(saturate_add_uint
(
(PST_PARAM_PTR(test_param))->input1,
(PST_PARAM_PTR(test_param))->input2
),
(PST_PARAM_PTR(test_param))->expected_result
);
}
PST_REGFCN(myRegFcn) {
PST_ADD_SIMPLE_TEST(test_saturate_add_uint);
}
#ifndef PSTEST_BUILD
int main(int argc, char *argv[]) {
PST_REGFCN_CALL(myRegFcn);
return PST_MAIN(argc, argv);
}
#endifSave this code in a file test.c.
The test consists of these macros from the Polyspace Test xUnit API:
PST_PARAM_WITH_VALUES– This macro defines a test parameter,test_param, that has typestruct test_param_t, uses the display functionpst_format_param_test_param_t, and iterates through the first 9 values in the arraytest_data.PST_SIMPLE_TEST_CONFIG– This macro defines the test configuration for the test casetest_saturate_add_uint.PST_ADD_PARAM– This macro adds the previously defined test parametertest_paramto the test configuration fortest_saturate_add_uint.PST_SIMPLE_TEST_BODY– This macro defines the test body fortest_saturate_add_uintand contains the call to the function under test.PST_ASSERT_EQ_UINT– This macro checks if the first argument is equal to the second. The first argument is the return value of the function under test,saturate_add_uint, with inputs from the test data array, and the second argument contains the corresponding expected result from the same array.PST_PARAM_PTR– This macro allows you to iterate through all values of the parametertest_param.
The remaining macros, PST_REGFCN, PST_ADD_SIMPLE_TEST and PST_REGFCN_CALL, register the test.
This test code also defines a specific display function pst_format_param_test_param_t to display the members of the test parameter structure test_param_t. For more information, see Custom Test Result Display Functions for Complex C/C++ Data Types.
Execute Test and Inspect Results
Compile the files example.c and test.c along with files that ship with Polyspace
Test:
gcc example.c test.c <PSTUNIT_SOURCE> -I <PSTUNIT_INCLUDE> -o testrunner
<PSTUNIT_SOURCE> and <PSTUNIT_INCLUDE>, see Set Up C/C++ Testing and Code Profiling Using Self-Managed Builds.Run the test executable:
testrunner.exe
./testrunner in Linux®).Since the test data contains nine values, you see the results of nine tests. The test data contains some failing values and the results end with this table.
| | Total | Passed | Failed | Incomplete |--------|--------|--------|--------|------------ | Suites | 1 | 0 | 1 | 1 | Tests | 9 | 6 | 3 | 3
UINT_MAX/2 - 1 and UINT_MAX/2 fails with this message:| Running | test | test_saturate_add_uint(3) with values ps
(test_param=in1:2147483646, in2:2147483647)
| ASSERT FAIL | |
test.c:45: Assert failed
Expression 'test_condition' evaluated to false
lhs="4294967293" rhs="4294967294"
| FAIL INCOMPLETE | test | test_saturate_add_uint(3) with values
(test_param=in1:2147483646, in2:2147483647)UINT_MAX is odd, the sum of UINT_MAX/2 - 1 and UINT_MAX/2 is UINT_MAX - 2 (4294967293), which is different from the expected result, UINT_MAX - 1 (4294967294).Further Exploration
To test the function with a more exhaustive list of inputs, you can use a bigger array of test data. The example, test_exhaustive.c, provides a bigger data set for the test parameter.