21

I'm going to be giving a talk to my department next week about unit testing and test-driven development. As part of this, I'm going to show some real-world examples from some code I've written recently, but I'd also like to show some very simple examples that I'll write in the talk.

I've been searching on the web for good examples, but I've been struggling to find any that are particularly applicable to our area of development. Almost all of the software we write is deeply-embedded control systems running on small microcontrollers. There's a lot of C code that is easily applicable to unit testing (I'll be talking about unit testing on the PC rather than on the target itself) as long as you stay clear of the 'bottom' layer: the stuff that talks directly to the microcontroller peripherals. However, most examples I've found tend to be based on string-processing (e.g. the excellent Dive Into Python Roman numerals example) and since we hardly ever use strings this isn't really suitable (about the only library functions our code typically uses are memcpy, memcmp and memset, so something based on strcat or regular expressions isn't quite right).

So, on to the question: please can anyone offer some good examples of functions that I can use to demonstrate unit testing in a live session? A good answer in my (subject to change) opinion would probably be:

  • A function that is simple enough that anyone (even those who only write code occasionally) can understand;
  • A function that doesn't appear pointless (i.e. working out the parity or CRC is probably better than a function that multiplies two numbers together and adds a random constant);
  • A function that is short enough to write in front of a room of people (I may take advantage of Vim's many clipboards to reduce errors...);
  • A function that takes numbers, arrays, pointers or structures as parameters and returns something similar, rather than handling strings;
  • A function that has a simple error (e.g. > rather than >=) that's easy to put in that would still work in most cases but would break with some particular edge case: easy to identify and fix with a unit test.

Any thoughts?

Although it's probably not relevant, the tests themselves will probably be written in C++ using the Google Test Framework: all our headers already have the #ifdef __cplusplus extern "C" { wrapper around them; this has worked well with the tests I've done so far.

gnat
  • 21,442
  • 29
  • 112
  • 288
DrAl
  • 443
  • 1
  • 3
  • 12
  • Taking the "problem" here as coming up with a presentation to sell TDD to management, this seems to me to fit the desired format reasonably well. The OP seems to be requesting existing solutions to this problem. – Technophile Jan 11 '16 at 21:06

2 Answers2

15

Here's a simple function that's supposed to generate a checksum over len bytes.

int checksum(void *p, int len)
{
    int accum = 0;
    unsigned char* pp = (unsigned char*)p;
    int i;
    for (i = 0; i <= len; i++)
    {
        accum += *pp++;
    }
    return accum;
}

It has a fencepost bug: in the for statement, the test should be i < len.

What's fun is, if you apply it to a text string like this...

char *myString = "foo";
int testval = checksum(myString, strlen(myString));

you'll get the "right answer"! That's because the extra byte that was checksummed was the zero string terminator. So you can wind up putting this checksum function into code, and maybe even shipping with it, and never notice a problem - that is, until you start applying it to something other than text strings.

Here's a simple unit test that will flag this bug (most of the time... :-)

void main()
{
    // Seed the random number generator
    srand(time(NULL));

    // Fill an array with junk bytes
    char buf[1024];
    int i;
    for (i = 0; i < 1024; i++)
    {
        buf[i] = (char)rand();
    }

    // Put the numbers 0-9 in the first ten bytes
    for (i = 0; i <= 9; i++)
    {
        buf[i] = i;
    }

    // Now, the unit test. The sum of 0 to 9 should
    // be 45. But if buf[10] isn't 0 - which it won't be,
    // 255/256 of the time - this will fail.
    int testval = checksum(buf, 10);
    if (testval == 45)
    {
        printf("Passed!\n");
    }
    else
    {
        printf("Failed! Expected 45, got %d\n", testval);
    }
}
Bob Murphy
  • 16,028
  • 3
  • 51
  • 77
  • Very good! This is just the kind of answer that I was hoping for: thank you. – DrAl Jun 01 '11 at 07:00
  • When you create the buffer you already have rubbish in that chunk of memory, is it really necessary to initialize it with random numbers? – Snake Sanders Aug 16 '13 at 14:12
  • @SnakeSanders I would say yes, because you want unit tests to be as deterministic as possible. If the compiler you use happens to put a 0 there on your developer machine and a 10 on your test machine, you'll have a terrible time finding the bug. I do think that making it depend on the time instead of a fixed seed is a bad idea, for the same reason. – Andrew Apr 27 '15 at 23:01
  • Relying on non-deterministic behaviors in a unit test is a bad idea. A flaky test will give you headaches sooner or later... – sigy Oct 24 '16 at 11:52
2

What about implementing a sort function like bubble sort? Once you have the sort function working, you can continue with binary search which is just as good for introducing unit testing and TDD.

Sorting and searching depends on comparisons which is easy to get wrong. It also involves swapping pointers around which must be done with care. Both is prone to errors, so feel free to mess up :)

A few more ideas:

  • Unit tests helps a lot when doing refactoring. So once your bubble sort works, you could change it into a more powerful sort like qsort, and the tests should still pass, proving your new sort function works as well.
  • Sorting is easy to test, the result is either sorted, or it is not, which makes it a good candidate.
  • The same for searching; it either exists or it doesn't.
  • Writing tests for sorting opens up discussions like what type of input to use for test (zero elements, random input, duplicate entries, huge arrays etc).
Martin Wickman
  • 13,305
  • 3
  • 31
  • 66