By dave | November 10, 2018

Unit testing embedded and Arduino projects

When you’ve got more than the simplest embedded program for Arduino (or any other framework), it becomes much harder to test that it’s working properly by purely running it. For something like Blink, testing is simple because all we need to do is upload it and see the LED turn on and off; there’s little risk of missing anything significant.

However, let’s skip forward to a menu based application with Serial or Ethernet control, there is very little chance that you’d catch all the edge cases by manual testing. In this case unit testing can be very effective, because we know that all the parts work in isolation, even in any known edge cases. Further, any new edge cases that are found can be added to the test suite, preferably before fixing the code so it serves as another test case.

Writing code that can be unit tested is a skill that comes from writing unit tests. Basically, it’s a case of trying to keep each component small, manageable and directly linking with the fewest possible dependencies. One way is to carefully use interfaces between components. Obviously as per my series on Arduino memory usage there is some overhead to doing that, but on the other hand, there’s even more overhead to having all your customers upset because their firmware keeps crashing or malfunctioning. It’s a balance between the two.

As an aside, my prediction is that the days of older chips such as the ATMEGA328 are now limited anyway. Once this generation of chip departs, only the smallest of projects on ATTINY chips will have 2K or less RAM anyway; with programs for these generally of quite low complexity. Most of our projects now use a mixture of Mega, SAMD and ESP development these days.

Why write unit tests

I’m always surprised when I see companies trying to build embedded projects using the waterfall model, by this I mean do all the analysis up front, then most of the development work followed by a long manual QA (quality assurance) cycle. For desktop / mobile software this is risky enough, where the code can be quickly updated through an App Store or auto-update, but for embedded development, where the device could be damaged or bricked by bad code, this surely must be unacceptable.

Often times, at the start of a project people are not sure how to unit test or run an Agile project on Arduino or other embedded platforms, because it doesn’t seem to be well published. Let’s say here that unit testing is very straight-forward and further the page on getting started with unit tests on Arduino covers how to get started with that. In terms of Agile, the sprints build functionality for internal release, with automated tests around them, followed by a feedback loop every few weeks.

Over time, what’s expected from micro-controller firmware has changed, back a few years ago most projects were quite simple, probably just a big loop testing a few sensors and maybe updating a 7-segment display. Fast forward to now and there’s an expectation that almost anything will be a connected appliance, and may even have a display with locally available functions. Even more importantly options to update after release may be limited.

How to design embedded applications that are testable

thecoderscorner consulting pages

First and foremost, I think an Agile iterative process is by far the best, where testing is fully built into the cycle. Embedded code should be stored in a code repository, just like you would mobile and desktop code. Avoid thinking about performance early on, rather focus on making things work first, in a reliable and fully tested way. Then it’s very easy to optimise the pinch points later knowing they are well tested.

In terms of developing the software, I don’t want to get into a long discussion about software design patterns here, but the most important steps for testability of embedded code can be broken down into a few statements.

Each class should have an area of direct concern

Try to ensure that each class sticks to doing one thing, this really helps with testability as the classes remain small and focused as a result. This is a well known OO design concept that most books on the subject discuss in detail.

Any external dependencies should be injected into the class

If your class needs access to another object, ensure that this dependency is injected into your class using either the constructor or an initialise method. Maybe read more about dependency injection as it really simplifies testing.

For example:

class WriterComponent {
private:
    Stream *toWrite; // << instead of using Serial direct, we pass in a stream.
    
public:
    WriterComponent(Stream* stream) { toWrite = stream; }
    
    void writeIt(const char* text) { toWrite->print(text); }
};

Put any hardware functions into their own methods

There most certainly will be interaction with hardware, try to put the hardware manipulations into their own methods. This way the methods can be overridden in a test version of the class, and any business logic in the class tested. You’ll know pretty quick if the hardware function doesn’t work when running normally, it’s easily tested.

For example:

class HardwareShifter {
public:

    void shiftIt(int num) {
        int toWrite = num << 4;
        writeToI2c(toWrite);
    }
    
    void writeToI2c(int val) {
        // do the write here....
    }
};

Then in the unit test..

class TestHardwareShfiter : public HardwareShifter {
public:
    void writeToI2c(int val) {
        // record the write to i2c here...
    }
};

Avoid using singletons

Unfortunately, in the Arduino domain there are many singletons, it was built favouring getting people going quickly, over making code more maintainable. However, all is not lost and there are easy solutions.

I’ve fallen into this trap before, using singletons can make it much harder to test, even if something is provided as a singleton, consider actually still passing the class to your method. This is just common sense anyway, as for example if you use Serial direct, then you could not work with Serial1. Same with Wire, it’s just common sense to inject into the class the version you want to use.

Check that any libraries you use are suitable for unit testing

Check over the libraries you are using, make sure they are easy to unit test, that they follow the above guidelines. If they don’t and you really need to use it, consider wrapping it to make it more testable.

Summary

Unit testing on Arduino or most other embedded platforms is not difficult, and in fact there are libraries that simplify it considerably. Also take a look at the next article in this series about testing with IoAbstraction; which takes Arduino programming to the next level.

Other pages within this category

comments powered by Disqus

This site uses cookies to analyse traffic, and to record consent. We also embed Twitter, Youtube and Disqus content on some pages, these companies have their own privacy policies.

Our privacy policy applies to all pages on our site

Should you need further guidance on how to proceed: External link for information about cookie management.

Send a message
X

Please use the forum for help with UI & libraries.

This message will be securely transmitted to our servers.