Here are some thoughts and observations regarding application design and unit testing in the quiz-project I recently wrote about.
When I was writing the Amazing Programming Language Guessing Script, I didn’t first write any unit tests. Partially because I just wanted to test something quickly, partially because I didn’t think there was any point in doing it.
When I later wanted to refactor the code to a bit better shape, I experienced the need for unit tests first hand: I refactored the code and totally broke it.
The original code
The first iteration of the quiz wasn’t exactly unit-test friendly. It did everything in the controller, which handled the quiz flow – accessing the database, storing things in session, all the logic, everything.
You can see the initial version of the controller here.
As you can see, it does a lot of stuff there and trying to test all that would be pretty difficult.
I wanted to improve it, so I started moving the question-related functionality to a separate class. That way the controller would be doing more of what it was supposed to be doing: Controlling the flow of the application, ie. parsing input, deciding whether to redirect and things like that.
The second version
The second version caught flames and burned down as soon as I first tested it. It wouldn’t get any questions right, and I didn’t really know why. Probably because I had copypasted the code from the controller, which was easy to do wrong, as there was so much going on that wasn’t even related to the actual process I wanted the newly created class to handle.
If you look at the code for the new class, you will probably notice how it’s pretty much similar as the code in the controller is. This class did a lot of things too, and it was probably a bit complicated.
The third version
Since the second version was such a failure, I decided to rewrite the whole guessing/question logic. The problem initially was that I didn’t have a clear idea of what would work, and I had just quickly written what I thought might be a workable solution.
While the initial solution worked, cleaning it up didn’t. Learning from the problems in version 2, I spent some more thought on the design for the rewritten version. This included writing unit tests at the same time as the class itself.
You can see the third version’s class here. You may notice that it’s doing much less, and the task it performs is much more constrained to simply working out the algorithms for determining the questions and answers.
The unit test for the class is perhaps a bit simple and tests just some common cases, but it helped in designing the class’ interface. This is one of the other benefits of unit testing that I’ve noticed: it helps you to design your classes better. When your class has just a single environment where it’s used (f.ex. the controller), you may easily write it in a manner which just makes it simple to use there, and not take into account that it may not actually be such a good idea to do it that way.
When adding another environment (the unit test suite), you actually have two very different ways you need to use the class. One is to perform the actual task it’s designed, and the another is to be able to verify that what it does is actually correct. Being able to get data out from the class often requires a bit different approach in designing the interface, compared to just making a class that can accept some data and then gives you back something else, as is the common case when you just use it in a controller.
The future
Of course, if you look at the v3 controller, it’s still pretty heavy. It does less unrelated things now, but there’s still code related to fetching things from the database, controlling the flow of some algorithms etc.
For most part, the controller should mainly concentrate on handling the “flow” of the application. User opens site, display page A. User goes to link B, show form C. That’s what I’m currently working on – moving the rest of the quiz-related code and database stuff out from the controller into a class of its own, which can be tested and used even for, say, a command-line quiz game!