Test styles and avoiding setup/teardown

Curious about NSpec, Amir Rajan posted a challenge that posited that NSpec can make your tests cleaner. The results are summarized here:

https://gist.github.com/amirrajan/6701522

One thing that is in stark contrast with my tests:

https://gist.github.com/jbogard/6690905

And the others is that I explicitly never use Setup/Teardown for any of the Arrange/Act/Assert steps of a test (or Setup/Execute/Verify/Teardown for those xUnit Patterns folks out there). The reasons are pretty simple, already well illuminated by several other folks:

http://jamesnewkirk.typepad.com/posts/2007/09/why-you-should-.html

http://agileprogrammer.com/2005/07/23/assiduously-avoid-setup-and-teardown/

http://agileprogrammer.com/2005/08/14/i-really-did-mean-it-avoid-setup-and-teardown/

In fact, the xUnit.net testing framework intentionally removed explicit support for setup/teardown.

My tests generally fall into two patterns:

  • Everything in a test method (AAA are entirely contained inside a single method)
  • Context-specification (each assert is its own method, arrange and act are lifted to fixture setup and executed once across all tests for that fixture)

The first one is fairly simple:

public class validating_subscriptions
{
   public void When_the_account_has_a_valid_subscription_should_report_as_valid {
        Account account = new Account();
        
        account.AddSubscription(GetValidSubscription());

        account.HasValidSubscription().ShouldBeTrue();
    }

    public void When_the_account_has_no_valid_subscriptions_should_report_as_invalid {
        Account account = new Account();
        account.AddSite("sites/1");
        var subscription = new Subscription(
            "products/1",
            "Fabrik Subscription",
            69M,
            DateTime.UtcNow.AddMonths(-13), // expired by one month
            new SubscriptionDuration(1, SubscriptionPeriod.Yearly));

        account.AddSubscription(subscription);

        account.HasValidSubscription().ShouldBeFalse();
    }
}

Arrange, act and assert are contained in each method. It’s really as simple as can be, and this code looks pretty much exactly as my code would in production. No mental gymnastics needed here to figure out what the execution lifecycle/steps are. The entire test is self-contained in a single method.

Context-specification looks a little different:

public class When_creating_a_new_account {
    Account account = new Account();
    
    public void Does_not_have_any_subscriptions() {
        account.Subscriptions.Count.ShouldEqual(0);
    }
    
    public void Does_not_have_any_sites() {
        account.Subscriptions.Count.ShouldEqual(0);
    }
    
    public void Generates_a_new_api_key() {
        account.ApiKeys.First().ShouldNotBeEmpty();
    }
}

In this test, I have one setup (that private field). Depending on your test framework, this looks different. It could be a constructor, [TestFixture] attribute and so on. The key is the arrange/act steps are executed once, as described in the context of the test. The asserts are all different test methods, explicitly named. Because I’m testing multiple facets of behavioral observations, this style lets me incrementally add and verify behavior.

There are cases where we need environmental setup (not fixture setup) before and/or after a test case. I might need to reset a database, clear data, etc. If there aren’t explicit hooks for this sort of work in your test framework (xUnit has before/after test hooks), you may have to use setup/teardown to do so.

If you are forced to do so, I’d consider switching to a different testing framework instead that does support AOP-style extensions. Environment setup/teardown is not important to see inside your test, but fixture setup certainly is.

It’s a simple couple of rules. Limiting yourself to these two styles will result in the clearest, most understandable tests. Ultimately, clean is good, but understandable is better. By matching our test design to good object design, we keep ourselves from getting to clever or novel, and focus on what truly matters.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in TDD, Testing. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Amir Rajan

    Thank you for posting this. And as always I appreciate the conversations we’ve had. You’re one of the few people that give other developers the time of day. Again, thank you for the discourse.

    That conversation on twitter lasted for two days, it was heated, and because of it I have a better understanding of test organization. Using namespaces as a layer of organization was something I never considered, and I can definitely see myself using that in my future tests. They’ll be NSpec tests of course ;-)

  • Pingback: Test styles and avoiding setup/teardown | I can...

  • Pingback: The Morning Brew - Chris Alcock » The Morning Brew #1451