Assiduously avoid Setup and TearDown!!

Enough people have been ragging on me for not having blogged anything significant for a while that I thought I’d make use of my airplane time to say something that might actually matter (as opposed to whining about various .Net features that I don’t like :)). So here goes…

Setup and Teardown Are Evil

When writing tests in NUnit, JUnit, and probably most of the other xUnits around town, you have the ability to share setup and teardown logic between tests. We were originally taught that any sort of replicated setup and teardown logic should be factored out to these special places, so that it was in our code Once and Only Once.

Well, during my travels through the test first world, I like to think I’ve learned a few things. One of the more interesting things that I think I’ve learned is that tests need to be understandable with the least amount of effort possible if they are going to serve their purpose of documentation. And I personally think that Setup and TearDown inhibit readability.

An Example

Here are some sample tests from some old code I have lying around. This is from a system that implements a very simple point of sale system — a fancy cash register. Theses are specifically tests around the PointOfSale class, which is the driver for the entire system. Take a look at this one test and see what you can infer from it:

        [Test]
        public void CanShowPriceOfPretzels()
        {
            MockDisplay d = new MockDisplay();
            MockProductList p = new MockProductList();
            POS pos = new POS(d, p);

            pos.DoSale("456");

            Assert.AreEqual("Pretzels", d.GetName());
            Assert.AreEqual("1.99", d.GetPrice());
        }

From my point of view, the first and most important thing that I can infer from this is something key about the architecture — the POS class uses a Display instance and a ProductList. This gives me the shape of the system, and helps me understand what the test does.  It sure seems like the POS must take in a bar code, look up what product it maps to using the ProductList, and finally display the information onto the Display. Admittedly, this exsample is a bit trivial, but there is a wealth of information to be gained by looking at this test.

Let’s look at another test in the same fixture:

       [Test]
       public void NothingDisplayedAtStartup()
       {
           MockDisplay d = new MockDisplay();
           MockProductList p = new MockProductList();
           POS pos = new POS(d, p);

           Assert.AreEqual("", d.GetName());
           Assert.AreEqual("0.00", d.GetPrice());
       }

As you can see, the first three lines are absolutely identical. Let’s go ahead and refactor these two tests to share their common setup logic and see how that affects the tests:

   [TestFixture]
    public class POSFixture 
    {
        private MockDisplay display;
        private MockProductList productList;
        private POS pos;

        [SetUp]
        public void CreateObjects()
        {
            display = new MockDisplay();
            productList = new MockProductList();
            pos = new POS(display, productList);
        }

        [Test]
        public void NothingDisplayedAtStartup()
        {
            Assert.AreEqual("", display.GetName());
            Assert.AreEqual("0.00", display.GetPrice());
        }

        [Test]
        public void CanShowPriceOfBeer()
        {
            pos.DoSale("123");

            Assert.AreEqual("Beer", display.GetName());
            Assert.AreEqual("2.99", display.GetPrice());
        }

        [Test]
        public void CanShowPriceOfPretzels()
        {
            pos.DoSale("456");

            Assert.AreEqual("Pretzels", display.GetName());
            Assert.AreEqual("1.99", display.GetPrice());
        }
    }

Here we see the entire test fixture in all its glory. And if we look at each of the tests, it is very easy to see exactly what that test does to the system and its intended effect. But in my mind you completely and totally lose the context of the system. To understand what the test is poking and prodding at, you have to tear your eyes away from the test and go find the setup method. I think this makes each individual test harder to understand. It also would make an automated documentation tool harder to write as well, since it would have to somehow bring along enough context to explain the test.

What about Once and Only Once?

I fully understand,appreciate, and follow the Once and Only Once (OAOO) mantra of the agile movement. But this mantra has to be taken in its context — it is a part of the definition of SimpleDesign. SimpleDesign means to write the system such that these things are true:

  1. Code is appropriate for its audience
  2. Communicates its intent clearly
  3. Each concept is represented once and only once
  4. Expressed in as few classes as possible
  5. Expressed in as few methods as possible

in that order! If you look carefully at that list, communication is #2 on the list, while OAOO is #3. This means the communication trumps a little duplication. Part of becoming more experienced at TDD, Refactoring, and Simple Design is knowing when it is appropriate to leave in a little duplication to enhance communication. And I think this is one of those times.

There is definitely a price to pay for it, however. Since the setup and teardown logic are potentially duplicated among a bunch of similar unit tests, you have to change each of them individually should the logic change. I’ve paid that price a bunch of times in the past, but I still believe that the gains in communication outweigh the cost of the replicated changes. YMMV :)

When should Setup and TearDown be used?

There are still some places that are very appropriate for using Setup and TearDown. In my mind, these are used for resource management. If I had a dime for every time I see someone put some cleanup code after the asserts in a test, I’d be rich beyond my wildest dreams :) What they apparently don’t understand is that if an assert fires, you exit the test right then and there, and your fancy cleanup logic never happens. Those with an appreciation for this fact have been known to encapsulate their tests with try/finally blocks, just to ensure that their cleanup happens.

It is this logic that I think deserves to be factored out. The cleanup logic is not part of the context for understanding the test, it is part of the plumbing that makes the test run. I fully and completely believe that this kind of logic needs to be factored out of each test and put someplace else. In fact, having it in the test confuses the issues and obscures the actual purpose of the test. By moving it into Setup and TearDown, you leave just that code in the test methods that really do establish the context of the test.

Examples of this kind of logic would include:

  • Resource management for opening and closing files, sockets, or other resources
  • Resetting housekeeping variables defined, managed, and manipulated in the tests themselves
  • Stuff you darn well want to make sure happens before and especially after each test.

I followed this pattern when I was writing tests for the Caching block in the Enterprise Library. One of the features of the Caching block was that it could store data into either a database or isolated storage. This storage is persistent, which meant that I had to make very, very sure that these resources were reset to known states before I could reliably run each test. So what I did was to clear out this persistent state before each test ran in the Setup method. I also made sure to clear out this persistent state after the last test was finished, so that the next fixture that ran could be assured that the state was clear. This last little hint was a hard-learned lesson where tests began to fail based on the order in which they ran, causing me to spend a few hours trying to track down why it was happening. It was at that point that I started making a point of cleaning up at the end as well :)

— bab

 

 

This entry was posted in 111. Bookmark the permalink.

14 Responses to Assiduously avoid Setup and TearDown!!

  1. Miki Watts says:

    You’ve got a point about the cleanup code, that should definitely be in the the Setup and Teardown, but only because those parts are guaranteed to run.

    However, I don’t agree with repeating the setup at each and every test. If you have code that runs at every test, just refactor it to it’s own function or factory class, just like you’d do in the actual code.

  2. Brian Button says:

    Hi, Miki,

    Thanks for reading. The main point that I was trying to get to is that tests needs to be understandable on their own. If you can find a way to factor out something in them and not affect the readability, then by all means do it. I find that the unseen setup/teardown stuff hides interesting things from me that well-named methods do not. But I still prefer my tests to communicate on their own, so I usually don’t do this. But at times, it do…

    – bab

  3. Roy Osherove says:

    I agree with the problem, but don’t full agree with the solution. here are my thoughts:

    http://weblogs.asp.net/rosherove/archive/2005/07/29/420952.aspx

  4. whales says:

    LoneStar is now somewhat in a visible shape.

  5. 6699 says:

    http://blogs.ya.com/xhxadybar/files/bbsman.htm

    http://blogs.ya.com/xhxadybar/files/crbbs.htm

    http://blogs.ya.com/xhxadybar/files/freedy.htm

    http://blogs.ya.com/xhxadybar/files/freesj.htm

    http://blogs.ya.com/xhxadybar/files/jwtuan.htm

    http://blogs.ya.com/xhxadybar/files/lovdy.htm

    http://blogs.ya.com/xhxadybar/files/lovedy.htm

    http://blogs.ya.com/xhxadybar/files/luxiang.htm

    http://blogs.ya.com/xhxadybar/files/manxs.htm

    http://blogs.ya.com/xhxadybar/files/mmyh.htm

    http://blogs.ya.com/xhxadybar/files/mnzp.htm

    http://blogs.ya.com/xhxadybar/files/rtystj.htm

    http://blogs.ya.com/xhxadybar/files/sanjipian.htm

    http://blogs.ya.com/xhxadybar/files/smalldy.htm

    http://blogs.ya.com/xhxadybar/files/sqxs.htm

    http://blogs.ya.com/xhxadybar/files/yesdy.htm

    http://blogs.ya.com/xhxadybar/files/zhaody.htm

    http://kan106.googlepages.com/200601.htm

    http://kan106.googlepages.com/200602.htm

    http://kan106.googlepages.com/200603.htm

    http://kan106.googlepages.com/200604.htm

    http://kan106.googlepages.com/200605.htm

    http://kan106.googlepages.com/200606.htm

    http://kan106.googlepages.com/200607.htm

    http://kan106.googlepages.com/200608.htm

    http://kan106.googlepages.com/200609.htm

    http://kan106.googlepages.com/200610.htm

    http://kan106.googlepages.com/200611.htm

    http://kan106.googlepages.com/200612.htm

    http://kan106.googlepages.com/200613.htm

    http://kan106.googlepages.com/200614.htm

    http://kan106.googlepages.com/200615.htm

    http://kan106.googlepages.com/200616.htm

    http://kan106.googlepages.com/200617.htm

    http://kan106.googlepages.com/200618.htm

    http://kan106.googlepages.com/200619.htm

    http://kan106.googlepages.com/200620.htm

    http://kan106.googlepages.com/200621.htm

    http://kan106.googlepages.com/200622.htm

    http://kan106.googlepages.com/200623.htm

    http://kan106.googlepages.com/200624.htm

    http://kan106.googlepages.com/200625.htm

    http://kan106.googlepages.com/200626.htm

  6. 6699 says:
  7. serthfd says:

    http://my.opera.com/007smsdy/homes/files/2541.htm

    http://my.opera.com/007smsdy/homes/files/2540.htm

    http://my.opera.com/007smsdy/homes/files/2539.htm

    http://my.opera.com/007smsdy/homes/files/2538.htm

    http://my.opera.com/007smsdy/homes/files/2537.htm

    http://my.opera.com/007smsdy/homes/files/2536.htm

    http://my.opera.com/007smsdy/homes/files/2535.htm

    http://my.opera.com/007smsdy/homes/files/2534.htm

    http://my.opera.com/007smsdy/homes/files/2533.htm

    http://my.opera.com/007smsdy/homes/files/2532.htm

    http://my.opera.com/007smsdy/homes/files/2531.htm

    http://my.opera.com/007smsdy/homes/files/2530.htm

    http://my.opera.com/007smsdy/homes/files/2529.htm

    http://my.opera.com/007smsdy/homes/files/2528.htm

    http://my.opera.com/007smsdy/homes/files/2527.htm

    http://my.opera.com/007smsdy/homes/files/2526.htm

    http://my.opera.com/007smsdy/homes/files/2525.htm

    http://my.opera.com/007smsdy/homes/files/2524.htm

    http://my.opera.com/007smsdy/homes/files/2523.htm

    http://my.opera.com/007smsdy/homes/files/2522.htm

    http://my.opera.com/007smsdy/homes/files/2521.htm

    http://my.opera.com/007smsdy/homes/files/2520.htm

    http://my.opera.com/007smsdy/homes/files/2519.htm

    http://my.opera.com/007smsdy/homes/files/2518.htm

    http://my.opera.com/007smsdy/homes/files/2517.htm

    http://my.opera.com/007smsdy/homes/files/2516.htm

    http://my.opera.com/007smsdy/homes/files/2515.htm

    http://my.opera.com/007smsdy/homes/files/2514.htm

    http://my.opera.com/007smsdy/homes/files/2513.htm

    http://my.opera.com/007smsdy/homes/files/2512.htm

    http://my.opera.com/007smsdy/homes/files/2511.htm

    http://my.opera.com/007smsdy/homes/files/2510.htm

    http://my.opera.com/007smsdy/homes/files/2509.htm

    http://my.opera.com/007smsdy/homes/files/2508.htm

    http://my.opera.com/007smsdy/homes/files/2507.htm

    http://my.opera.com/007smsdy/homes/files/2506.htm

    http://www.greatestjournal.com/users/kan201/316.html

    http://www.greatestjournal.com/users/kan201/726.html

    http://www.greatestjournal.com/users/kan201/826.html

    http://www.greatestjournal.com/users/kan201/1048.html

    http://www.greatestjournal.com/users/kan201/1436.html

    http://www.greatestjournal.com/users/kan201/1645.html

    http://www.greatestjournal.com/users/kan201/1991.html

    http://www.greatestjournal.com/users/kan201/2211.html

    http://www.greatestjournal.com/users/kan201/2409.html

    http://www.greatestjournal.com/users/kan201/2750.html

    http://www.greatestjournal.com/users/kan201/3028.html

    http://www.greatestjournal.com/users/kan201/3177.html

    http://www.greatestjournal.com/users/kan201/3427.html

    http://www.greatestjournal.com/users/007sms/732.html

    http://www.greatestjournal.com/users/007sms/446.html

    http://www.greatestjournal.com/users/007sms/789.html

    http://www.greatestjournal.com/users/007sms/1076.html

    http://www.greatestjournal.com/users/007sms/1308.html

    http://www.greatestjournal.com/users/007sms/1600.html

    http://www.greatestjournal.com/users/007sms/1857.html

    http://www.greatestjournal.com/users/007sms/2268.html

    http://www.greatestjournal.com/users/007sms/2515.html

    http://www.greatestjournal.com/users/007sms/2735.html

    http://www.greatestjournal.com/users/007sms/3044.html

    http://www.greatestjournal.com/users/007sms/3194.html

    http://www.greatestjournal.com/users/007sms/3377.html

    http://www.greatestjournal.com/users/007sms/3660.html

    http://www.greatestjournal.com/users/007sms/3891.html

    http://www.greatestjournal.com/users/007sms/4248.html

    http://www.greatestjournal.com/users/007sms/4553.html

    http://www.greatestjournal.com/users/007sms/4629.html

    http://www.greatestjournal.com/users/007sms/5022.html

    http://www.greatestjournal.com/users/007sms/5220.html

    http://www.greatestjournal.com/users/007sms/5608.html

  8. seo says:

    Good test. Thanks for sharing

  9. wes says:

    thanks thanks for that …it really help me …for awhile i just loose hoped..hehe

Comments are closed.