There is real value in not skipping this step; not jumping straight to writing code to try to make it pass.
- One reason is improving the diagnostic. Without care and attention diagnostics are unlikely to diagnose much.
- A second reason is to be sure the test is actually running! Suppose for example, you're using JUnit and you forget its @Test annotation? Or the public specifier?
- A third reason is because sometimes, as we saw last time, you get an unexpected green! Here's another nice example of exactly this which happened to me during a cyber-dojo demo today.
I started by writing my first test, like this:
static void assert_fizz_buzz(const char * expected, int n)
{
char actual[16];
fizz_buzz(actual, sizeof actual, n);
if (strcmp(expected, actual) != 0)
{
printf("fizz_buzz(%d)\n", n);
printf("expected: \"%s\"\n", expected);
printf(" actual: \"%s\"\n", actual);
assert(false);
}
}
static void numbers_divisible_by_three_are_Fizz(void)
{
assert_fizz_buzz("Fizz", 3);
}
I made this fail by writing the initial code as follows (the
(void)n is to momentarily avoid the
"n is unused" warning which my makefile promotes to an error
using the -Werror option):
void fizz_buzz(char * result, size_t size, int n)
{
(void)n;
strncpy(result, "Hello", size);
}
which gave me the diagnostic:
...: assert_fizz_buzz: Assertion `0' failed. fizz_buzz(3) expected: "Fizz" actual: "Hello"
I made this pass with the following slime
void fizz_buzz(char * result, size_t size, int n)
{
if (n == 3)
strncpy(result, "Fizz", size);
}
Next, I returned to the test and added a test for 6:
static void numbers_divisible_by_three_are_Fizz(void)
{
assert_fizz_buzz("Fizz", 3);
assert_fizz_buzz("Fizz", 6);
}
I ran the test, fully expecting it to fail, but it passed!
Can you see the problem?
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
The problem is in
assert_fizz_buzz which starts like this:
static void assert_fizz_buzz(const char * expected, int n)
{
char actual[16];
...
}
Here's what's happening:
assert_fizz_buzz("Fizz", 3)is calledchar actual[16]is definedfizz_buzz(actual, sizeof actual, 3)is calledif (n == 3)istrue"Fizz"isstrncpy'd intoactualfizz_buzz(actual, sizeof actual, 3)returnsstrcmpsays thatexpectedequalsactual- ...
assert_fizz_buzz("Fizz", 6)is calledchar actual[16]is definedactualexactly overlays its previous location so its first 5 bytes are still'F','i','z','z','\0'fizz_buzz(actual, sizeof actual, 6)is calledif (n == 3)isfalsefizz_buzz(actual, sizeof actual, 6)returnsstrcmpsays thatexpectedequalsactual
My mistake was in the test;
actual has automatic
storage duration so does not get initialized.
It's initial value is indeterminate.
The first call to assert_fizz_buzz is accidentally interfering
with the second call.
Tests should be isolated from each other.
I tweaked the test as follows:
static void assert_fizz_buzz(const char * expected, int n)
{
char actual[16] = { '\0' };
...
}
I ran the test again and this time it failed :-)
...: assert_fizz_buzz: Assertion `0' failed. fizz_buzz(6) expected: "Fizz" actual: ""
I made the test pass:
void fizz_buzz(char * result, size_t size, int n)
{
if (n % 3 == 0)
strncpy(result, "Fizz", size);
}
Let's hear it for starting with a test for new functionality and seeing it fail.