Monday, October 19, 2015

VerySillyMUD: Unit Tests

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 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.