When I made my previous post about testing with two frameworks I'd inadvertently made a good decision to write the tests using the MS Test attributes on my classes, and then redirected them to the appropriate NUnit attributes, rather than the other way around.
Why was this a good decision? Because Microsoft have more attributes in common use than NUnit. Here's the comparative list:
| MS Test Attribute | NUnit Attribute | Interoperable | Purpose | Other notes |
| TestClass | TestFixture | Yes | Indicates that a class contains a number of tests to be run by the framework | |
| TestMethod | Test | Yes | Indicates that a method is a test. Both frameworks expect the method to be public and with a void return. | |
| TestInitialize | Setup | Yes | A setup method to be run before each test is run. | |
| TestCleanup | Teardown | Yes | A teardown method used to clean up resources after each test is run. | |
| ClassInitialize | TestFixtureSetup | No - MSTest expects ClassInitialize to be static. | A setup method to be run before ANY tests are run in a test class / fixture. | |
| ClassCleanup | TestFixtureTeardown | No - MSTest expects ClassCleanup to be static. | A teardown method used to clean up resources after all tests in a test class / fixture is run. | There's a further difference between the way the two frameworks handle these attributes. In NUnit the method is called immediately following the completion of the last test in a fixture, or after the Teardown method. In MSTest it is run before the AssemblyCleanup attribute (see this blog post). |
| ExpectedException | ExpectedException | Yes | Instructs the framework that it is expected that the test will result in an exception being thrown. | |
| - | Category | n/a | Defines a category for the test (see http://www.nunit.org/index.php?p=category&r=2.2) | |
| - | Explicit | n/a | Requires the test be run explicitly (see http://www.nunit.org/index.php?p=explicit&r=2.2) | |
| - | Suite | n/a | Defines a suite of tests | |
| Ignore | Ignore | Yes | Indicates to the framework to ignore the test in all cases. | |
| AssemblyInitialize | - | n/a | A setup method to be run before ANY tests are run in a test assembly. | |
| AssemblyCleanup | - | n/a | A teardown method to be run after all tests are run in a test assembly. | |
| CssIteration | - | n/a | Indicates the iteration in the project to which this test corresponds. | |
| CssProjectStructure | - | n/a | Indicates the node in the team project hierarchy to which this test corresponds. | |
| DataSource | - | n/a | Defines databinding for data driven testing | |
| DeploymentItem | - | n/a | Indicates a file that MSTest must copy into the testing directory prior to running your test. | |
| Description | - | n/a | Contains a description of the test. | |
| HostType | - | n/a | Used to specify the type of host that this unit test will run in | |
| Owner | - | n/a | Used to specify the person responsible for maintaining, running, and/or debugging the test. | |
| Priority | - | n/a | Used to specify the priority of a unit test. | |
| TestProperty | - | n/a | Generic test metadata container | |
| Timeout | - | n/a | Used to specify the time-out period of a unit test. | |
| WorkItem | - | n/a | Used to specify a work item associated with a test. | |
That which doesn't hurt us...
The nice thing about the extra attributes found in MSTest is that we can either ignore them or circumvent them. For example DeploymentItem is only used by MSTest because of the way they move files into a test directory prior to running the tests, no such functionality is required in NUnit as it quite happily runs inside your binary output folders. The same can be said of all the meta data attributes (Owner, Priority, etc) which don't have a functional equivalent in NUnit, they're useful to decorate our tests inside Team System, but for our desktop testing they're largely irrelevant.
Unfortunately there's nothing we can do about the AssemblyInitialize, AssemblyCleanup, DataSource and HostType, if you're going down the dual testing framework route your going to simply have to not use them.
How to include a MSTest meta data attribute and not make NUnit hurl chunks
Quite straightforward really, we define ourselves a placeholder attribute that has constructors that match the signatures of the extra attributes and then redirect to this attribute in our using block.
1: namespace TestHelpers
2: {
3: [AttributeUsage(AttributeTargets.All)]
4: public class NullAttribute : Attribute
5: {
6: public NullAttribute(string v) { }
7: }
8: }
1: #if NUNIT
2: using NUnit.Framework;
3: using TestClass = NUnit.Framework.TestFixtureAttribute;
4: using TestMethod = NUnit.Framework.TestAttribute;
5: using TestInitialize = NUnit.Framework.SetUpAttribute;
6: using TestCleanup = NUnit.Framework.TearDownAttribute;
7: using ClassInitialize = NUnit.Framework.TestFixtureSetUpAttribute;
8: using ClassCleanup = NUnit.Framework.TestFixtureTearDownAttribute;
9: using DeploymentItem = TestHelpers.NullAttribute;
10: #else
11: using Microsoft.VisualStudio.TestTools.UnitTesting;
12: #endif
13:
14: using System;
15: using System.Collections.Generic;
16: using System.Linq;
17: using System.Text;
18: using Library;
19:
20: namespace UnitTests
21: {
22: [TestClass]
23: public class TemperatureConverterTests
24: {
25: [TestMethod]
26: [DeploymentItem("ConversionChart.xml")]
27: public void FahrenheitToCelsius_0Degress_Fahrenheit()
28: {
29: Assert.AreEqual(-18d,Math.Round(_converter.FahrenheitToCelsius(0),0));
30: }
31: }
32: }
Lather, rinse, repeat the "using DeploymentItem = TestHelpers.NullAttribute;" for all the additional attributes you're using and everything should compile, and NUnit will happily ignore the strange attributes it knows absolutely nothing about.
Final safety tip
Put your conditional using block at the TOP of your list of usings. Doing this prevents the IDE from inserting new usings (inserted using resolve) into the part of the IF block you're currently using. It can get very confusing trying to figure out why your tests don't compile in release, but do in debug.