When testing you want certainty above all else. Random events destroy confidence in your test suite and force needless extra runs "to be sure". A good test places the subject under test into a tightly controlled environment. A test chamber if you like. This makes the tests fast, repeatable and reliable.
To create a test chamber for testing code, we have to control any outgoing calls from the code under test. We won't believe our test failure if our code is making calls to the internet for example. The internet can fail all by itself. Not only do we not have total control, but it means we have to get dependent components working before we can test the higher level code. This makes it difficult to code top down.
The solution to this dilemma is to write stub code for the components whilst the higher level code is written. This pollutes the code base with temporary code, and the test isolation disappears when the system is eventually fleshed out.
The ideal is to have minimal stubs written for each individual test. Cgreen encourages this approach by making such tests easier to write.
How do we test this code...?
char *read_paragraph(int (*read)(void *), void *stream) { int buffer_size = 0, length = 0; char *buffer = NULL; int ch; while ((ch = (*read)(stream)) != EOF) { if (++length > buffer_size) { buffer_size += 100; buffer = realloc(buffer, buffer_size + 1); } if ((buffer[length] = ch) == '\n') { break; } } return buffer; }This is a fairly generic stream filter that turns the incoming characters into C string paragraphs. Each call creates one paragraph, returning a pointer to it or returning NULL if there is no paragraph. The paragraph has memory allocated to it and the stream is advanced ready for the next call. That's quite a bit of functionality, and there are plenty of nasty boundary conditions. I really want this code tested before I deploy it.
The problem is the stream dependency. We could use a real stream, but that will cause all sorts of headaches. It makes the test of our paragraph formatter dependent on a working stream. It means we have to write the stream first, bottom up coding rather than top down. It means we will have to simulate stream failures - not easy. It will also mean setting up external resources. This is more work, will run slower, and could lead to spurious test failures.
By contrast we could write a simulation of the stream for each test, called a "server stub".
For example, when the stream is empty nothing should happen. We hopefully get NULL from read_paragraph when the stream is exhausted. That is, it just returns a steady stream of EOFs.
static int empty_stream(void *stream) { return EOF; } static void reading_lines_from_empty_stream_gives_null() { assert_equal(read_paragraph(&empty_stream, NULL), NULL); } TestSuite *stream_tests() { TestSuite *suite = create_test_suite(); add_test(suite, reading_lines_from_empty_stream_gives_null); return suite; }Our simulation is easy here, because our fake stream returns only one value. Things are harder when the function result changes from call to call as a real stream would. Simulating this would mean messing around with static variables and counters that are reset for each test. And of course, we will be writing quite a few stubs. Often a different one for each test. That's a lot of clutter.
Cgreen handles this clutter for us by letting us write a single programmable function for all our tests.
We can redo our example by creating a stub_stream() function (any name will do)...
static int stub_stream(void *stream) { return (int)mock(); }Hardly longer that our trivial server stub above, it is just a macro to generate a return value, but we can reuse this in test after test.
For our simple example above we just tell it to always return EOF...
static int stub_stream(void *stream) { return (int)mock(); } static void reading_lines_from_empty_stream_gives_null() { always_return(stub_stream, EOF); assert_equal(read_paragraph(&stub_stream, NULL), NULL); }The always_return() macro takes as arguments the function name and the return value. We have told stub_stream() to always return EOF when called.
Let's see if our production code actually works...
Running "main"... Completed "main": 1 pass, 0 failures, 0 exceptions.So far, so good. On to the next test.
If we want to test a one character line, we have to send the terminating EOF or "\n" as well as the single character. Otherwise our code will loop forever, giving an infinite line of that character.
Here is how we can do this...
static void one_character_stream_gives_one_character_line() { will_return(stub_stream, 'a'); will_return(stub_stream, EOF); char *line = read_paragraph(&stub_stream, NULL); assert_string_equal(line, "a"); free(line); }Unlike the always_return() instruction, will_return() sets just a single return value. It acts like a record and playback model. Successive instructions map out the return sequence that will be given back once the test proper starts.
We'll add this test to the suite and run it...
Running "main"... Failure!: stream_tests -> one_character_stream_gives_one_character_line -> [] should match [a] at [stream_test.c] line [19] Completed "main": 1 pass, 1 failure, 0 exceptions.Oops. Our code under test doesn't work. Already we need a fix...
char *read_paragraph(int (*read)(void *), void *stream) { int buffer_size = 0, length = 0; char *buffer = NULL; int ch; while ((ch = (*read)(stream)) != EOF) { if (++length > buffer_size) { buffer_size += 100; buffer = realloc(buffer, buffer_size + 1); } if ((buffer[length - 1] = ch) == '\n') { break; } } return buffer; }After which everything is fine...
Running "main"... Completed "main": 2 passes, 0 failures, 0 exceptions.
How do the Cgreen stubs work? The will_return() calls build up a static list of return values which are cleared between tests. The mock() macro captures the __func__ property. It uses these to look up entries in the return list, and also to generate more helpful messages.
We can crank out our tests quite quickly now...
static void one_word_stream_gives_one_word_line() { will_return(stub_stream, 't'); will_return(stub_stream, 'h'); will_return(stub_stream, 'e'); always_return(stub_stream, EOF); assert_string_equal(read_paragraph(&stub_stream, NULL), "the"); }I've been a bit naughty. As each test runs in it's own process, I haven't bothered to free the pointers to the paragraphs. I've just let the operating system do it. Purists may want to add the extra clean up code.
I've also used always_return() for the last instruction. Withou this, if the stub is given an instruction is does not expect, it will throw a test failure. This is overly restrictive, as our read_paragraph() function could quite legitimately call the stream after it had run off of the end. OK, that would be odd behaviour, but that's not what we are testing here. If we were, it would be placed in a test of it's own. The always_return() call tells Cgreen to keep going after the first three letters, allowing extra calls.
As we build more tests, they start to look like a specification of the behaviour...
static void drops_line_ending_from_word_and_stops() { will_return(stub_stream, 't'); will_return(stub_stream, 'h'); will_return(stub_stream, 'e'); will_return(stub_stream, '\n'); assert_string_equal(read_paragraph(&stub_stream, NULL), "the"); }...and just for luck...
static void single_line_ending_gives_empty_line() { will_return(stub_stream, '\n'); assert_string_equal(read_paragraph(&stub_stream, NULL), ""); }This time we musn't use always_return(). We want to leave the stream where it is, ready for the next call to read_paragraph(). If we call the stream beyond the line ending, we want to fail.
It turns out that we are failing anyway...
Running "main"... Failure!: stream_tests -> drops_line_ending_from_word_and_stops -> [the ] should match [the] at [stream_test.c] line [36] Failure!: stream_tests -> single_line_ending_gives_empty_line -> [ ] should match [] at [stream_test.c] line [41] Completed "main": 3 passes, 2 failures, 0 exceptions.Clearly we are passing through the line ending. Another fix later...
char *read_paragraph(int (*read)(void *), void *stream) { int buffer_size = 0, length = 0; char *buffer = NULL; int ch; while ((ch = (*read)(stream)) != EOF) { if (++length > buffer_size) { buffer_size += 100; buffer = realloc(buffer, buffer_size + 1); } if ((buffer[length - 1] = ch) == '\n') { buffer[--length] = '\0'; break; } buffer[length] = '\0'; } return buffer; }And we are passing again...
Running "main"... Completed "main": 5 passes, 0 failures, 0 exceptions.
There are no limits to the number of stubbed methods within a test, only that two stubs cannot have the same name. So this will cause problems...
static int stub_stream(void *stream) { return (int)mock(); } static void bad_test() { will_return(stub_stream, 'a'); do_stuff(&stub_stream, &stub_stream); }It will be necessary to have two stubs to make this test behave...
static int first_stream(void *stream) { return (int)mock(); } static int second_stream(void *stream) { return (int)mock(); } static void good_test() { will_return(first_stream, 'a'); will_return(second_stream, 'a'); do_stuff(&first_stream, &second_stream); }We now have a way of writing fast, clear tests with no external dependencies. The information flow is still one way though, from stub to the code under test. When our code calls complex procedures, we won't want to pick apart the effects to infer what happened. That's too much like detective work. And why should we? We just want to know that we dispatched the correct information down the line.
Things get more interesting when we thing of the traffic going the other way, from code to stub. This gets us into the same territory as mock objects.
Setting expectations on mock functions
To swap the traffic flow, we'll look at an outgoing example instead. Here is the prewritten production code...
void by_paragraph(int (*read)(void *), void *in, void (*write)(void *, char *), void *out) { while (1) { char *line = read_paragraph(read, in); if (line == NULL) { return; } (*write)(out, line); free(line); } }This is the start of a formatter utility. Later filters will probably break the paragaphs up into justified text, but right now that is all abstracted behind the void write(void *, char *) interface. Our current interests are: does it loop through the paragraphs, and does it crash?
We could test correct paragraph formation by writing a stub that collects the paragraphs into a struct. We could then pick apart that struct and test each piece with assertions. This approach is extremely clumsy in C. The language is just not suited to building and tearing down complex edifices, never mind navigating them with assertions. We would badly clutter our tests.
Instead we'll test the output as soon as possible, right in the called function...
... void expect_one_letter_paragraph(char *paragraph, void *stream) { assert_string_equal(paragraph, "a", NULL); } void one_character_is_made_into_a_one_letter_paragraph() { by_paragraph( &one_character_stream, NULL, &expect_one_letter_paragraph, NULL); } ...By placing the assertions into the mocked function, we keep the tests minimal. The catch with this method is that we are back to writing individual functions for each test. We have the same problem as we had with hand coded stubs.
Again, Cgreen has a way to automate this. Here is the rewritten test...
static int reader(void *stream) { return (int)mock(stream); } static void writer(void *stream, char *paragraph) { mock(stream, paragraph); } void one_character_is_made_into_a_one_letter_paragraph() { will_return(reader, 'a'); always_return(reader, EOF); expect(writer, want_string(paragraph, "a")); by_paragraph(&reader, NULL, &writer, NULL); }Where are the assertions?
Unlike our earlier stub, reader() can now check it's parameters. In object oriented circles, an object that checks it's parameters as well as simulating behaviour is called a mock object. By analogy reader() is a mock function, or mock callback.
Using the expect macro, we have stated that writer() will be called just once. That call must have the string "a" for the paragraph parameter. If this parameter does not match, the mock function will issue a failure straight to the test suite. This is what saves us writing a lot of assertions.
Here is the full list of instructions that can be sent to the mocks...
Macro | Parameters |
---|---|
will_return(function, result) | Returns result once only, but successive calls will be replayed in order. Generates a failure when called too many times. |
always_return(function, result) | Returns result repeatedly. |
expect(function, arguments...) | Sets up an expectation on each argument. If there is a mismatch, or a call is made without an expectation, a failure is generated. |
always_expect(function, arguments...) | Must receive exactly these arguments from now on. |
expect_never(function) | This function must not be called or a failure is generated. |
will_respond(function, result, arguments...) | Combines will_return() and expect(). |
always_respond(function, result, arguments...) | Combines always_return() and always_expect(). |
The will_respond() macro combines the will_return() and the expect() calls into one call, as does always_respond.
Each parameter can be tested with a constraint. Two constraints are available: want(parameter, expected) for integers and pointers, and want_string(parameter, expected) does a string comparison.
It's about time we actually ran our test...
Running "main"... Completed "main": 6 passes, 0 failures, 0 exceptions.Confident that a single character works, we can further specify the behaviour. Firstly an input sequence...
static void no_line_endings_makes_one_paragraph() { will_return(reader, 'a'); will_return(reader, ' '); will_return(reader, 'b'); will_return(reader, ' '); will_return(reader, 'c'); always_return(reader, EOF); expect(writer, want_string(paragraph, "a b c")); by_paragraph(&reader, NULL, &writer, NULL); }A more intelligent programmer than me would place all these calls in a loop. Next, checking an output sequence...
static void line_endings_generate_separate_paragraphs() { will_return(reader, 'a'); will_return(reader, '\n'); will_return(reader, 'b'); will_return(reader, '\n'); will_return(reader, 'c'); always_return(reader, EOF); expect(writer, want_string(paragraph, "a")); expect(writer, want_string(paragraph, "b")); expect(writer, want_string(paragraph, "c")); by_paragraph(&reader, NULL, &writer, NULL); }Like the will_return() stubs above, the expect() calls follow a record and playback model. Each one tests a successive call. This sequence confirms that we get "a", "b" and "c" in order.
Then we'll make sure the correct stream pointers are passed to the correct functions. This is a more realistic parameter check...
static void resources_are_paired_with_the_functions() { will_respond(reader, 'a', want(stream, 1)); always_respond(reader, EOF, want(stream, 1)); expect(writer, want(stream, 2)); by_paragraph(&reader, (void *)1, &writer, (void *)2); }And finally we'll specify that the writer is not called if there is no paragraph.
static void empty_paragraphs_are_ignored() { will_return(reader, '\n'); always_return(reader, EOF); expect_never(writer); by_paragraph(&reader, NULL, &writer, NULL); }This last test is our undoing...
Running "main"... Failure!: stream_tests -> empty_paragraphs_are_ignored -> Unexpected call to function [writer] at [stream_test.c] line [96] Completed "main": 14 passes, 1 failure, 0 exceptions.Obviously blank lines are still being dispatched to the writer(). Once this is pointed out, the fix is obvious...
void by_paragraph(int (*read)(void *), void *in, void (*write)(void *, char *), void *out) { while (1) { char *line = read_paragraph(read, in); if ((line == NULL) || (strlen(line) == 0)) { return; } (*write)(out, line); free(line); } }Tests with expect_never() can be very effective at uncovering subtle bugs.
Running "main"... Completed "main": 14 passes, 0 failures, 0 exceptions.All done.