06 April 2012

TDD benefits example

Hello everyone,
this is my first blog ever about test driven development (TDD).
The goal of this post is to show one of the many benefits of developing code in this direction, which means: unit tests first, implementation later.
Basically you should develop your most basic implementation first (maybe a class and a single method). Then you go and write a simple unit test for that method. At first run unit test will fail. Then you steadily increase the complexity of your implementation to achieve what is needed by your unit test. Finally when you do that, you can again increase the complexity of your test, by adding more test methods to test even more implementation code functionality. As you do that, some of the tests will fail again. Then you should keep refactoring the implementation code to make the tests green and so on.. That is the basic overview of how TDD should work.
The example i am about to show you is a simple piece of code written in Java, and unit testing framework is junit. So here we go.

Few days ago, in my free time i decided to utilize Google Web Toolkit (GWT) to develop a small game. Since i am an occasional fan of playing 9×9 Sudoku game i decided to design this game as a simple web application.
The most difficult challenge i taught would be the design of game logic that is supposed to generate a valid 9×9 Sudoku numbers. You can not of course just generate random numbers. My idea was to generate a full Sudoku matrix which is filled with all the numbers and then based on clients difficulty settings i would hide certain amount of numbers from the board, leaving the client to fill in the rest.. Which is kind of the point for playing the game :)
So I’ve started my development in test driven manner. In no time I’ve wrote a class called SudokuGenerator. This is the class which has the logic to generate a SudokuGrid model.
SudokuGrid represents basically a wrapper for HashMap which stores byte values. These byte values represent Sudoku numbers and are stored in a map in this manner: row and column index are keys in the map and value of the map entry is the actual Sudoku number in a certain cell. Here is an example of Sudoku grid map {6,4=3, 6,5=1, 6,2=5...}.
So row 6 column 4 has value of 3 etc… Now this sudoku generator engine should be used from my Sudoku service in this way:
So after writing Sudoku generator class i have added an empty getSudokuGrid() method to the class.
Then I’ve started with my unit test. I’ve created SudokuGeneratorTest and added one test method to it testGetSudokuGrid(). This method for now contained one simple assertion:
Of course after running this test for the first time it failed… Natural thing in TDD.. Now my goal was to build a valid Sudoku object to make the test green… So I’ve started working on my Sudoku generation algorithm. And I’ve had it done in no time… So i taught: “ok.. I did it. This test is now green”. But this test only proved that I’ve managed to return a non null object.. Nothing more. And then i taught i could add maybe 2 more assertions to check my SudokuGrid model for the validity (correctness) of the values in the grid. Maybe my generation logic has flows. Ok.. So i added one more assertion to the method. We all know that if you play 9×9 Sudoku that means, naturally you have 81 numbers. That means i should have 81 entries in my map. So here is the assertion I’ve added to my test:
I ran the test. It failed.
So here is one of the benefits of developing code in TDD manner. Bugs can be discovered in the development time, pretty early. This is effective. And if you are on real commercial project where money is involved, i think this would definitely save you some time because you would not hunt the bug later and redeploy the application or what ever technique you use to bring application to production. We know on complex multi tiered application these operations are pretty time consuming. They could also very easily be money consuming. So with TDD you definitely save some time and money, to your company and also the customer (if you have one).
Now back to the subject. I had to return to the debugger and pretty soon I’ve found out the mistake. I had just a small error in my logic. One of the loops was looping from 0-9 instead of 0-8 (I’ve went to zero index approach with my Sudoku). So in some special occasions i have been adding more elements to my Sudoku hash map then i am supposed to.
And just look how quickly i identified the reasons for this bug. Just imagine if i deployed this bug. How much time would pass until i notice it. To make matters worse, it would probably be noticed by some of the players (users). Then i would spent a lot of time searching for the reasons why this happens. And then i would have to fix it and redeploy the application. Possibly i would have to inform the users of the application when the redeployment will take place. This would not bring too much good publicity to my game, would it?
Now just imagine you are working on a project where a lot of people is involved, some pretty serious customer which is demanding a high performing application. This customer would probably not want to hear very often that you want from him to redeploy an application. Customer could potentially have 10 000 users just using the application all the time.
After fixing the issue i also made another adjustment to the test to test my code even further.
I’ve added more test methods which checked for the logical validity of my Sudoku model (basic Sudoku rules like no column or row can contain duplicate numbers etc..).
And i have also limited my test methods execution time to 0.2 seconds. I wish that my generation algorithm is reasonably fast. I do not want clients to wait for Sudoku model to build up for too long.
So if my Sudoku generation algorithm is too slow for some reason, the tests will fail.
After adding each of these test methods, test would fail, indicating that i need to improve my code even further to fulfill the requirements i made in the test methods.
So by increasing requirements steadily in the tests and writing implementation to fulfill the requirements later, enables you to create better quality code which inspires more confidence and is more bug-resistent.

No comments:

Post a Comment