A Dozen Ways to get the Testing Bug
I was pretty excited about this presentation. I've long toyed with the idea of Test Driven Development and Mike Clark just added fuel to the fire.
You can Grab the Presentation Here
Mike talked a lot about Test Driven Development - which involves writing tests first, before you actually write your code - as really being a design tool. In the recent WITS conference, I talked about Software Design and how to develop good design skills. I'm very tempted to add TDD as something to consider to help improve designs.
Mike freely admits that unit testing is hard to do consistently. It's something that we often try to just dive into and do after the fact. I can't tell you how often I've looked at a completed project and said "I really need to write tests for that", but I don't actually do it. When we put off writing tests until after the code is complete, we can never justify the added time. It's also much more painful to actually sit down and write tens or hundreds of tests at once. It's easier to do it as you go.
Mike also discussed the impact of not having tests. See the presentation slides for some pretty images... ok, not that pretty, but effective in getting the message across. The gist of it is that when we don't have tests, we are reluctant to change our code. Why? because we might break something. With tests in place, you can change code without fear of breaking it. You'll know immediately if you broke something because the test will fail. At that point, it's generally cheap and easy to fix (compared to pushing the code out to test).
Mike made the case that testing helps you develop code faster. The justification for this statement comes from the idea that whether we write tests or not, we still spend a lot of time testing our code. Take a minute and think about the mental checklists that you go through for your app and the manual deploys and tests that you do on every code iteration. If you're honest with yourself, you'll see that you do spend a considerable amount of time doing testing throughout the development process. Why not just write a simple test instead of adding a scenario to your mental check list?
To use Mike's words, Let the computer do the boring stuff and replace Visual code inspection with automated tests. You'll have a 1 time cost that can generally be less than an iteration of visual inspection, and you'll reap the rewards of this investment every time you touch the code!
So, Automated Testing is good and we already have a framework for it - JUnit. Note that there are similar frameworks for other languages such as PLSQLUnit for PL/SQL or JSUnit for JavaScript.
Another thing we need to do to get back some inefficient testing time is to do away with Debugger testing. Now, wait a second, I didn't say do away with Debuggers or with using the Debugger. I said do away with Debugger Testing. Many programmers use their debugger to walk through their code and visually inspect values as a matter of testing their code. "Is value A really incrementing here?" and so on. This includes System.out.println(). So Debuggers are good for Debugging... like for finding out why a given test fails, but it is not a regression testing tool. Now, repeat after me, "My Debugger is NOT a regression testing tool." Time spent manually testing the code is not recyclable or reusable. We need to move toward automated testing.
Again, look at how much time you spend visually inspecting your code or walking through your debugger to test your code. Compare that to the amount of time you would spend writing simple JUnit tests up front and you'll see how much time you can get back.
I'll try to get off the soapbox now. I think you get the idea that we can actually save time by writing automated tests. Now we'll move on to how and, more importantly, when to do it.
It's really quite simple. Write your tests first. Yep, that means before you implement a business rule or feature into the code, you should write the test.
Here's the basic formula:
If you look at this it seems odd. I can't code unless a test fails, but how can I write a test for code that doesn't if the code doesn't exist yet? We'll just have to get over the Chicken-Egg syndrome here and move on :)
When you receive a new requirement, or an enhancement request, the first thing you should do is to write a test. Now, compile your code and run the tests. Guess what? The test will fail since you haven't implemented the use case yet. Now, go and implement the use case, run the test and watch it pass. Now go clean up and refactor the code until it is clean. Run the test and watch it pass. It's simple.
Think of this as part of the design process. Mike spent quite a bit of time talking about how testing first actually helps in design. This may sound crazy at first, but I really believe he's right. The underlying idea is that as you code, you have to think about how you will test the code. This means that your tests are actually the first user of your code. Mike took us through a design / development use case of a shopping cart and showed the complex design that he came up with. The first instinct is to couple the shopping cart code with the Database. The cart saves stuff into a database as stuff is added to the cart. Then he asked "How do I test this?" Of course, his tests will be dependant on the Database and on specific data in the database. This is difficult to maintain over time. Someone will eventually change or archive your test data and your tests will suddenly fail. This also makes the test much more complex. In this scenario, you'd actually be testing your cart functionality, the JDBC layer, possibly the Application Container (for JNDI lookups) and your database. That's a bit much for a test that should only focus on the cart itself.
So, when you look at it from the testing perspective, you realize that you abstract the database stuff away into a separate class, and bury it behind an interface. Once you've done this, you can easily create a mock class to take the place of your database for the test.
In short, by looking at the problem from a testing perspective, you end up with a much more robust design:
There is a great value in being able to test your code independantly of the Database or the App Container. Think about how much time you spend deploying your app to the dev tier... It adds up quickly.
Mike also mentioned the Mock Objects Framework (http://mockobjects.com) which provides mock implementations of crazy complex things such as HttpServlet (over 100 methods that you'd have to implement if you wanted to mock this yourself). This should be a valueable resource for creating mock objects for testing.
Another advantage to building tests is that you corner bugs for life. When you find a bug, you'll write a test around that bug, then you'll add code to make the test pass. Now, you'll never get hit by that bug again! You'll always catch it in your tests.
I know I've rambled on here. I hope it makes some sense. The goal of Mikes presentation was to get folks excited about Test Driven Development and to show some simple ways to get started. Do take a look at the slides for source-code examples on the shopping-cart example above.
One point to remember in all this is that some tests are better than no tests, so don't strive to be perfect, rather do something, write one test and you are better off than you were yesterday.
Now, start writing tests!
You can Grab the Presentation Here
Mike talked a lot about Test Driven Development - which involves writing tests first, before you actually write your code - as really being a design tool. In the recent WITS conference, I talked about Software Design and how to develop good design skills. I'm very tempted to add TDD as something to consider to help improve designs.
Mike freely admits that unit testing is hard to do consistently. It's something that we often try to just dive into and do after the fact. I can't tell you how often I've looked at a completed project and said "I really need to write tests for that", but I don't actually do it. When we put off writing tests until after the code is complete, we can never justify the added time. It's also much more painful to actually sit down and write tens or hundreds of tests at once. It's easier to do it as you go.
Mike also discussed the impact of not having tests. See the presentation slides for some pretty images... ok, not that pretty, but effective in getting the message across. The gist of it is that when we don't have tests, we are reluctant to change our code. Why? because we might break something. With tests in place, you can change code without fear of breaking it. You'll know immediately if you broke something because the test will fail. At that point, it's generally cheap and easy to fix (compared to pushing the code out to test).
Mike made the case that testing helps you develop code faster. The justification for this statement comes from the idea that whether we write tests or not, we still spend a lot of time testing our code. Take a minute and think about the mental checklists that you go through for your app and the manual deploys and tests that you do on every code iteration. If you're honest with yourself, you'll see that you do spend a considerable amount of time doing testing throughout the development process. Why not just write a simple test instead of adding a scenario to your mental check list?
To use Mike's words, Let the computer do the boring stuff and replace Visual code inspection with automated tests. You'll have a 1 time cost that can generally be less than an iteration of visual inspection, and you'll reap the rewards of this investment every time you touch the code!
So, Automated Testing is good and we already have a framework for it - JUnit. Note that there are similar frameworks for other languages such as PLSQLUnit for PL/SQL or JSUnit for JavaScript.
Another thing we need to do to get back some inefficient testing time is to do away with Debugger testing. Now, wait a second, I didn't say do away with Debuggers or with using the Debugger. I said do away with Debugger Testing. Many programmers use their debugger to walk through their code and visually inspect values as a matter of testing their code. "Is value A really incrementing here?" and so on. This includes System.out.println(). So Debuggers are good for Debugging... like for finding out why a given test fails, but it is not a regression testing tool. Now, repeat after me, "My Debugger is NOT a regression testing tool." Time spent manually testing the code is not recyclable or reusable. We need to move toward automated testing.
Again, look at how much time you spend visually inspecting your code or walking through your debugger to test your code. Compare that to the amount of time you would spend writing simple JUnit tests up front and you'll see how much time you can get back.
I'll try to get off the soapbox now. I think you get the idea that we can actually save time by writing automated tests. Now we'll move on to how and, more importantly, when to do it.
It's really quite simple. Write your tests first. Yep, that means before you implement a business rule or feature into the code, you should write the test.
Here's the basic formula:
- Write new code only if an automated test has failed
- Refactor to keep the code clean
- Repeat
If you look at this it seems odd. I can't code unless a test fails, but how can I write a test for code that doesn't if the code doesn't exist yet? We'll just have to get over the Chicken-Egg syndrome here and move on :)
When you receive a new requirement, or an enhancement request, the first thing you should do is to write a test. Now, compile your code and run the tests. Guess what? The test will fail since you haven't implemented the use case yet. Now, go and implement the use case, run the test and watch it pass. Now go clean up and refactor the code until it is clean. Run the test and watch it pass. It's simple.
Think of this as part of the design process. Mike spent quite a bit of time talking about how testing first actually helps in design. This may sound crazy at first, but I really believe he's right. The underlying idea is that as you code, you have to think about how you will test the code. This means that your tests are actually the first user of your code. Mike took us through a design / development use case of a shopping cart and showed the complex design that he came up with. The first instinct is to couple the shopping cart code with the Database. The cart saves stuff into a database as stuff is added to the cart. Then he asked "How do I test this?" Of course, his tests will be dependant on the Database and on specific data in the database. This is difficult to maintain over time. Someone will eventually change or archive your test data and your tests will suddenly fail. This also makes the test much more complex. In this scenario, you'd actually be testing your cart functionality, the JDBC layer, possibly the Application Container (for JNDI lookups) and your database. That's a bit much for a test that should only focus on the cart itself.
So, when you look at it from the testing perspective, you realize that you abstract the database stuff away into a separate class, and bury it behind an interface. Once you've done this, you can easily create a mock class to take the place of your database for the test.
In short, by looking at the problem from a testing perspective, you end up with a much more robust design:
- Your Data Access Layer is now completely separated from your cart
- Your Data Access Layer is abstracted behind an interface so that it can be changed as needed
- You can test your code without having a dependency on the database
There is a great value in being able to test your code independantly of the Database or the App Container. Think about how much time you spend deploying your app to the dev tier... It adds up quickly.
Mike also mentioned the Mock Objects Framework (http://mockobjects.com) which provides mock implementations of crazy complex things such as HttpServlet (over 100 methods that you'd have to implement if you wanted to mock this yourself). This should be a valueable resource for creating mock objects for testing.
Another advantage to building tests is that you corner bugs for life. When you find a bug, you'll write a test around that bug, then you'll add code to make the test pass. Now, you'll never get hit by that bug again! You'll always catch it in your tests.
I know I've rambled on here. I hope it makes some sense. The goal of Mikes presentation was to get folks excited about Test Driven Development and to show some simple ways to get started. Do take a look at the slides for source-code examples on the shopping-cart example above.
One point to remember in all this is that some tests are better than no tests, so don't strive to be perfect, rather do something, write one test and you are better off than you were yesterday.
Now, start writing tests!

1 Comments:
I'm glad to hear some of someone at NI getting excited about TDD. I've felt a lot better about the code I've been writing since I've started writing unit tests for everything. However, for a complex system it takes a lot of tricks to write good tests and to make sure your code is easily testable. If you want to promote writing unit tests, I think the best thing you can do is have training on features of NUnit, testing patterns, Mock objects, and dependency injection models. It can get pretty frustrating to test a highly-connected system unless you learn some new tricks.
Post a Comment
<< Home