Unit Testing, Agile Development, Leadership & .NET - By Roy Osherove
Marking the test fixture as IDisposable isn't enough. I don't know about you, but the only time I need to use TearDown is to take down static data that could bleed into other tests, not as a place to dispose of resources.
I've often found that sometimes reusing some methods and code inside of unit test fixture classes can make the unit tests more declarative and easier to understand.
"Don't put anything in a setup method that doesn't pertain to every test method in the test class"
I think the biggest thing I'm going to change in my TDD style from now on is ditching the one testfixture class per production class way of organizing unit tests. That does lead to some ugliness.
"If we don't all realize that in the real world tests have to be as maintainable (or even more so) as production code, we won't come out of the middle ages of unit testing."
I absolutely couldn't agree more.
Jeremy, I agree with what I believe is one your points: NUnit made a mistake by not creating a new instance of the class for each test. With xUnit.net, you won't need to clean up shared static state between tests, because your constructor (SetUp) should be creating non-static state which will automatically be cleared between runs by virtue of getting a new instance per test method.
Most of the cases where I've wanted TearDown -- rolling back a database being the most common -- are things I would rather implement as cross-cutting concern attributes like [AutoRollback].
I'm not talking about state in the TestFixture classes themselves. I'm thinking specifically of static cache's or IoC container state that could spill out between unit tests. Just creating a new TestFixture class on every unit test doesn't necessarily help in that regard.
Another example that I hit a couple months back is screen testing. I use a combination of NUnitForms and Fit to drive tests directly through a WinForms screen. What I quickly found out is that if I left a screen open at the end of one test without closing it down the next UI test in a completely different TestFixture would fail. Using a TearDown was a cheap way to clear up this problem.
Granted, you don't want to put yourself in that position, but it still happens, especially in integration tests.
I'm not sure that I'd call a new test fixture instance per unit test a real improvement. How much does that slow down the testing?
I totally agree with you Roy. Making changes to tests is a lot easier when they are written with good coding practices. I've see hours of refactoring that could have been avoided if better practices were followed.
If you find your setup code is too big or that you're lost scrolling through your test, its a smell your class does too much.
If you really are testing an isolated, independent unit then I venture that a test which is complicated enough to require code re-use can be considered a code smell - the production code class should be easy enough to use that you don't need lots of complicated set up. And you can often avoid complicated logic in the assertions by using a collection of specific "this input leads to this output" tests, which I find to be a valuable (because conceptually perpendicular) cross-check of the more procedurally defined behavior in the production code.
Like any code smell, it's relative. I'm quite happy with using helper methods for abbreviation purposes, for example. But keep the conditionals out of the test machinery, please.
I appreciate that xUnit frameworks are used for developer (rather than acceptance) level testing of integrated systems as well as more primitive units, and in those instances my nose is much less sensitive.