Did you hear the one about the programmer who wouldn't rewrite some bad code because he was afraid of breaking something else? Since the product couldn't be easily enhanced, innovation was stilted and the company's market value tanked. Our protagonist spent the next two years maintaining that same ugly code. It's not a funny story, but it happens all the time. It doesn't have to. We can rewrite code without fear. We don't have to be the butt of that sad joke. All we need are fully automated unit tests to validate the behavior of our classes and methods.
"Okay, fine," you may be thinking, "but where do these tests come from? They don't exactly fall from the sky." No, they don't. Programmers write tests before they write the production code. Start right now – Write tests for each new method. It's easy and it has done wonders for innumerable projects.
But Wait, There's More!
A.K.A., The Benefits
You can not safely repair or rewrite poor code unless you have tests to prove that things still work. If you do have tests, you can restructure, replace, rewrite until your heart is content. So unit testing enables Refactoring , which keeps working code from degrading as changes are made. When you have unit tests, you have confidence to change any code, even code written by someone else. If the tests pass after making your change, you know you didn't break anything. If the tests worked before, but don't work now, you know you broke it! It's black and white. This enables shared ownership of the code.
Wow! Now we have clean, refactored, tested code and confident programmers. But isn't refactoring and extra testing going to slow us down? NO! Developers actually program faster with fewer defects when using Unit Tests: First, clean, refactored, tested code is easier (read: quicker) to maintain. Second, the tests either pass or fail. The programmers get immediate feedback. They aren't left wondering whether they've tested everything. Third, when tests fail, the code is still fresh in mind – the bugs are easier to find and fix . Fourth, the tests are run every time the code is changed. A programmer is not going to repeat by hand all manual tests ever performed on a class. It's not possible and if it were it would be too time consuming. Happily, automated tests can be run numerous times every day . Finally, you know when you are done because all the tests run!
But wait! That's not all. Automated Unit Testing actually identifies design mistakes . When a design involves too much complexity or coupling, unit tests are difficult to write. By restructuring the design to make it testable we accomplish goals of good design: Low Coupling, and High Cohesion.
All this for one LOW PRICE!
Benefits Outweigh CostsWrite Unit Tests test for everything that could possibly break, using automated tests that must run perfectly all the time. Writing the test code adds to the initial overhead of writing the production code. And, you MUST maintain the tests just as you maintain production code. Thankfully, once written, tests rarely need to change. As explained earlier, automated tests pay for themselves in programmer speed and code quality. With a little practice, you'll be able to write tests quickly.
TESTIFY!Did you actually do this or is this just theory?
Yes, we actually did and do this. Here's a little testimony from three of ISS's internal projects (code names are used).
Stingray and DiabloThe SAFEsuite Events team started writing automated unit tests in the Stingray project (Events Controlled Release). On that effort, we only wrote tests for the Java Applet portion of the system. At first, our team had difficulty. In a short time, we developed habits and techniques to easily incorporate unit testing into development. When shipped, the Applet had over 100 tests. The experience was so positive that our current project (Diablo) uses unit tests throughout the entire system. The Engineers now rely on unit tests . The benefits have been demonstrated. We only want to work with code that has unit tests. Unit tests have enabled the team to go faster, simplify design, measure progress, and share ownership .
MagellanThe Magellan team started writing automated tests in early to mid 2000, after the project was well underway. They, at first, struggled to internalize test writing. But even under extreme deadline pressure, they learned to test and saw benefits. Most importantly, they stuck with it even after the original unit-test evangelist had left the team. Now, they have over 70 tests. They're hooked – They'll never write code without writing tests.
But First, the Requirements…You can't expect these benefits with just any old wad of test code. There are a few qualities the tests must possess:
Easy to run – Programmers are lazy. They need to be able to press a single button to run the tests (all or a subset). Make it easy and developers will run them frequently. Make it hard, and no one will run them at all. Easy and Automated means the tests should not require any user input.
Self-checking – Programmers are lazy. They don't want to (and won't) examine a bunch of output to determine if anything failed. The test should check its own results and simply report "Pass" or "Fail".
Fast – Programmers are impatient. They'll be running many tests several times per hour. The tests must be quick. Tests that take too long will have to be optimized. We often use stubs: sometimes to fake I/O or provide a quicker access method; sometimes to short-circuit certain initializations.
The easiest way to get started is to grab someone who has done this and ask them to show you.
We use an off-the-shelf framework to manage the execution and reporting: JUnit for Java, CppUnit for C++. These excellent free frameworks are available online at xprogramming.com/software.htm. These frameworks make it easy to write and automate the Unit Tests.
Create unit tests when writing new code, fixing bugs, and refactoring . Don't worry about adding tests to existing code. Start out writing tests for new code and for any changes you make in existing code. You'll gradually add tests for the old logic as you make enhancements and fix defects. When making changes, first write tests for the parts of the system the changes will affect. The objective is to test everything that could possibly break. Use judgment; there is no need to go nuts. Use risk to drive which tests to implement. You should concentrate on where the risk is. [Refactoring; Martin Fowler; pp 101] There is a point of diminishing returns with testing – when you think you've done enough, stop!
IF and only IF your team is not writing enough tests, visibly focus on testing by tracking the total number of unit tests each day. We have code you can use that counts the tests and makes a cool plot (Figure 1).
Unit Testing the GUIDon't. Don't test the GUI by firing UI events, scripting user input, scraping screens etc. Instead, use design patterns that separate the visuals from the logic behind the GUI. The rule is "Do No Processing In Your GUI Code." Some patterns that are popular are Model-View-Controller (MVC) [See getting_started2, search java.sun.com for MVC or read Krasner, Pope, A cookbook for using the model-view-controller user interface paradigm in Smalltalk-80, Journal of Object-Oriented Programming , 1(3):26-49, August/September 1988] and Application Façade [appfacades.pdf].
Write Unit Tests that exercise the logic of the system. With MVC, the tests would exercise the Model. For an Application Façade, the tests would exercise the Façade or the classes within the Façade.
Testing the visual portion of the interface is better left for humans. This is one area where we need the expertise that can only be provided by a skilled Quality Assurance team.
Debugging with Unit TestsDefects indicate a missing unit test. Before coding a fix, develop a test that exposes the defect. Once it's fixed, your job is done – all the tests pass so Quit, Enough Done! (You will not have to run lots of tests by hand.) After fixing a defect, it is important to take a moment to think about the possibility of other missing unit tests. Write all those tests and make them pass. You'll quickly get good at this.
Unit Tests and Functional Test
Don't confuse "white-box" Unit Testing with "black-box" Functional Testing. Functional tests verify the entire end-to-end operation of each feature. Typically functional tests relate directly to system requirements (MRD). We still need automated functional tests (customer acceptance tests). And we still need a QA teams for their unique perspective and abilities. Unit Tests are very small tests on individual classes and methods of the system. They don't require the entire system to execute. A test may cover several classes or methods but the smaller and more specific the better. Since unit tests are written by development / for development and since all tests must pass before any changes are checked in, QA will never see a failed unit test.
Further ReadingRefactoring : Improving the Design of Existing Code (Addison-Wesley Object Technology Series); Addison-Wesley Pub Co; by Martin Fowler et al.; Chapter 4.
Chapter 4 gives a great introduction and how-to on Unit Testing. "The programming side of XP is all about being ready for the next requirement; refactoring is how you do it. Martin catalogs over 70 refactorings, the key steps in transforming a program to improve its structure while preserving its function. Refactoring is a core practice in XP, and this is the text." From Extreme Programming Installed
Extreme Programming Installed (The XP Series) ; Addison-Wesley Pub Co; by Ron Jeffries et al.; Ch 13 and 34.
Says the same thing we've said here, but they say it much more eloquently.
Extreme Programming Explained (The XP Series); Addison-Wesley Pub Co; by Kent Beck; Ch 18.
The book that started it all.
C2.com wiki on Unit Tests http://c2.com/cgi/wiki?UnitTests
Online community of Unit Test practitioners.
XP and Unit Tests http://www.xprogramming.com/Practices/PracUnitTest.html and http://www.xprogramming.com/publications/software_testing.htm
The home of XP on the net.
A framework for running Unit Tests.