Click here to Skip to main content
15,889,116 members
Articles / .NET
Tip/Trick

Running NUnit tests in parallel

Rate me:
Please Sign up or sign in to vote.
5.00/5 (6 votes)
8 Feb 2013CPOL2 min read 39.7K   12   5
NUnit doesn't support running the tests in a fixture in parallel. A trick for getting around this limitation is given.

Problem

For a project I'm working on, I have several NUnit test fixtures with integration tests which are not very resource intensive as they call web services and have to wait for results. As NUnit tests are run in sequence, this means that the whole suite of tests takes a long time to run on the build server. An obvious solution to this problem would be to somehow parallelize the tests but unfortunately NUnit has no support for this. Also I want to be able to still run the tests individually from Visual Studio for debugging purposes.  

Possible solutions

After browsing around a bit, I have come up with the following options:  
  1. Wait for NUnit 3, which should support running tests in parallel.
  2. Use an alternative unit testing framework. The one integrated in Visual Studio 2010 should have support for parallel tests, though I haven't tested it.
  3. Download source code for NUnit and customize.
  4. Come up with some ad-hoc solution.
Options 1 and 2 are not relevant for me as I'm forced to use the version of NUnit available now. I downloaded the NUnit source code and actually managed to make it run unit tests in parallel without too much effort, but the solution was kind of hacky and I would have to deploy custom NUnit DLLs to the build server, which will probably be a hassle and may not be an option at all.  

Ad-hoc Solution

The solution I came up with is a base class which uses reflection to start all tests at once. This base class is inherited by test fixtures which call the Parallelize method in their TestFixtureSetUp method to start running all tests. The test fixture now has two methods for each test: The TestX method decorated with the Test attribute just calls the RunTest method while the actual testing is performed in the XAction method which is called in a separate thread. The RunTest method joins with the thread matching the calling test method and rethrows any exceptions thrown in the test thread. This allows errors to be correlated with the right test.  

C#
public class ParallelizedTestFixture
{
    private readonly IDictionary<string, TestRunner> threads = new Dictionary<string, TestRunner>();
    private readonly IDictionary<string, Action> actions = new Dictionary<string, Action>();
 
    protected bool RunFromConsole { get; set; }
 
    protected void Parallelize()
    {
        string executable = Environment.GetCommandLineArgs()[0];
        RunFromConsole = executable.ToLower().Contains("nunit-console");
 
        foreach (MethodInfo methodInfo in GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance))
        {
            if (methodInfo.GetCustomAttributes(typeof(TestAttribute), false).Length == 1 &&
                methodInfo.GetCustomAttributes(typeof(IgnoreAttribute), false).Length == 0)
            {
                string key = GetKey(methodInfo);
                MethodInfo actionMethod = GetType().GetMethod(key + "Action",
                                                                BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
                if (actionMethod != null)
                {
                    actions.Add(key, () => actionMethod.Invoke(this, new object[] { }));
                }
            }
        }
 
        if (RunFromConsole)
        {
            foreach (KeyValuePair<string, Action> actionPair in actions)
            {
                TestRunner testRunner = new TestRunner(actionPair.Value);
                ThreadStart ts = testRunner.DoWork;
                Thread thread = new Thread(ts);
                testRunner.Thread = thread;
                thread.Start();
                threads.Add(actionPair.Key, testRunner);
            }
        }
    }
 
    protected void RunTest()
    {
        string key = GetKey(new StackFrame(1, false).GetMethod());
        if (RunFromConsole)
        {
            TestRunner testRunner = threads[key];
            testRunner.Thread.Join();
            Assert.IsNull(testRunner.Error, string.Format(CultureInfo.InvariantCulture, "Unexpected exception {0}", testRunner.Error));
        }
        else
        {
            actions[key]();
        }
    }
 
    private static string GetKey(MethodBase method)
    {
        return method.Name.Substring("Test".Length);
    }
 
    private class TestRunner
    {
        private readonly Action action;
 
        public TestRunner(Action action)
        {
            this.action = action;
        }
 
        public Thread Thread { get; set; }
        public Exception Error { get; private set; }
 
        public void DoWork()
        {
            try
            {
                action();
            }
            catch (Exception e)
            {
                Error = e;
            }
        }
    }
}
 
[TestFixture]
public class SomeTextFixture : ParallelizedTestFixture
{
    [TestFixtureSetUp]
    public void FixtureSetup()
    {
         Parallelize();
    }
   
    public void Scenario1Action()
    {
         // Do actual testing.
    }
 
    [Test]
    public void TestScenario1()
    {
         RunTest();
    }
 
    public void Scenario2Action()
    {
    }
 
    [Test]
    public void TestScenario2()
    {
         RunTest();
    }        
}
Note the RunFromConsole property which indicates if the tests are run using nunit-console. This means that the tests are run in parallel when run on the build server and run individually when I start them from Visual Studio.   This solution has a few problems:  
  • Each test requires two methods.
  • There should be no SetUp and or TearDown in fixtures inhering from ParallelizedTestFixture as they will be called at the wrong time.

Conclusion

The solution described is not particularly beautiful but it has cut the execution time of NUnit on the build server by 66% so I will stick with it for now. Suggestions for improvements or alternatives are very welcome.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Denmark Denmark
.NET developer. I wanted to be first an astronaut, then a jet pilot, but when I got a Commodore 64 for Christmas I never looked back. Also I would never have qualified for the first two things and everybody knows computer programmers get all the girls.

Comments and Discussions

 
GeneralThanks! Didn't know about that. Pin
Dr.Walt Fair, PE19-Dec-10 16:13
professionalDr.Walt Fair, PE19-Dec-10 16:13 
GeneralInteresting. Didn't know about NUnit 3 supporting parallel t... Pin
Indivara8-Dec-10 21:14
professionalIndivara8-Dec-10 21:14 
GeneralReason for my vote of 5 An interesting technique to speed up... Pin
BillW338-Dec-10 6:54
professionalBillW338-Dec-10 6:54 
QuestionWhat about test cases? Pin
Dmitri Nеstеruk10-Apr-11 10:25
Dmitri Nеstеruk10-Apr-11 10:25 
AnswerRe: What about test cases? Pin
Charles Ritchea11-Aug-14 11:45
Charles Ritchea11-Aug-14 11:45 
I know this was over 3 years ago, but maybe someone will find this interesting. I'm not implying any of this is for "unit testing", but rather acceptance testing. This is not for dynamic test cases, but rather organized test cases that can share the same or similar test logic. TestCase(Foo), TestCase(Bar) can be adapted to a TestCaseData collection.

I implemented parallelization of TestCases this way. I have abstract classes that contain 1 or many parameterized tests that use filtered or unfiltered TestCaseSource that contain a key to a dictionary of what I call CustomAssertions. These CustomAssertions have a key like this "ShouldNotFoo" and take a lambda with the actual assertion logic. Now, the abstract classes are the base class for both a Parallelized list of TestFixtures and the Individual TestFixtures themselves. The Individual TestFixtures are all marked Explicit and only run when you want to debug them, but the Parallelized test runs during the nightly build. Each TestFixture has a collection of CustomAssertions that run once their time-consuming setup logic is complete. All of the assertions for each test case in a parallized list get concatenated into the TestCaseSource collection and can be filtered to partition the Tests in the UI. So all the "ShouldFoo" tests have their own Test method and all the ShouldBar tests have their own method, but they share the same setup logic. This allows me to submit time-consuming jobs to a server that supports concurrency, and can take 1/6 the time to run then if they ran serially.

Another solution that does not allow the individual debug run unfortunately is to use a SetUpFixture to post the long-running jobs and add them to a HashSet with a key the Individual TestFixtures are aware of. The SetUpFixture will run once for an entire Namespace, so keep that in mind. You then poll for completion when doing the actual tests.

In both these patterns the key is to separate the logic that takes a long time from the actual assertion logic which should be instant.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.