diff options
Diffstat (limited to 'junit4/doc/testinfected')
-rw-r--r-- | junit4/doc/testinfected/IMG00001.GIF | bin | 0 -> 6426 bytes | |||
-rw-r--r-- | junit4/doc/testinfected/IMG00002.GIF | bin | 0 -> 6934 bytes | |||
-rw-r--r-- | junit4/doc/testinfected/IMG00003.GIF | bin | 0 -> 5992 bytes | |||
-rw-r--r-- | junit4/doc/testinfected/logo.gif | bin | 0 -> 964 bytes | |||
-rw-r--r-- | junit4/doc/testinfected/testing.htm | 617 |
5 files changed, 617 insertions, 0 deletions
diff --git a/junit4/doc/testinfected/IMG00001.GIF b/junit4/doc/testinfected/IMG00001.GIF Binary files differnew file mode 100644 index 0000000..ca491c1 --- /dev/null +++ b/junit4/doc/testinfected/IMG00001.GIF diff --git a/junit4/doc/testinfected/IMG00002.GIF b/junit4/doc/testinfected/IMG00002.GIF Binary files differnew file mode 100644 index 0000000..2fc614f --- /dev/null +++ b/junit4/doc/testinfected/IMG00002.GIF diff --git a/junit4/doc/testinfected/IMG00003.GIF b/junit4/doc/testinfected/IMG00003.GIF Binary files differnew file mode 100644 index 0000000..2695af3 --- /dev/null +++ b/junit4/doc/testinfected/IMG00003.GIF diff --git a/junit4/doc/testinfected/logo.gif b/junit4/doc/testinfected/logo.gif Binary files differnew file mode 100644 index 0000000..d0e1547 --- /dev/null +++ b/junit4/doc/testinfected/logo.gif diff --git a/junit4/doc/testinfected/testing.htm b/junit4/doc/testinfected/testing.htm new file mode 100644 index 0000000..d11c11c --- /dev/null +++ b/junit4/doc/testinfected/testing.htm @@ -0,0 +1,617 @@ +<!doctype html public "-//w3c//dtd html 4.0 transitional//en"> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <meta name="GENERATOR" content="Mozilla/4.75 [en] (Windows NT 5.0; U) [Netscape]"> + <title>Test Infected: </title> +</head> +<body> + +<h1> +<b><font color="#00CC00">J</font><font color="#FF0000">U</font><font color="#000000">nit</font> <font size=+3>Test +Infected: Programmers Love Writing Tests</font></b></h1> +<br>Note: this article describes JUnit 3.8.x. + +<hr WIDTH="100%"> +<p>Testing is not closely integrated with development. This prevents you +from measuring the progress of development- you can't tell when something +starts working or when something stops working. Using <i>JUnit</i> you +can cheaply and incrementally build a test suite that will help you measure +your progress, spot unintended side effects, and focus your development +efforts. +<h1> +Contents</h1> + +<ul> +<li> +<a href="#TheProblem">The Problem</a></li> + +<li> +<a href="#Example">Example</a></li> + +<li> +<a href="#TestingPractices">Testing Practices</a></li> + +<li> +<a href="#Conclusion">Conclusions</a></li> +</ul> + +<h1> +<a NAME="TheProblem"></a>The Problem</h1> +Every programmer knows they should write tests for their code. Few do. +The universal response to "Why not?" is "I'm in too much of a hurry." This +quickly becomes a vicious cycle- the more pressure you feel, the fewer +tests you write. The fewer tests you write, the less productive you are +and the less stable your code becomes. The less productive and accurate +you are, the more pressure you feel. +<p>Programmers burn out from just such cycles. Breaking out requires an +outside influence. We found the outside influence we needed in a simple +testing framework that lets us do a little testing that makes a big difference. +<p>The best way to convince you of the value of writing your own tests +would be to sit down with you and do a bit of development. Along the way, +we would encounter new bugs, catch them with tests, fix them, have them +come back, fix them again, and so on. You would see the value of the immediate +feedback you get from writing and saving and rerunning your own unit tests. +<p>Unfortunately, this is an article, not an office overlooking charming +old-town Zürich, with the bustle of medieval commerce outside and +the thump of techno from the record store downstairs, so we'll have to +simulate the process of development. We'll write a simple program and its +tests, and show you the results of running the tests. This way you can +get a feel for the process we use and advocate without having to pay for +our presence. +<h1> +<a NAME="Example"></a>Example</h1> +As you read, pay attention to the interplay of the code and the tests. +The style here is to write a few lines of code, then a test that should +run, or even better, to write a test that won't run, then write the code +that will make it run. +<p>The program we write will solve the problem of representing arithmetic +with multiple currencies. Arithmetic between single currencies is trivial, +you can just add the two amounts. Simple numbers suffice. You can ignore +the presence of currencies altogether. +<p>Things get more interesting once multiple currencies are involved. You +cannot just convert one currency into another for doing arithmetic since +there is no single conversion rate- you may need to compare the value of +a portfolio at yesterday's rate and today's rate. +<p>Let's start simple and define a class <a href="#classMoney">Money</a> +to represent a value in a single currency. We represent the amount by a +simple int. To get full accuracy you would probably use double or java.math.BigDecimal +to store arbitrary-precision signed decimal numbers. We represent a currency +as a string holding the ISO three letter abbreviation (USD, CHF, etc.). +In more complex implementations, currency might deserve its own object. +<pre><a NAME="classMoney"></a><tt>class Money { + private int fAmount; + private String fCurrency;</tt></pre> + +<pre><tt> public Money(int amount, String currency) { + fAmount= amount; + fCurrency= currency; + } + + public int amount() { + return fAmount; + } + + public String currency() { + return fCurrency; + } +}</tt></pre> +When<font size=-1> </font>you add two Moneys of the same currency, the +resulting Money has as its amount the sum of the other two amounts. +<pre><a NAME="MoneyAdd"></a><tt>public Money add(Money m) { + return new Money(amount()+m.amount(), currency()); +}</tt></pre> +Now, instead of just coding on, we want to get immediate feedback and practice +"code a little, test a little, code a little, test a little". To implement +our tests we use the JUnit framework. To write tests you need to get the +<a href="http://sourceforge.net/projects/junit/">latest +copy</a> JUnit (or write your own equivalent- it's not so much work). +<p>JUnit defines how to structure your test cases and provides the tools +to run them. You implement a test in a subclass of TestCase. To test our +Money implementation we therefore define <a href="#class MoneyTest">MoneyTest</a> +as a subclass of TestCase. In Java, classes are contained in packages and +we have to decide where to put MoneyTest. Our current practice is to put +MoneyTest in the same package as the classes under test. In this way a +test case has access to the package private methods. We add a test method +testSimpleAdd, that will exercise the simple version of <a href="#MoneyAdd">Money.add()</a> +above. A JUnit test method is an ordinary method without any parameters. +<pre><a NAME="class MoneyTest"></a><tt>public class MoneyTest extends TestCase { + //… + public void testSimpleAdd() { + Money m12CHF= new Money(12, "CHF"); // <a NAME="MoneyTest1"></a>(1) + Money m14CHF= new Money(14, "CHF"); + Money expected= new Money(26, "CHF"); + Money result= m12CHF.add(m14CHF); // <a NAME="MoneyTest2"></a>(2) + Assert.assertTrue(expected.equals(result)); // <a NAME="MoneyTest3"></a>(3) + } +}</tt></pre> +The testSimpleAdd() test case consists of: +<ol> +<li> +<a href="#MoneyTest1">Code</a> which creates the objects we will interact +with during the test. This testing context is commonly referred to as a +test's<i> fixture</i>. All we need for the testSimpleAdd test are some +Money objects.</li> + +<li> +<a href="#MoneyTest2">Code</a> which exercises the objects in the fixture.</li> + +<li> +<a href="#MoneyTest3">Code</a> which verifies the result.</li> +</ol> +Before we can verify the result we have to digress a little since we need +a way to test that two Money objects are equal. The Java idiom to do so +is to override the method <i>equals</i> defined in Object. Before we implement +equals let's a write a test for equals in MoneyTest. +<pre><tt>public void testEquals() { + Money m12CHF= new Money(12, "CHF"); + Money m14CHF= new Money(14, "CHF"); + + Assert.assertTrue(!m12CHF.equals(null)); + Assert.assertEquals(m12CHF, m12CHF); + Assert.assertEquals(m12CHF, new Money(12, "CHF")); // <a NAME="TestEquals1"></a>(1) + Assert.assertTrue(!m12CHF.equals(m14CHF)); +}</tt></pre> +The equals method in Object returns true when both objects are the same. +However, Money is a <i>value object</i>. Two Monies are considered equal +if they have the same currency and value. To test this property we have +added a test <a href="#TestEquals1">(1)</a> to verify that Monies are equal +when they have the same value but are not the same object. +<p>Next let's write the equals method in Money: +<pre><tt>public boolean equals(Object anObject) { + if (anObject instanceof Money) { + Money aMoney= (Money)anObject; + return aMoney.currency().equals(currency()) + && amount() == aMoney.amount(); + } + return false; +}</tt></pre> +Since equals can receive any kind of object as its argument we first have +to check its type before we cast it as a Money. As an aside, it is a recommended +practice to also override the method hashCode whenever you override method +equals. However, we want to get back to our test case. +<p>With an equals method in hand we can verify the outcome of testSimpleAdd. +In JUnit you do so by a calling +<A HREF="../../javadoc/junit/framework/Assert.html#assertTrue(boolean)">Assert.assertTrue</a>, +which triggers a failure that is recorded by JUnit when the argument isn't +true. Since assertions for equality are very common, there is also +an Assert.assertEquals convenience method. In addition to testing for equality +with equals, it reports the printed value of the two objects in the case they +differ. This lets us immediately see why a test failed in a JUnit test +result report. The value a string representation created by +the toString converter method. +There are <a href="../../javadoc/junit/framework/Assert.html"> +other asertXXXX variants</a> not discussed here. +<p>Now that we have implemented two test cases we notice some code duplication +for setting-up the tests. It would be nice to reuse some of this test set-up +code. In other words, we would like to have a common fixture for running +the tests. With JUnit you can do so by storing the fixture's objects in +instance variables of your +<a href="../../javadoc/junit/framework/TestCase.html">TestCase</a> +subclass and initialize them by overridding +the setUp method. The symmetric operation to setUp is tearDown which you +can override to clean up the test fixture at the end of a test. Each test +runs in its own fixture and JUnit calls setUp and tearDown for each test +so that there can be no side effects among test runs. +<pre><tt>public class MoneyTest extends TestCase { + private Money f12CHF; + private Money f14CHF; + + protected void setUp() { + f12CHF= new Money(12, "CHF"); + f14CHF= new Money(14, "CHF"); + } +}</tt></pre> +We can rewrite the two test case methods, removing the common setup code: +<pre><tt>public void testEquals() { + Assert.assertTrue(!f12CHF.equals(null)); + Assert.assertEquals(f12CHF, f12CHF); + Assert.assertEquals(f12CHF, new Money(12, "CHF")); + Assert.assertTrue(!f12CHF.equals(f14CHF)); +} + +public void testSimpleAdd() { + Money expected= new Money(26, "CHF"); + Money result= f12CHF.add(f14CHF); + Assert.assertTrue(expected.equals(result)); +}</tt></pre> +Two additional steps are needed to run the two test cases: +<ol> +<li> +define how to run an individual test case,</li> + +<li> +define how to run a <i>test suite</i>.</li> +</ol> +JUnit supports two ways of running single tests: +<ul> +<li> +static</li> + +<li> +dynamic</li> +</ul> +In the static way you override the runTest method inherited from TestCase +and call the desired test case. A convenient way to do this is with an +anonymous inner class. Note that each test must be given a name, so you +can identify it if it fails. +<pre><tt>TestCase test= new MoneyTest("simple add") { + public void runTest() { + testSimpleAdd(); + } +};</tt></pre> +A template method<a href="#Gamma, E., et al. Design Patterns: Elements of">[1]</a> +in the superclass will make sure runTest is executed when the time comes. +<p>The dynamic way to create a test case to be run uses reflection to implement +runTest. It assumes the name of the test is the name of the test case method +to invoke. It dynamically finds and invokes the test method. To invoke +the testSimpleAdd test we therefore construct a MoneyTest as shown below: +<pre><tt>TestCase test= new MoneyTest("testSimpleAdd");</tt></pre> +The dynamic way is more compact to write but it is less static type safe. +An error in the name of the test case goes unnoticed until you run it and +get a NoSuchMethodException. Since both approaches have advantages, we +decided to leave the choice of which to use up to you. +<p>As the last step to getting both test cases to run together, we have +to define a test suite. In JUnit this requires the definition of a static +method called suite. The suite method is like a main method that is specialized +to run tests. Inside suite you add the tests to be run to a +<a href="../../javadoc/junit/framework/TestSuite.html">TestSuite</a> object +and return it. A TestSuite can run a collection of tests. TestSuite and +TestCase both implement an interface called Test which defines the methods +to run a test. This enables the creation of test suites by composing arbitrary +TestCases and TestSuites. In short TestSuite is a Composite <a href="#Gamma, E., et al. Design Patterns: Elements of">[1].</a> +The code below illustrates the creation of a test suite with the dynamic +way to run a test. +<pre><tt>public static Test suite() { + TestSuite suite= new TestSuite(); + suite.addTest(new MoneyTest("testEquals")); + suite.addTest(new MoneyTest("testSimpleAdd")); + return suite; +}</tt></pre> +Since JUnit 2.0 there is an even simpler dynamic way. You only pass the +class with the tests to a TestSuite and it extracts the test methods automatically. +<p><tt>public static Test suite() {<br> + return new TestSuite(MoneyTest.class);<br> +}</tt><tt></tt> +<p>Here is the corresponding code using the static way. +<pre><tt>public static Test suite() { + TestSuite suite= new TestSuite(); + suite.addTest( + new MoneyTest("money equals") { + protected void runTest() { testEquals(); } + } + ); + + suite.addTest( + new MoneyTest("simple add") { + protected void runTest() { testSimpleAdd(); } + } + ); + return suite; +}</tt></pre> +Now we are ready to run our tests. JUnit comes with a graphical interface +to run tests. Type the name of your test class in the field at the top +of the window. Press the Run button. While the test is run JUnit shows +its progress with a progress bar below the input field. The bar is initially +green but turns into red as soon as there is an unsuccessful test. Failed +tests are shown in a list at the bottom. <a href="#Figure1">Figure 1</a> +shows the TestRunner window after we run our trivial test suite. +<center><img SRC="IMG00001.GIF" > +<br><a NAME="Figure1"></a><b>Figure 1</b>: A Successful Run</center> + +<p>After having verified that the simple currency case works we move on +to multiple currencies. As mentioned above the problem of mixed currency +arithmetic is that there isn't a single exchange rate. To avoid this problem +we introduce a MoneyBag which defers exchange rate conversions. For example +adding 12 Swiss Francs to 14 US Dollars is represented as a bag containing +the two Monies 12 CHF and 14 USD. Adding another 10 Swiss francs gives +a bag with 22 CHF and 14 USD. We can later evaluate a MoneyBag with different +exchange rates. +<p>A MoneyBag is represented as a list of Monies and provides different +constructors to create a MoneyBag. Note, that the constructors are package +private since MoneyBags are created behind the scenes when doing currency +arithmetic. +<pre><tt>class MoneyBag { + private Vector fMonies= new Vector(); + + MoneyBag(Money m1, Money m2) { + appendMoney(m1); + appendMoney(m2); + } + + MoneyBag(Money bag[]) { + for (int i= 0; i < bag.length; i++) + appendMoney(bag[i]); + } +}</tt></pre> +The method appendMoney is an internal helper method that adds a Money to +the list of Moneys and takes care of consolidating Monies with the same +currency. MoneyBag also needs an equals method together with a corresponding +test. We skip the implementation of equals and only show the testBagEquals +method. In a first step we extend the fixture to include two MoneyBags. +<pre><tt>protected void setUp() { + f12CHF= new Money(12, "CHF"); + f14CHF= new Money(14, "CHF"); + f7USD= new Money( 7, "USD"); + f21USD= new Money(21, "USD"); + fMB1= new MoneyBag(f12CHF, f7USD); + fMB2= new MoneyBag(f14CHF, f21USD); +}</tt></pre> +With this fixture the testBagEquals test case becomes: +<pre><tt>public void testBagEquals() { + Assert.assertTrue(!fMB1.equals(null)); + Assert.assertEquals(fMB1, fMB1); + Assert.assertTrue(!fMB1.equals(f12CHF)); + Assert.assertTrue(!f12CHF.equals(fMB1)); + Assert.assertTrue(!fMB1.equals(fMB2)); +}</tt></pre> +Following "code a little, test a little" we run our extended test with +JUnit and verify that we are still doing fine. With MoneyBag in hand, we +can now fix the add method in Money. +<pre><tt>public Money add(Money m) { + if (m.currency().equals(currency()) ) + return new Money(amount()+m.amount(), currency()); + return new MoneyBag(this, m); +}</tt></pre> +As defined above this method will not compile since it expects a Money +and not a MoneyBag as its return value. With the introduction of MoneyBag +there are now two representations for Moneys which we would like to hide +from the client code. To do so we introduce an interface IMoney that both +representations implement. Here is the IMoney interface: +<pre><tt>interface IMoney { + public abstract IMoney add(IMoney aMoney); + //… +}</tt></pre> +To fully hide the different representations from the client we have to +support arithmetic between all combinations of Moneys with MoneyBags. Before +we code on, we therefore define a couple more test cases. The expected +MoneyBag results use the convenience constructor shown above, initializing +a MoneyBag from an array. +<pre><tt>public void testMixedSimpleAdd() { + // [12 CHF] + [7 USD] == {[12 CHF][7 USD]} + Money bag[]= { f12CHF, f7USD }; + MoneyBag expected= new MoneyBag(bag); + Assert.assertEquals(expected, f12CHF.add(f7USD)); +}</tt></pre> +The other tests follow the same pattern: +<menu> +<li> +testBagSimpleAdd - to add a MoneyBag to a simple Money</li> + +<li> +testSimpleBagAdd - to add a simple Money to a MoneyBag</li> + +<li> +testBagBagAdd - to add two MoneyBags</li> +</menu> +Next, we extend our test suite accordingly: +<pre><tt>public static Test suite() { + TestSuite suite= new TestSuite(); + suite.addTest(new MoneyTest("testMoneyEquals")); + suite.addTest(new MoneyTest("testBagEquals")); + suite.addTest(new MoneyTest("testSimpleAdd")); + suite.addTest(new MoneyTest("testMixedSimpleAdd")); + suite.addTest(new MoneyTest("testBagSimpleAdd")); + suite.addTest(new MoneyTest("testSimpleBagAdd")); + suite.addTest(new MoneyTest("testBagBagAdd")); + return suite; +}</tt></pre> +Having defined the test cases we can start to implement them. The implementation +challenge here is dealing with all the different combinations of Money +with MoneyBag. Double dispatch <a href="#Beck, K. Smalltalk Best Practice Patterns,">[2]</a> +is an elegant way to solve this problem. The idea behind double dispatch +is to use an additional call to discover the kind of argument we are dealing +with. We call a method on the argument with the name of the original method +followed by the class name of the receiver. The add method in Money and +MoneyBag becomes: +<pre><tt>class Money implements IMoney { + public IMoney add(IMoney m) { + return m.addMoney(this); + } + //… +}</tt></pre> + +<pre><tt>class MoneyBag implements IMoney { + public IMoney add(IMoney m) { + return m.addMoneyBag(this); + } + //… +}</tt></pre> +In order to get this to compile we need to extend the interface of IMoney +with the two helper methods: +<pre><tt>interface IMoney { +//… + IMoney addMoney(Money aMoney); + IMoney addMoneyBag(MoneyBag aMoneyBag); +}</tt></pre> +To complete the implementation of double dispatch, we have to implement +these methods in Money and MoneyBag. This is the implementation in Money. +<pre><tt>public IMoney addMoney(Money m) { + if (m.currency().equals(currency()) ) + return new Money(amount()+m.amount(), currency()); + return new MoneyBag(this, m); +} + +public IMoney addMoneyBag(MoneyBag s) { + return s.addMoney(this); +}</tt></pre> +Here is the implemenation in MoneyBag which assumes additional constructors +to create a MoneyBag from a Money and a MoneyBag and from two MoneyBags. +<pre><tt>public IMoney addMoney(Money m) { + return new MoneyBag(m, this); +} + +public IMoney addMoneyBag(MoneyBag s) { + return new MoneyBag(s, this); +}</tt></pre> +We run the tests, and they pass. However, while reflecting on the implementation +we discover another interesting case. What happens when as the result of +an addition a MoneyBag turns into a bag with only one Money? For example, +adding -12 CHF to a Moneybag holding 7 USD and 12 CHF results in a bag +with just 7 USD. Obviously, such a bag should be equal with a single Money +of 7 USD. To verify the problem let's implement a test case and run it. +<pre><tt>public void testSimplify() { + // {[12 CHF][7 USD]} + [-12 CHF] == [7 USD] + Money expected= new Money(7, "USD"); + Assert.assertEquals(expected, fMB1.add(new Money(-12, "CHF"))); +}</tt></pre> +When you are developing in this style you will often have a thought and +turn immediately to writing a test, rather than going straight to the code. +<p>It comes to no surprise that our test run ends with a red progress bar +indicating the failure. So we fix the code in MoneyBag to get back to a +green state. +<pre><tt>public IMoney addMoney(Money m) { + return (new MoneyBag(m, this)).simplify(); +} + +public IMoney addMoneyBag(MoneyBag s) { + return (new MoneyBag(s, this)).simplify(); +} + +private IMoney simplify() { + if (fMonies.size() == 1) + return (IMoney)fMonies.firstElement() + return this; +}</tt></pre> +Now we run our tests again and voila we end up with green. +<p>The code above solves only a small portion of the multi-currency arithmetic +problem. We have to represent different exchange rates, print formatting, +and the other arithmetic operations, and do it all with reasonable speed. +However, we hope you can see how you could develop the rest of the objects +one test at a time- a little test, a little code, a little test, a little +code. +<p>In particular, review how in the development above: +<ul> +<li> +We wrote the first test, testSimpleAdd, immediately after we had written +add(). In general, your development will go much smoother if you write +tests a little at a time as you develop. It is at the moment that you are +coding that you are imagining how that code will work. That's the perfect +time to capture your thoughts in a test.</li> + +<li> +We refactored the existing tests, testSimpleAdd and testEqual, as soon +as we introduced the common setUp code. Test code is just like model code +in working best if it is factored well. When you see you have the same +test code in two places, try to find a way to refactor it so it only appears +once.</li> + +<li> +We created a suite method, then extended it when we applied Double Dispatch. +Keeping old tests running is just as important as making new ones run. +The ideal is to always run all of your tests. Sometimes that will be too +slow to do 10 times an hour. Make sure you run all of your tests at least +daily.</li> + +<li> +We created a new test immediately when we thought of the requirement that +a one element MoneyBag should just return its element. It can be difficult +to learn to switch gears like this, but we have found it valuable. When +you are struck by an idea of what your system should do, defer thinking +about the implementation. Instead, first write the test. Then run it (you +never know, it might already work). Then work on the implementation.</li> +</ul> + +<h1> +<a NAME="TestingPractices"></a>Testing Practices</h1> +Martin Fowler makes this easy for you. He says, "Whenever you are tempted +to type something into a print statement or a debugger expression, write +it as a test instead." At first you will find that you have to create a +new fixtures all the time, and testing will seem to slow you down a little. +Soon, however, you will begin reusing your library of fixtures and new +tests will usually be as simple as adding a method to an existing TestCase +subclass. +<p>You can always write more tests. However, you will quickly find that +only a fraction of the tests you can imagine are actually useful. What +you want is to write tests that fail even though you think they should +work, or tests that succeed even though you think they should fail. Another +way to think of it is in cost/benefit terms. You want to write tests that +will pay you back with information. +<p>Here are a couple of the times that you will receive a reasonable return +on your testing investment: +<ul> +<li> +During Development- When you need to add new functionality to the system, +write the tests first. Then, you will be done developing when the test +runs.</li> + +<li> +During Debugging- When someone discovers a defect in your code, first write +a test that will succeed if the code is working. Then debug until the test +succeeds.</li> +</ul> +One word of caution about your tests. Once you get them running, make sure +they stay running. There is a huge difference between having your suite +running and having it broken. Ideally, you would run every test in your +suite every time you change a method. Practically, your suite will soon +grow too large to run all the time. Try to optimize your setup code so +you can run all the tests. Or, at the very least, create special suites +that contain all the tests that might possibly be affected by your current +development. Then, run the suite every time you compile. And make sure +you run every test at least once a day: overnight, during lunch, during +one of those long meetings…. +<h1> +<a NAME="Conclusion"></a>Conclusion</h1> +This article only scratches the surface of testing. However, it focuses +on a style of testing that with a remarkably small investment will make +you a faster, more productive, more predictable, and less stressed developer. +<p>Once you've been test infected, your attitude toward development is +likely to change. Here are some of the changes we have noticed: +<p>There is a huge difference between tests that are all running correctly +and tests that aren't. Part of being test infected is not being able to +go home if your tests aren't 100%. If you run your suite ten or a hundred +times an hour, though, you won't be able to create enough havoc to make +you late for supper. +<p>Sometimes you just won't feel like writing tests, especially at first. +Don't. However, pay attention to how much more trouble you get into, how +much more time you spend debugging, and how much more stress you feel when +you don't have tests. We have been amazed at how much more fun programming +is and how much more aggressive we are willing to be and how much less +stress we feel when we are supported by tests. The difference is dramatic +enough to keep us writing tests even when we don't feel like it. +<p>You will be able to refactor much more aggressively once you have the +tests. You won't understand at first just how much you can do, though. +Try to catch yourself saying, "Oh, I see, I should have designed this thus +and so. I can't change it now. I don't want to break anything." When you +say this, save a copy of your current code and give yourself a couple of +hours to clean up. (This part works best you can get a buddy to look over +your shoulder while you work.) Make your changes, all the while running +your tests. You will be surprised at how much ground you can cover in a +couple of hours if you aren't worrying every second about what you might +be breaking. +<p>For example, we switched from the Vector-based implementation of MoneyBag +to one based on HashTable. We were able to make the switch very quickly +and confidently because we had so many tests to rely on. If the tests all +worked, we were sure we hadn't changed the answers the system produced +at all. +<p>You will want to get the rest of your team writing tests. The best way +we have found to spread the test infection is through direct contact. The +next time someone asks you for help debugging, get them to talk about the +problem in terms of a fixture and expected results. Then say, "I'd like +to write down what you just told me in a form we can use." Have them watch +while you write one little test. Run it. Fix it. Write another. Pretty +soon they will be writing their own. +<p>So- give JUnit a try. If you make it better, please send us the changes +so we can spread them around. Our next article will double click on the +JUnit framework itself. We will show you how it is constructed, and talk +a little about our philosophy of framework development. +<p>We would like to thank Martin Fowler, as good a programmer as any analyst +can ever hope to be, for his helpful comments in spite of being subjected +to early versions of JUnit. +<h1> +References</h1> + +<ol> +<li> +<a NAME="Gamma, E., et al. Design Patterns: Elements of"></a>Gamma, E., +et al. Design Patterns: Elements of Reusable Object-Oriented Software, +Addison-Wesley, Reading, MA, 1995</li> + +<li> +<a NAME="Beck, K. Smalltalk Best Practice Patterns,"></a>Beck, K. Smalltalk +Best Practice Patterns, Prentice Hall, 1996</li> +</ol> + +<hr SIZE=1 WIDTH="100%"> +</body> +</html> |