Image courtesy of Cycling Cosmonaut
Today I thought I would share with you something I had to do recently at work. To give some background I work for Talent Q who are a provider of online psychometric tests; I am one of the developers who works on all the various systems we have such as the candidate tests, the administration side and all sorts of backend services to keep everything working as it should.
In our last sprint we were working on adding a new suite of tests to our product list. In fact we’ve already got them running now as part of some trials before global release and have already witnessed tens of thousands of completions – they clearly work so that’s always a good start! Me and my colleagues did want to go back over the code though before the final release as developers always have that feeling of wanting to perfect their code; we also had a view of making sure long-term maintainability could be achieved.
So we looked through what we had already and found some areas that we wanted to improve. The main part was that the code was trying to be too clever for its own good:
- Most classes were trying to be very generic but ended up not incorporating the specifics of each individual test, resulting in some code smells such as lots of object casting which was unnecessary.
- We also had several God objects that tried to contain every conceivable detail you could imagine yet at the end of day even missing some vital details.
- Due to all the various classes being very generic it was difficult to follow through the logic of the code. There was clearly a design in there to not have too many strong dependencies interlinked with each other – something that I believe is definitely worthwhile – but you could also get easily lost down a codepath; bear in mind that these tests were built up in an organic manner as most things are so the end result does tend to have more than you actually need (which I will discuss later on).
So I was the one tasked with implementing the changes we wanted. We decided that we would not make all these generic types which somehow had to be tied together, instead each test would have a dedicated class with a clear focus even if it meant some duplication of logic – as long as we have working code we would be able to compare the differences and refactor later if required.
All seems reasonable so far. Oh, did I mention that due to a number of planning issues I had to do all of this in a matter of days?
But I managed it, all within working hours (no overtime) and it all worked first time! Here’s how I did it…
Plan Your Work First
You might think that with a tight deadline in front of you that you should not waste any time and start writing code immediately, right?
You could do this but without a clear focus on what you are trying to achieve you’ll struggle to keep up with what you are doing; you don’t want to be thinking of what you need to do and how to translate that into code at the same time.
So first of all I spent a few hours going over what I needed to do. I looked at each of the new tests we were creating and wrote down notes to help me understand the high-level logic that drove them, such as:
- How do I load the questions?
- How do I keep track of state and where the candidate is in the test?
- What if the candidate quits a session and restarts from the last question they answered? Will we be able to know where to pick up the test from?
- How do I get a final result from the test?
Fortunately out of the three new test types we were creating two of them were similar to previous assessments we had already released so it wasn’t too hard to work out the details for them. By the end of this I had a one-page document of notes for each test which explained to me everything they needed to do. With this now written down and not cluttering my mind I could finally get down to writing the code.
Test Driven Development is Your Friend
Now I knew what to do I had to figure out how I was going to get my code written down and make sure it all worked quickly. The trial code we currently had was not designed to be testable; it was all written under the assumption that there would always be a database and always have a user interface driving it, meaning that testing that code was manual, laborious and the feedback loop was painfully slow.
I’m a big fan of Test Driven Development though; the idea of writing lots of little tests to quickly verify that your code is working is something I’ve jumped onto with gusto. In the end, although I don’t particularly like the idea of rewriting code from scratch, I decided that I was going to have to write the code for these assessments from first principles again and this time focus on making them work in an isolated environment meaning no database or website required.
Image originally from Tumblr
With all this in place it was time for me to start typing, and fast! One of the benefits of Test Driven Development is that because unit tests are so small you can clearly focus on one tiny bit at a time. For example if I wrote the constructor for the assessment then under the scenario of a brand new assessment being run I knew that the outcome of creating the object would mean:
- The internal state would be set to “Running Test” instead of “Completed”
- That the first question number would be one
- That the total number of questions available would be a specific number
- That the first question was loaded into the test
So I simply wrote one test at a time, used the test runner to make sure it passed, and moved to the next one. I managed to keep up this pace and produce quite a number of test cases quickly, all the while improving the code and verifying that it all worked as I expected it to.
Getting “In the Zone”
My blog is called “In the Coding Zone” as it refers to that mythical place where developers love to roam and simply be lost in their own thoughts without any kind of interruption. When “in the zone” developers are able to churn out vast amounts of code and be hugely productive.
Getting there is a challenge in itself, especially when working in an open office environment. Fortunately I got very few distractions during this time so that I really could just ignore everything around me and focus purely on hitting this deadline.
The End Result
After a lot of hard work I hit that deadline but what else did I have to show for it?
- With the code rewritten with testing principles in mind we now had our first ever set of assessments which we could run using test automation. This means we can now verify that these assessments work even without a real database and just using dummy data, or build upon the unit tests to create integration tests using a real bank of questions.
- For the first time our assessments finally make sense; figuring out how our assessments worked used to be a daunting task but I realised this is because it was down to the sheer amount of extraneous code that was written, not due to the complexity of the logic itself. Test Driven Development is really good at focusing you on what you need to achieve to get your tests running; once all tests pass then you don’t need to write any more code so your final implementation tends to be very lean and ultimately simpler to understand.
- One drawback though is that I was mentally tired after this exercise. Keeping up a relentless pace of writing code/tests almost non-stop for days does take it’s toll on you and you can’t keep it up forever.
Developing software seems to have this aurora of always being delayed and not working until you test it over and over again but I wanted to prove that it is possible to have something work first time (and on time!) as long as you plan your work effectively, focus on the job in hand and basically test as you go, not leaving it as an afterthought.