In this post, I will share with you my experience in writing unit tests for C language code using the tool C++Test by Parasoft. These tips are not written anywhere in the documentation provided by Parasoft, and it took me a while to figure these out myself. By sharing this information with you, I hope it will save you a lot of time.
I have no commercial relationship with Parasoft and this post was written by myself based on my own experience and opinion.
Let Parasoft create the unit tests and stubs for you
This does not mean that the tests will be perfect and that there won’t be any more work to do. There will still be a lot more work to do such as:
- verifying the results of each test
- improving the coverage of your tests
- fixing issues with the tests such as memory exceptions
This will still save you a lot of time in creating the unit tests and stubs. The unit tests automatically get created with function input variables automatically defined.
Please note that the input variable won’t necessarily be defined in a way to improve the coverage of the code and you can have some control over the way they are defined in the “Test Configurations” of the “Generate Unit Tests” or “Generate Test Suites” configurations.
Sending Arrays to Functions – A Safer & More Efficient Way For Faster Work With Parasoft C++Test
I also strongly suggest modifying the way you write code in your application so that the unit tests get created automatically in a better way with far fewer exceptions. In addition, this will help make your code cleaner and more readable with greater safety.
Consider a function receiving an array as an input and assigns the value 0 to the first 10 positions:
void FuncArrayTen(uint8_t* ucArr)
{
for(uint8_t ucIndex ; ucIndex < 10u ; ucIndex++ )
ucArr[ucIndex] = 0u;
}
Now, typically as a safety precaution, we would ask to verify to send the size of the array especially if we plan to modify the contents of the array. So we would add the size of the array to the function:
void FuncArrayTen (uint8_t *ucArr, uint8_t ucSize)
{
if( ucSize < 10u )
return;
for(uint8_t ucIndex=0 ; ucIndex < 10u ; ucIndex++ )
ucArr[ucIndex] = 0u;
}
But there are several issues with this popular solution:
- If you let parasoft create a unit test for FuncArrayTen automatically then it will not “know” that the first input needs to be an array. It will create a pointer to a variable and send it to the function. It will not “know” that ucSize needs to reflect the size of the array and can assign it a value greater than 1 but smaller than 10 which would result in an exception due to accessing undefined memory space.
- You can also have a bug in your own code which defines the pointer sent to the array as a pointer to an array of a smaller size than 10 which would result in undefined behavior in your code.
- When you write code calling this function you might forget what you had intended the array size to be when you originally created the function which could lead to confusion and possible error down the line. Sure, you can use proper commenting in the code to document the function and its inputs but this also requires constant maintenance if there are future function changes.
There is a very simple solution that covers all these issues. You can define the input type received by the function as a pointer type variable to an array of a predefined size (in this case 10) using the following syntax: uint8_t (*ucArr)[10]
Don’t confuse the above syntax with this syntax: uint8_t * ucArr[10] which is equivalent to writing uint8_t **ucArr.
Also the following syntax won’t do you any good either: uint8_t ucArr[10] since it is equivalent to writing uint8_t *ucArr. However, it is worth noting that by writing it this way, C++TEST will send not just a pointer to a variable but actually send arrays – but of varying sizes without regards to the value you put in the brackets. So this is still not ideal since it can lead to multiple exceptions.
When you write it the way I recommended above then the compiler (!) verifies this and warns you when the input is wrong. So this is the safe way to send an array to a function:
void FuncArrayTen (uint8_t (*ucArr)[10])
{
for(uint8_t ucIndex=0 ; ucIndex < 10u ; ucIndex++ )
ucArr[ucIndex] = 0u;
}
Uint8_t ucArray1[10];
Uint8_t ucArray2[15];
FuncArrayTen(&ucArray1);
Please notice that if you had called the function this way: FuncArrayTen(ucArray1); or FuncArrayTen(&ucArray2) then you would have gotten a warning from the compiler. Now you are protected from your own mistakes, and the compiler becomes your reviewer!
- In addition, Parasoft C++Test will automatically create unit tests with inputs as arrays of size 10 instead of just a pointer to a single variable. If you end up writing a lot of code then using this safer syntax will save you a tremendous amount of time with Parasoft’s C++Test because you won’t be required to fix so many tests.
- Notice how it is also no longer required to check the size of the array since it is already verified at the compiler level.
If, however, your function logic requires variable sizes of arrays then that’s still covered. You should simply define the input parameter to a maximum size that covers all available options and you can add a size parameter to define how much of the array you sent you actually need to use. For example, let’s say I want to calculate an 8-bit CRC on a stream of bytes in an array and return its value. Let’s also assume that in our application the number of bytes can vary up to no more than 20 bytes. Then I would do the following:
uint8_t CRCfunc(uint8_t (*ucArr)[20], uint8_t ucSize)
{
if(ucSize > 20)
return 0;
for(uint8_t ucIndex=0 ; ucIndex < ucSize; ucIndex++ )
// write logic here
}
The only disadvantage of doing it this way is that the array input of ucSize that is generated automatically by C++TEST will not be synced to the size of the array being sent to ucArr (which is always of size 20 in the example above). But since we have the protection that checks ucSize within the function there will be no exceptions.
Calling Static Functions and Using Static Global Variables Within the File of the Tested Function
- When Parasoft creates a unit test for a function in your code that is located in File.c then it might seem obvious that from within the unit test function you cannot call a static function in that file.
- It would also seem obvious that within the unit test function you cannot use any static global variable (a variable that is not defined within the scope of a function) that was defined in File.c.
However, this assumption is wrong. Technically, it is possible to do both those things by including the file File.c in the unit test suite document that contains the unit test function by typing
#include File.c
(but don’t do this! Please read on).
However, if you ever tried to do such a thing you would come to realize that this typically causes a huge problem of solving dependencies during linking and compilation.
Here is where Parasoft solves the issue. Quite amazingly, it just works. You don’t even need to include the File.c in your unit tests suites file. You can call static functions and use global static variables without any issues within the unit test function as long as the function being tested is within the same file as those static functions or static global variables.
Why does it work? I guess as part of their automation of creating unit tests they did include the File.c AND solved the dependencies (which manually could take hours if not days).
So this is very powerful and it can help a lot.