The bugs were little things, they always are. Most of the wasted time was spent simply getting the server into a state where I could poke it just right and make the bug wave a white flag. For example, a bug only shows up when you have 3 players and one does something particular in round 2 and another leaves when he shouldn’t in round 3.
I bemoaned the fact that though I talked a good game I wasn’t test infected and so thought I needn’t bother with testable development for this particular project. Today I spent a few hours rectifying that. Now I have a test harness that lets me write code to drive the game engine and compares the test results with expected results. If I’d had these tests this morning I could have written a test in no time and had those bugs on the ropes before breakfast.
I think I was lucky. Moving from no tests to some tests didn’t take me as long as I expected it to. Several hours is far better than the weeks that it took to get to the same state with the refactoring project. Of course the game’s code is in much better shape to start with; I’m responsible for most of it and I’m a disciplined sort of a chap. However, the code hadn’t been written with testing in mind…
I needed to create three interfaces to replace explicit coupling between the parts of the code that I wanted to test and the parts that I didn’t. The first interface separated the games from the game manager. The game manager is the link between the games and the socket server code. I only wanted to test the game play for now as that’s where the requirements shift the most. Once the games talked to the manager via an interface I could create a mock game manager object in my test code.
The second abstracted away the database. The database was already pretty abstracty but it was a concrete class and not an interface. Slipping in an interface makes it possible to mock up a database layer that just does what my tests want it to do. Strangely I’d toyed with the idea of doing it this from the start but decided that it didn’t buy me anything. I does now.
The third interface was for user output. The socket input comes in via the game manager and is routed to the users by calling functions on the users. My test harness was already driving the users directly, but it needed some way of capturing the output that was being sent to the users. Again, the output was already reasonably abstract, but the user knew about the socket that carried the data back to the client. Not sure why it knew, it didn’t need to. One new interface and a shim class that implements it and knows about the socket and the user no longer cared how where its output went.
So now we have a mock game manager; a mock database; and a mock output system. All of these log their actions via a test log base class - nothing fancy, just a vector of strings. The mock objects plug into the game and user objects, these are the actual classes we want to test. We can now run a game scenario, check that all the actions that we expect to be able to perform on the objects work and then finally check the output of the test logs to make sure that everything happened that should have happened. Coolio.