Technology with opinion

Thursday, May 08, 2008

NUnit Best Practices

Programming is not just an art, not just a science, but a discipline.  Part of what we all know we should be doing includes solid unit testing.  Below is my list of best practices, feel free to comment and add others I enjoy feedback.
  1. Create a separate assembly for your test fixtures
    • i.e. don't be lazy and put your test fixtures in the same assembly as the application code)
  2. Create a bin folder per solution and place nunit.framework.dll in there
  3. Each project should copy their binaries to the solution bin folder.  Your Post-Build event should look like this: copy $(TargetFileName) "../../../bin/"
  4. Categorize your test fixtures such as DAO, ETL and BusinessRules.  This will let you test pieces of your application more easily: [Category("ETL")]
  5. Once you have the basic interface of a class written, it is time to write your unit test for it.  Even though it will fail it will at least serve as a place holder to go back to as well as serving as a test for when things are working
  6. Make sure each test is atomic.  Therefore if you are inserting data into a table and you know say CustomerName must be unique, then the second time you run your test it will fail because of uniqueness.  For this reason have your persistence tests be all inclusive, include an insert, update and delete.  Put your delete code into a finally so that it is always executed regardless of if the other tests succeed or not
  7. If you notice all of your test fixtures perform certain setup every time, create a base class for this, let's say each one of your DAO classes will be instantiating a connection and opening it before starting and this connection needs to be closed at the end of testing whether it fails or succeeds
    • Obviously we would never want to handle connection objects in our Unit Tests, this would be bad design, in the real world it may be a stream or nhibernate session that you  are configuring.
[TestFixture]
public class DAOBaseFixture
{
SqlConnection cn;

[SetUp]
public void Init()
{
  cn = new SqlConnection();
  cn.Open("...");
}

[TearDown]
public Cleanup()
{
  cn.Close();
}
}

Now derivatives of this class won't even have to implement the [TestFixture] attribute nor take care of their setup.  They will be free to just take care of their own tests.

public class CustomerDAOTests : DAOBaseFixture
{
[Test]
[Category("DAO")]
public void FindAll()
{...}
}

I still recommend to people writing .Net code to use NUnit.  First it's free and you don't have to own any version of Visual Studio to support it.  Therefore if you have programmers or testers offshore who only have Visual Studio Pro they can still run it.  You don't even need a Visual Studio license to run it.  Finally it's a very mature product and it's semantics are well accepted and somewhat portable from language to language.

9 comments:

Thomas Eyde said...

I slightly disagree with your point #7: I think it's a good thing to let my test classes expose everything it does. I consider base setups and teardowns a code smell.

In other words, when it comes to test classes, it's ok to inherit common implementation, but not common usage. Whenever my test class opens a database connection, I want to see it in the test - not in the base.

TrueWill said...

For step 2, it's nice to have a global library directory in source control with the NUnit binaries in there (Pragmatic Unit Testing recommends something similar).

Step 3 I don't quite get; we tend to create a separate project in the same solution for the unit test project. It references the main project using a project reference. There's no need for a post-build event.

Thanks,
Bill Sorensen

Scott White said...

Good feedback...

on #7
I think your approach is valid. However I would argue that if you setup you connection in your setup & teardown that it is part of you test since these are executed if you just run one test. Also there's no reason you couldn't expose the setup/teardown methods in your base class

on #3
By having all your bins necessary in a central directory someone can run all your tests whether they are using just one project or even if they are not a programmer at all. You could also have your build server check in the latest build to this location into your source code control as well.

Thanks

Thomas Eyde said...

@Scott: I think we are on the same page on #7. Just to clarify, I prefer all my test classes to:

[Setup]
public void Setup() {
OpenConnection();
}

[TearDown]
public void Teardown() {
CloseConnection();
}

However, OpenConnection and CloseConnection could be part of the base class.

Manfred said...

I'm not quite sure whether I understand #5. Generally I would say that test-driven developement is about driving the design of the code. This includes the interfaces, and maybe in particular the interfaces. In other words I would start with the tests even for driving out the interface of a class. - Maybe I'm overlooking something. What is the reason that you create the "basic interface" first? What do you mean with "basic interface"?

Cheers,
Manfred.
---
Manfred Lange.
csUnit developer
csUnit Home Page
On Agile Leadership
On Dot Net

Joe said...

"Create a separate assembly for your test fixtures"
Then I can only test public methods from my test code.
Yikes.
One of the problems with Microsoft's nUnit-wanna-be implementation is this very fact. And for that very reason our company sticks with nUnit.
Making something public should take great consideration. Making it public so it can be tested....ouch!

TrueWill said...

Hi Joe,

Some people feel that testing only the public interface is a best practice; that generally works well for me.

If you prefer, you can use InternalsVisibleTo to allow your unit test assembly to access items with internal visibility.

Vincent said...

@Thomas

Yes, I must agree on your point #7: We need to inherit common implementation instead of common usage.

Scott White said...

In all honesty, I really don't like the word "Best Practices" even though I used it, ;-). In retrospect it's not the correct title, 'NUnit recipes' would have been more accurate. Thanks for the feedback