Unit tests are like Google Maps for your code. When you zoom in and look at small pieces, you can only go so far. However, when you zoom out and take a look from a greater distance, you can see the full path from A to B. The idea of unit testing, as it relates to LUCID development, is that it maps out all the different ways to get from one end of your application to the other. Unit tests take the question of “how does my data get from the my user’s request, through my code and back to the user again?” They answer that question just like any well written application: by breaking it down into smaller more manageable chunks.
Each unit test answers a single question. One test may ask, “what happens if I call findTournament() with a user that has played this game before?” Another test may ask, “what happens if I call findTournament() with a user that has never played a game before?” The more questions you ask about your code, the more you know about how your code is likely to function in production. If you have a question that your unit tests can’t answer, you have a blind spot that may come back to haunt you later. A lack of code coverage, is like a missing road on a map. If you can’t see how data is moving through a section of your code, you can’t be sure how it is going to get from one end to the other.
Unit tests are great because they isolate problems. If a test is failing, you know exactly where to the fix needs to go. However, it is easy to lose the big picture when you are so narrowly focused on individual units of code. The goal of having unit tests is not to ensure that 300 separate pieces of code work well by themselves. The goal is to ensure that those 300 pieces can come together to form one well functioning application. Your tests are the zoomed in pieces of the map that need to be strung together to get you from A to B. That isn’t to say that your tests should do more than one thing. You just need to keep the entire application in mind when creating your unit tests, and more importantly, when creating your test data.
In the development phase, your testing is really a best guess at what you think your code will see. You pass in data that you expect to be passed in in the real world. Your unit tests for your email validation method probably verify the functionality when give a valid email and one or two invalid emails. But, when your application gets out in to the wild world of the Internet, will it be able to handle all of the crazy data that your users throw at it? There isn’t a great way to know early in the game. However, once you deploy your application and you start gathering user information, you can begin to fill in some of those missing gaps.
Unit testing isn’t just a development phase activity. You need to continue to write new tests and update old tests. You need to make sure that your unit tests and your test coverage are evolving in the same way that your users and your data are evolving. The data you are capturing in your logs is a great source for real world test cases. When your logs tip you off that something isn’t working quite like you expected, extract the data and use it to build a new unit test. Because your application has great logging, finding the data that is going into and coming out of any method should be pretty straight forward.
A LUCID application feeds into itself, helping to answer questions about how it works. It uses the data it captures through logging to help solidify behaviors and allows the team to better predict real world behaviors through unit tests. Of course, you can’t test everything and there will always be bugs. That is why it is important for an application to expect failures, and do everything it can to continue working in the face of adversity. My next post will be about building a more fault tolerant and controllable application.