Saturday, June 7, 2008

Testing Concurrency with Nunit



Nunit is really nice for unit testing. One thing I wanted to do in my unit test is simulate multiple users using the system. Normally it would be nice to have a set of tests for single user scenario. Once those tests pass, it is nice to be able to run them in a "multi-user" environment. I didn't really want to re-code my tests to simulate a multi-user environment, and I didn't really want to create complex threading system to do it. So to solve the problem I used the .NET built-in thread pool, and I leverage the existing tests I have. Here is an example
Suppose we have 2 simple unit tests, one to test a customer request, and the other to test updating a customer record. The implementation is not needed for this sample.

      
      [TestFixture]
      public class TestCustmoerUserCase
      {
         [Test]
         public void TestProcessCustmerRequest()
         {
            // processing to handle a custmer request
         }
         [Test]
         public void TestUpdateCustomer()
         {
            // processing to update a custmer
         }
      }

Suppose I would like to test these two scenarios in a multi-user environment. Here is one possible solution:

      
      [TestFixture]
      public class TestCustmerUserCaseAsync
      {
         private delegate void AsyncDelegate();
         [Test]
         public void TestAsync()
         {
            TestCustmoerUserCase custTest = new TestCustmoerUserCase();
            // you can also run initalization code here, before starting the threads...
            AsyncDelegate asyncOperation = delegate
            {
               custTest.TestProcessCustmerRequest();
               custTest.TestUpdateCustomer();
            };
            Console.WriteLine("Running thread 1");
            IAsyncResult r1 = asyncOperation.BeginInvoke(null, null);
            Console.WriteLine("Running thread 2");
            IAsyncResult r2 = asyncOperation.BeginInvoke(null, null);
            Console.WriteLine("Running thread 3");
            IAsyncResult r3 = asyncOperation.BeginInvoke(null, null);
            Console.WriteLine("Waiting for threads to finish");
            asyncOperation.EndInvoke(r1);
            Console.WriteLine("Finished thread 1");
            asyncOperation.EndInvoke(r2);
            Console.WriteLine("Finished thread 2");
            asyncOperation.EndInvoke(r3);
            Console.WriteLine("Finished thread 3");
         }
      }
   }

Lets make a few notes about the example above

  • I have created a delegate using .NET C# 2.0 anonymous methods. This allows me to place a method within a method and give it a delegate name. In this case my delegate is called asyncOperation.
  • Within the body of asyncOperation are the unit tests calls (which we normally call from a single thread). Note that declaring the delegate does not execute it.
  • Right after declaring my delegate I ask .NET to invoke it 3 times on the thread pool. I do this with the BeginInvoke method, which each .NET delegate support.
  • I pass null for callback, and null for state. That's because in this case I don't need them
  • Each time a BeginInvoke is called, I get a "token" for the threaded execution called IAsyncResult. Its important to keep all 3 references I get from BeginInvoke. (in this example they are saved as r1, r2 and r3
  • After launching the 3 threads. I need to wait for them to finish. Calling EndInvoke waits for the execution to finish, but which one? this is where the IAsyncResult plays an important role.
  • EndInvoke(r1) will wait for the first thread to finish, EndInvoke(r2) waits for the second thread ... as so on.
  • I could of used a loop and an array to store the IAsyncResults, but that would of made the example more complex. For production use, you should use a loop and array for running the threads.

So there you have it. You ran a set of unit tests that you already have without too much work. But, remember multi-threading requires a few things:

  • Your unit tests need to be thread safe! its best that they don't share memory, or the small amount of memory they do share is protected between threads.
  • For more on Nunit go see http://www.nunit.org/
Happy .Netting everyone.

No comments: