Insights and discoveries
from deep in the weeds
Outsharked

Thursday, November 10, 2011

How to run NUnit tests in Visual Studio 2010/MSTest

There are probably millions of lines of test code written against NUnit, and most people take no joy in switching to MSTest just so they can use Visual Studio's IDE. There is an extension called Visual NUnit which adds some support. This is actually a really nice, solid extension, but unfortunately, it doesn't solve the most basic problem: being able to debug tests from directly within your project.

MSTest is unfortunately closed, sealed, locked. It's virtually impossible to extend it. But there is a quick and dirty way to get around this problem and have the best of both worlds: NUnit as a testing framework, but still have the ability to run (and debug!) tests from within VS. And you don't have to sacrifice anything - they will still work in any NUnit test runner.

Step 1: Assert Your Independence

Add both testing framework namespaces:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using NUnit.Framework;

Create aliases in your namespace declarations:

using Assert = NUnit.Framework.Assert;
using CollectionAssert = NUnit.Framework.CollectionAssert;
using StringAssert = NUnit.Framework.StringAssert;

This causes the Assert references to unambiguously refer to the NUnit version. That covers the objects, then there are all the attributes used to mark things for the framework. Luckily, most attributes do not conflict. Description is an exception, you'll have to pick a framework if you use this attribute, e.g. :

using Description = NUnit.Framework.DescriptionAttribute;

will cause all appearances of Description to be recognized only in NUnit.

Step 2: Search & Replace

You need to add all the corresponding MSTest attributes to get the IDE runner to recognize things. Just add both attributes to each class/method, e.g. [TestClass,TestFixture]

FunctionNUnitMicrosoft

Run tests in this class at start (class-level attribute)

[SetupFixture]

[AssemblyInitialize]
[AssemblyCleanup]

(SetupFixture & AssemblyIntialize/AssemblyCleanup work differently - in NUnit a class is marked as [SetupFixture] and has [Setup] and [TearDown] methods. In MS, these just apply to any static methods, with a limit of one each per namespace)

Run once at start per class

[TestFixtureSetUp]

[ClassInitialize]

Run once at end per class

[TestFixtureTearDown]

[ClassCleanup]

Identify a class containing tests

[TestFixture]

[TestClass]

Run before each test

[Setup]

[TestInitialize]

Run after each test

[Cleanup]

[TestCleanup]

... and, of course, A Test

[Test]

[TestMethod]

Step 3: Instance Setup/Teardown

There are some other differences. The setup types for MS must all be static methods, whereas NUnit allows them to also be instance methods. Recoding everything to use static methods is a headache, so I just do this instead. Chances are, your units tests already inherit from some other class. If so, just change your template class. If not, add one. To fake the NUnit instance startup/teardown methods, just use the constructor and destructor of your base class:

public class Test() 
{
    public Test()
    {
        Setup();
    }
    ~Test() {
        TearDown();
    }

    public virtual void Setup()
    {    }

    public virtual void TearDown()
    {    }
}
I've basically just skipped out on using any of the framework class-level setup/teardown methods, and use the regular class constructor/destructor instead. In each unit test, you just override Setup and Teardown. You probably do this already if your tests inherit from a base class, this just changes the mechanism by which they are invoked. I haven't thought too much about possible side effects of this, but it would seem to be functionally equivalent.

Step 4: TestContext

If you happen to be using TestContext, this will be another conflict, since both frameworks have a same-named object. The MS static intitialization methods have it as a parameter, too, whereas for the NUnit framwork, it's a static object you can always access. An easy solution is just to alias the MS one, since you probably haven't written any code against it yet, e.g.:

using MsTestContext = Microsoft.VisualStudio.TestTools.UnitTesting.TestContext;
using TestContext = NUnit.Framework.TestContext;

Now, if you actually want to use the MS static methods, you can just use MsTestContext as a parameter, and TestContext will refer unambiguously to the NUnit one.

Step 5: Convert to Test Project

Visual Studio won't give you the testing tools until you add this to the .csproj file of your test project. It goes under Project/PropertyGroup.

<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>

Step 6. Develop, Test, Not Necessarily In That Order!


You are now done. If you've been careful, nothing you've done will in any way break this when running under NUnit, and all these tests will now run directly in the Visual Studio IDE as well.


In Summary:

  • Alias conflicting objects
  • Use both the NUnit & MS attributes on each class/method as appropriate
  • Deal with non-implemented instance setup & teardown methods using constructor/destructor
  • Convert to a test proejct

... and you should be good to go. While it may take a little bit of work to update large existing test suites, it's mostly search and replace. For new work, just build this into your template, and it's already done.


2 comments:

  1. Great post, really useful. I managed to get most of my tests going in both frameworks.

    An interesting curly one is: ExpectedException. e.g.
    [ExpectedException(typeof(ArgumentException))]

    I added this line (to make it work in Nunit)
    using ExpectedException = NUnit.Framework.ExpectedExceptionAttribute;
    But I also needed it to work in VisualStudio.

    So I did this:
    using ExpectedException = NUnit.Framework.ExpectedExceptionAttribute;
    using ExpectedException2 = Microsoft.VisualStudio.TestTools.UnitTesting.ExpectedExceptionAttribute;

    [Test, TestMethod]
    [ExpectedException(typeof(ArgumentException))]
    [ExpectedException2(typeof(ArgumentException))]
    public void MyTestThatShouldThrow()
    {
    DoStuffThatThrowsException();
    }

    ReplyDelete
  2. @Flip, thanks for the addition! And that technique would also be useful for other conflicts like the Description attribute.

    ReplyDelete