VerySillyMUD: Unit Tests
Series [ VerySillyMUD ]
This post is part of my “VerySillyMUD” series, chronicling an attempt to refactor an old MUD into working condition [ 1 ].
In our last episode, we encountered a function we needed to refactor:
Only trouble is: we don’t have unit tests in place, and I don’t really have any idea what this function is supposed to be doing. Looks like we have to get some tests! Now, I am used to the wonderful JUnit family of unit testing frameworks, but it’s unclear which C unit testing framework to use. I decided to opt for the Criterion library, as it was xUnit-styled and seemed pretty straightforward.
The first step is to figure out how to run a basic test. In the short
term, I’ll have to
disable the -Werror
flag
the -Werror flag that treats compiler warnings as errors;
in order to write and run unit tests against the current code, I’m
going to need to that code to compile first! Recall that we’ve cleared
true compilation errors already, so this should work.
Now, the way Criterion unit tests work is that you compile your code
under test along with your unit tests and then link in the Criterion
library into a single executable; when you run that executable it runs
your unit tests. So let’s try to get a basic unit test written against
the dsearch
function:
There’s a few things to note here: first, I explicitly included the
prototype for dsearch
. The project currently puts all the
function prototypes into a single protos.h
file and includes
that everywhere, but I ran into some conflicts trying to do that
here. At some point once I’m in a cleaner project it will be worth
going back through to move each source file’s prototypes out into a
separate .h
file so that they can be included exactly where
appropriate, and so that incremental changes don’t require rebuilding
everything (right now, if there is a change to protos.h
we
have to recompile everything). Second, Criterion’s Test
macro
takes two arguments; the first is the name of a test suite and the
second is the name for the test. I used the name of the C source file
act.wizard.c
as the basis for the suite name and just chose
an initial test name for now. I will probably go back and rename it to
something that reflects the property this test is checking once I
understand dsearch
a little better.
Now, let’s get a make test
target implemented so that it’s
easy for us to run the unit tests. My initial attempt at creating a
test executable tried to just link act.wizard.o
(the code
under test) with test.act.wizard.o
and -lcriterion
,
but it turns out that the act.wizard.c
code refers to
external symbols in other source code files, so linking failed. Rather
than sort out exactly which object files I need, I decided to just
link them all in together into one fat unit test
executable. Unfortunately, -lcriterion
contains a
main()
function in it (so it can run the test suite), so the
rest of the linked object code needs not to have one in it. Right now,
comm.c
has the main
function for the MUD, so first
what
we’ll do
is rename that function and then create a main.c
file
that has a main
for the MUD that calls the one in
comm.c
; then we simply won’t include main.o
in the
test executable.
Next, we can set up some new Makefile
variables for
TEST_SRCS
and TEST_OBJS
and then create two targets:
one to build the test executable tests
and one to actually
run it. Finally, we need to run makedepend
to update all the
dependencies. I note that when I did this I get a lot more detailed
dependencies than were in the Makefile
originally. The way to
do this nowadays would be through automake
and
autoconf
, but I won’t tackle that right now; I’ll just create
an issue on
the Github repo.
Wow, ok.
Running unit tests!
The next step will actually involve diving into the
guts of the dsearch
function to figure out what it currently
does and to document its behavior with tests, which we’ll do in the
next episode.
[ 1 ] SillyMUD was a derivative of DikuMUD, which was originally created by Sebastian Hammer, Michael Seifert, Hans Henrik Stærfeldt, Tom Madsen, and Katja Nyboe.