In my first post in the Unit Testing Best Practices series, I introduced you to the basic lifecycle of an NUnit test fixture. This time, we’re going to see what happens when we give our tests a common test fixture base class.
Why a base class?
There are two cases where having a base test fixture can be useful in keeping your test code simpler:
- You have lots of the same kind of tests. Maybe you’re writing integration tests for your persistence layer. Your tests are going to all be performing some similar operations, like creating a database connection. Maybe they all need to reference an Inversion of Control container like StructureMap or Autofac in order to retrieve repository instances. You don’t want to write that boilerplate code for every fixture, do you?
- You have a hierarchy of classes that need testing. If you have a base class in your domain, you will probably want to set up a parallel type hierarchy of test fixtures. In this case, a base test fixture gives you one place to define tests that verify the base class invariants to which all your derived classes need to conform.
The Code
Notice that the base class isn’t attributed with the [TestFixture] attribute. I only want this class to act as a base for my actual fixtures. I don’t intend for it to be run as a fixture in its own right.
Note too that I’m reinforcing the fact that this class doesn’t stand alone by making the base class abstract, and following standard practices for an abstract base class by making the constructor protected instead of public. We’ve also made the IDisposable.Dispose method implementation virtual so we can extend its behavior in derived classes.
Test code is still code. Follow the same design practices here that you would in your application code.
The Results
Now when we run our tests, we get the following output:
- Base Constructor
- Derived Constructor
- Base TestFixtureSetup
- Derived TestFixtureSetup
- Base SetUp
- Derived SetUp
- Derived First Test passes!
- Derived TearDown
- Base TearDown
- Base SetUp
- Derived SetUp
- Derived Second Test fails!
- Derived TearDown
- Base TearDown
- Base SetUp
- Derived SetUp
- Base First Test passes!
- Derived TearDown
- Base TearDown
- Base SetUp
- Derived SetUp
- Base Second Test fails!
- Derived TearDown
- Base TearDown
- Derived TestFixtureTearDown
- Base TestFixtureTearDown
- Derived Dispose
- Base Dispose
Observations
Note that even with a base class, all our observations from last time are still true:
- The Test Fixture class is constructed once.
- TestFixtureSetup and TestFixtureTeardDown run one time.
- TestSetup and TestTearDown run around every test.
- TestTearDown runs even when a test fails.
- NUnit knows about IDisposable.
- TestFixture constructors and dispose methods are called in the same order as for any other class. This is one of the reasons I prefer using constructor/disposable semantics with test fixtures instead of relying on the special FixtureSetUp/FixtureTearDown attributes – we already know the rules for constructors and disposables.
- The base class SetUp and TearDown methods run around those from the derived class. This is true for both Fixture- and Test- SetUp and TearDown methods. You can think of your base class wrapping your derived class, like Russian nested dolls. This pattern also extends as you create deeper inheritance hierarchies. SetUp is run from the outermost base class down to the leaf derived class, and TearDown is run in exactly the opposite order.
- All Test-level SetUp and TearDown methods are run for every test. This behavior is a little surprising. Notice that even on the test methods defined in the base class, the derived class’ SetUp and TearDown methods are still getting run. If you start seeing base class tests failing because something they expected from SetUp is suddenly different, this is a likely culprit.
- Tests are run in alphabetical order. Ok, you can’t infer this one from the output, you’re just going to have to take my word for it (or go play with the test names yourself and see what happens.) NUnit sorts all tests on the fixture by name, from A to Z, and then runs them in that order. Base class test methods are identified as <ClassName>.<TestMethodName> for purposes of sorting. This means that your base class tests will all be grouped together when the fixture is run, but they could all run right in the middle of your derived class’ tests.
You can quickly see from the results above that our first guideline, Use constructors for class-wide setup. Use IDisposable for class-wide cleanup. Avoid TestFixtureSetup and TestFixtureTearDown, is even more relevant once we start creating a hierarchy of test fixtures. Constructors and IDisposable keep our test fixtures working like any other class we’d write.
- Don’t rely on test run order. Test method run order varies between different hosts. NUnit and ReSharper and Gallio rarely all agree on which order to run your tests. As a developer, you’re often not even running a full test suite -you’re running specific fixtures, or even just a few tests within a fixture. If your tests only pass when they’re run in a specific order, you’re going to waste a lot of time hunting down false failures. Remember, the closer you are to release, the more likely that your tests will start failing for “no reason at all.” Keep your tests independent of each other, and you’ll always be able to run them with confidence.
- Limit test-level SetUp and TearDown semantics. It’s bad enough when a test fails. It’s even worse when you have to jump around between base and derived class code to stitch together what’s happening for each test. If at all possible, limit the definition of SetUp and TearDown methods to your base class. In recent years, I find that if I get my fixture initialization right in the constructor, then I rarely have to do much resetting around each individual test.
Next time, we’re going to see how to run our own initialization code before any of our test fixtures are even created.
> Notice that the base class isn’t attributed with the [TestFixture] attribute.
sorry, in your sample base class is attributed with the textfixture attribute)
Ah, good catch. I updated the sample code to remove the attribute. Thanks!