All of the guys who are software gurus do some sort of unit testing... and you should too. There are lots of tools out there, from the very slimmest to some very expensive packages. I'm going to show you the quick and dirty method that will cover 80% of your low-level functionality without breaking a sweat. Note that these techniques will ONLY test a certain class of functions and will not test whole-program execution. If your function takes parameters and returns values based SOLELY on those parameters (think math or algorithms), then read on! If your program reads 10 bytes from the SPI port and processes that and returns it back to some other hardware, you should look elsewhere.
For this tutorial, I am not using a test framework! But you should know that there are test frameworks out there that will go through your test code and generate automated test reports. These test frameworks are geared towards x86/x64 architectures, so you won't be able to easily just drop most of them into your project and simulator and hit the road. My favorite test framework thus far is Unity. I will probably look for a good way to integrate this into MPLAB X and Code Composer Studio at some point because running automated tests on your code is AWESOME! You find so many bugs and, when a bug gets fixed, you can integrate the scenario that revealed your bug into the permanent test suite.
We are going to emulate the functionality of the test framework in the simulator. There is nothing that would keep you from doing the same thing with a debugger on the silicon as well.
Fire up your project and create a 'main' file. Include the file that you are testing. This sounds obvious, but it is easy to forget. Be sure that you can compile code before you move on. You should have a main function and all of the standard jazz that allows you to compile before proceeding.
Just above your 'main' function, declare your 'ASSERT' functions. In a normal test framework, these are usually macros rather than functions.
1 2 3 4 5 6 7 8 9 10 11 12 | /* variables */
unsigned int tests_executed = 0;
unsigned int tests_passed = 0;
unsigned int tests_failed = 0;
void TEST_ASSERT_FLOAT_WITHIN(float tolerance, float desiredValue, float testValue);
void TEST_ASSERT_EQUAL_INT16(int16_t n0, int16_t n1);
int main(void) {
return 0;
}
|
Next, write your test function implementations. In my case, I have two types of tests that I'm running. The first is a check for float equality and the second is a check for integer equality. Since checking float equality is always a bad idea, this ASSERT has a tolerance associated with it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | void TEST_ASSERT_FLOAT_WITHIN(float tolerance, float desiredValue, float testValue){
float maxValue = desiredValue + tolerance;
float minValue = desiredValue - tolerance;
int pass = 1;
if(testValue > maxValue){
pass = 0;
}
if(testValue < minValue){
pass = 0;
}
if(pass != 1){
printf("Test Failure\n");
printf("\tExpected Value: %f\n", (double)desiredValue);
printf("\tTest Value: %f\n", (double)testValue);
tests_failed++;
}else{
tests_passed++;
}
tests_executed++;
}
void TEST_ASSERT_EQUAL_INT16(int16_t n0, int16_t n1){
if(n0 != n1){
printf("Test Failure\n");
printf("\tExpected Value: %d\n", n0);
printf("\tTest Value: %d\n", n1);
tests_failed++;
}else{
tests_passed++;
}
tests_executed++;
}
|
I am using MPLAB X, so I am using printf statements to allow me to view the tests that failed directly on the console within the simulator.
This seems obvious, but the mechanics may be a bit mysterious. Simply use your test functions to check the functionality of your test functions.
1 2 3 4 5 6 7 8 | int main(void) {
TEST_ASSERT_FLOAT_WITHIN(0.000031, 0, q15_to_dbl(0));
TEST_ASSERT_EQUAL_INT16(0, q15_to_int(0));
while(1);
return 0;
}
|
I have one instance of each type of test above. I have made this intentionally small for the sake of illustration. You should probably have 4 to 10 tests for each of your functions. Try to think of edge cases and be sadistic! Always test the '0' values along with the max and mins. The while loop is just to 'catch' my debugger. I usually place a breakpoint here so that my tests execute and then notify me when they are complete.
Finally, build and execute. At the completion of each test run, I audit the console output and determine which tests failed, make the appropriate corrections, and re-run the tests.
For a more complete version of this, take a look at the Q1.15 library and its associated test project.