I have inherited a project with xxxx number of lines of C code to be assembled and executed on a PIC microcontroller. As you might expect, once I did this nothing happened and I am left swinging in the wind about what went wrong.
Although I might be able to fix this using the MPLAB ICD debugger, I am convinced that writing unit tests for the code will highlight any bug(s) in the code and is a better long-term solution for a) bugfixes, b) feature updates, c) when developing new products.
The only problem is, I am unsure in what environment these unit tests should be written in.
Does anyone have any thoughts/hints/experience in: a) writing unit tests for C code? b) writing unit test for embedded systems?
Start at the very beginning, and make sure the hardware works, the clock oscillates etc. Write a minimal program, no interrupts, just loops and wiggles a pin that you can see with a scope. Once that is working, you've got your compiler settings right, so work on a bit at a time, checking that the various bitsypieces hung on the IO behave as expected.
Then start with the project code. Put probes in the code if possible, to output a serial mesage you can read, or wiggle a spare IO pin, when it gets to some particular point. For example, put in a tight pin wiggling loop just after the interrupts get enabled- if it keeps going, at least the interrupts aren't crashing the system. Put a wiggle in the time interrupt, and see that it's going, and at the expected rate. Treat the other interrupt routines the same, one at a time.
Congratulations on being interested in writing unit tests. You are a rarity amoung engineers.
There are a couple of fairly simple approaches that you can use, both of which should accomplish what you want.
The idea is to treat code functions, whether high level or assembly language equivalents as units divorced from the finished product. The approach with the lower startup cost it to use additional C code to generate test routines that will call the function under test (FUT). A more sophisticated approach does essentially the same but with the use of a commercial test tool such as Cantata
The most common difficulty in either case is separating the FUT from the other functions in the file (presuming you are not using one file per function). To test your function, you would call it from the test function and ideally have stubs written to replace any functions it calls.
The stubs should be able to report that they were called and be able to return a definable set of good/bad values that are expected by the FUT.
If you use printf or fprintf in your code and stubs, you can produce a test record that identifies what is being tested and that all the stubs were called in the correct sequence, with the correct values, etc.
These calls to the FUT are repeated with a range of the test values to determine how your function will respond. Use both good and bad values that represent a good range of potential inputs. Remember, the concept in testing is to attempt to prove the code does not work. Success by failure, so to say.
The advantage to this type of testing is that it does not necessarily need to be done on the target (PIC in your case). You can do all of this testing on a PC, divorced from the actual hardware. It is common to repeat the testing on the target, but this is not always possible/practical on smaller processors.
Of course this is all based on having some "decent" specifications for what the code is supposed to do.
The actual environment can be kept simple often with little more than the normal development environment. The fundamental difference between embedded system environments and hosted test environments is C compilers for embedded systems generate optimized code that is quite different when small changes are made to the source code. Instrumentation in embedded applications are prone to generate very different code than the application when the instrumented is not included. ( Heisenbugs :) In many cases instrumented application code will not for various reasons run on the real application target.
At Byte Craft (we also make a C compiler for Microchip PIC's), we have worked with a high volume customers to develop technology designed for Unit Testing that runs on the same application code image that will ultimately be shipped. The two goals were a) eliminate the Heisenbugs in the testing process b) automate the process to reduce human error.
We made two changes in the compiler to support the test process. The first change was to create a messaging system so the compiler could communicate directly with the debug environments either a simulator or emulator. This messaging system does quite a few things
1) passes information that is embedded in the source program in the form of reports. It includes debugging setup information, identifies library and function information and links to test data so each of these can be tested in situ using the code and variable assignments compiled into the actual application code.
2) The compiler was modified to emit information about control flow. These are essentially the same kind of nodes that are used by McCabe and others to instrument sources for code coverage tests, the difference is again we are instrumenting the application without changing the application image. The node lists are used by the debug environment to track execution progress and obtain automated results from insitu unit tests.
Thank you everyone for your suggestions. I think in the short term, I will have to go back to basics to find the fault, but for the long term will try to integrate unit testing within the design process.