Recently I figured that my DCF77 library requires a suitable set of tests. This was due to two reasons. First I start to get significant contributions for my github repository. Second I read the book Working Effectively with Legacy Code (by Robert C. Martin). So now I need a unit test framework. As it turns out most frameworks have goals that do not align very well with my goals. Typically they require additional instrumentation (e.g. a script language like Python) or they are geared to generating statistics. What I need is just something that can be used to easily test and stop at the first error. If it stops it shall give as much hints for troubleshooting as possible. Also I do not want to get lots of framework provided helper functions (aka matchers).
Since I did not find such a framework I just rolled my own. Obviously this is not really hard to do. See below the my code.
namespace ut { // templates to have a print function with multiple arguments which will be // output in a comma separated fashion template <typename T> void print(T v) { Serial.print(v); } template <typename T, typename... Args> void print(T first, Args... args) { print(first); print(F(", ")); print(args...); } // required for the F macro typedef const __FlashStringHelper* pstr; void handle_error(bool ok) { if (!ok) { while (stop_on_first_error); } }; uint32_t passed = 0; uint32_t failed = 0; void assert_internal(uint32_t line, pstr what, bool ok) { if (ok) { ++passed; if (verbose) { Serial.print(F("OK: ")); Serial.print(line); Serial.print(F(": ")); Serial.println(what); } } else { ++failed; Serial.print(F("failed: ")); Serial.print(line); Serial.print(F(": ")); Serial.println(what); } } void assert(uint32_t line, pstr what, bool ok) { assert_internal(line, what, ok); handle_error(ok); } // templates to implement the actual validation template <typename... Args> void assert(uint32_t line, pstr what, bool ok, Args... args) { assert_internal(line, what, ok); if (!ok) { Serial.print(F(" [")); print(args...); Serial.println(']'); Serial.println(); } handle_error(ok); } // we want to have the line number automatically inserted // notice that macros are visible outside the namespace since // they are processed by the preprocessor #define assert(...) ut::assert (__LINE__, __VA_ARGS__) void print_statistics() { Serial.println(); Serial.print(F("passed: ")); Serial.println(passed); Serial.print(F("failed: ")); Serial.println(failed); Serial.println(); } } void setup() { Serial.begin(115000); // ... some tests ut::print_statistics(); } void loop() {}
The ut namespace delivers the whole framework.
// uint8_t uint8_t i = 1; uint8_t j = 0; minimize(i, j); assert(F("min (1,0) == 0"), i == 0, 0, i); i = 0; j = 1; minimize(i, j); assert(F("min (0,1) == 0"), i == 0, 0, i); i = 1; minimize(i, i); assert(F("min (i,i) == i"), i == 1, 1, i); i = 1; j = 1; minimize(i, j); assert(F("min (1,1) == 1"), i == 1, 1, i);
Want to see it in full action? Have a look at the unit test example in my Github Repository.