Mar 21, 2011

Embedded Unit Testing: In the trenches Part 2

In part 1 we talked about the unique challenge to embedded programming when it comes to proprietary hardware and how we can use dependency injection and interfaces to help us test. In this part  we continue on with the same example, we had just created our StreamBucket class. Turns out it's highly likely to be useful for testing any project that uses serial communication so both IStreamWriter and StreamBucket ended up in my standard library.

Using StreamBucket

The first thing I did after writing the StreamBucket class (did I just hear a bunch of  TDDers  cringe?) was I wrote unit tests for it.  I needed to make sure it initialized and that binary and string data pushed that was pushed onto the queue could be popped back as expected. Also since data written to a serial port is similar to making  a copy I wanted StreamBucket to have a copy of the data written not a reference to the original data. Not exactly difficult, but you don't want to be chasing red herrings that are a result of improperly written classes meant to support unit testing.  The easiest approach was to write a fixture that created the vector<byte> container that I would need in the tests. The actual data didn't really matter so I just picked a few random bytes.
SNAGHTMLec85492
Then I just wrote the tests. Here's just one example test:
SNAGHTMLec8e50e

Testing the TouchScreen

Now it was time to test TouchScreen . Again to save typing I set up a struct that creates my touch screen class and injects my StreamBucket class.
SNAGHTMLec6419f
Here's just one test that uses the fixture.
SNAGHTMLec6fc74
You'll notice that it uses an as yet unmentioned class TouchScreenCommands. I didn't get far in testing before I realized that creating expected byte arrays by hand was duplicative and error prone (not to mention boring). Originally, the TouchScreen class either had private members that created all the required byte vectors or they were created in line in public methods. This made them hard if not impossible to reuse, plus it was giving the TouchScreen class a responsibility it shouldn't have had.  I found it improved the maintainability of the class to move all those parts of the class that created touch screen commands  out to a static TouchScreenCommands class. Not all members of the TouchScreen class are so simple. The above test really isn't much different that the unit test written for TouchScreenCommands that tests the GetForceManualUpdateCmd() method.
Here's the test for TouchScreen::DrawText(ScreenText st)DrawText does several things, it sets the font, sets the font color, sets the position for the text and then writes out the string. The call to DrawText is highlighted, the code before the call sets up the ScreenText object.  The code afterwards shows all the separate tests that verify each command produced by DrawText .SNAGHTMLea78800

Integration Testing

I am still skeptical of many of the claims made for unit testing. When programming solely on the desktop environment, having all your unit tests pass doesn't mean your code works. That  of course is even more obvious in the embedded world. Sometimes the hardware doesn't work as advertised and most times timing can play a big role in how things actually work. I'm not convinced my code is any good at all until I run it against the actual hardware. However, it turns out that a unit testing framework can help you here too. When I was ready to test the actual touch screen, it required nothing more than duplicating the  touch screen tests that used a StreamBucket and creating a fixture that used an actual serial port as shown below:
SNAGHTMLec38fbe

I'm still using the unit testing framework but I'm not really writing unit tests anymore.  The actual device and the tester observing results are both in the loop. Since I don't care about actual test results, I just automatically pass each test. The screenshot below is an example of testing DrawText on the actual device. I no longer need a test for each step as I'll be able to see if the text shows up in the right font, color and location.
SNAGHTMLeb35f99

Running tests like this did teach me something. At the end of the unit test the TouchScreenTester fixture will get torn down. This will close the serial port. Since, I'm using all blocking writes in my commands I didn't anticipate this would be a problem. However, it turns out it does. I needed to insert an OSTimeDly(2) to introduce a minimum 50ms delay in the SerialPort::Close() method to get reliable serial port behavior with the particular device I'm using.
That is one more advantage to doing unit testing. It gives you quick easily digestible way to show a vendor an issue with a product. You can ask them to create the equivalent unit test and either prove or disprove the behavior you see. If they prove it, the new unit test will stay in their test suite and it will NEVER rear its ugly head again in shipping code. If they're not doing unit testing, you have earned the privilege to commit Vermont Royster's eighth deadly sin of self-righteousness and ask them "Why the hell not?"

About Me

My photo
Tod Gentille (@todgentille) is now a Curriculum Director for Pluralsight. He's been programming professionally since well before you were born and was a software consultant for most of his career. He's also a father, husband, drummer, and windsurfer. He wants to be a guitar player but he just hasn't got the chops for it.