Monday, May 11, 2009

How to test your multi-threaded code (part 2 of 3)?

Previously we saw how to build a test to find a multi-thread bug our your code. Now we will look at how to reproduce debug and fix it.

Remember our bank account class:

public class Account
{
   public double Balance { get; set; }

   private Account(double amount)
   {
       Balance = amount;
   }

   public static Account OpenNew(double amount)
   {
       return new Account(amount);
   }

   public void Deposit(double amount)
   {
       double tempAmount = Balance;
       // potential problem
       lock (this)
       {
           Balance = tempAmount + amount;
       }
   }

   public void Withdraw(double amount)
   {
       double tempAmount = Balance;
       // potential problem
       lock (this)
       {
           Balance = tempAmount - amount;
       }
   }
}

And our test:

[TestMethod()]
[HostType("Chess")]
public void BalanceMutiThreadTest()
{
   Account account = Account.OpenNew(10000);

   Thread thread = new Thread(a => ((Account) a).Withdraw(100));
   thread.Start(account);
   account.Deposit(100);
   thread.Join();

   Assert.AreEqual(10000, account.Balance);
}

Now if we look carefully at the test result detail, there is an explanation on how to reproduce this particular schedule. All you have to do is to copy/paste the code provided just before your test method call.

[TestMethod()]
[HostType("Chess")]
[TestProperty("ChessMode", "Repro")]
[TestProperty("ChessBreak", "BeforePreemption")]
#region ChessScheduleString (not human readable)
[TestProperty("ChessScheduleString", @"bpilaiaaaaaaaaaaaeaaonlnahgabmejjgcfcgcpgnmkhlhpekpfeknhoahekbaiiagabdcenijaeabaommbiimnogjcombngjehcdcjklckibmkgffggffnggbgeammonjnlmphnohloplnphnohloplnphlkdljneochphnpppdpfmgggeabgmpgmoeknkmjjocbiakkmibpdphohmbpdpcchomnfpodnhpidfpappfpfhhpponplpkgpmdelpppbgpkplkpoflfmbopdpkgdppbppfpephpocllfpedhppkopjkppjlohpppohpaaldcaoojfhfaaaaaa")]
#endregion
public void BalanceMutiThreadTest()
{
   Account account = Account.OpenNew(10000);

   Thread thread = new Thread(a => ((Account) a).Withdraw(100));
   thread.Start(account);
   account.Deposit(100);
   thread.Join();

   Assert.AreEqual(10000, account.Balance);
}

(your code may be different)

With that code in place if you run your test again (without the debugger) you will see that only one schedule was evaluated and you got the same result as the previous test. From there, as a tester, you job is done. You check in the code and hand it to the development team. If you are a member of a small team, as I usually am, you may be the tester and the developer so you’ll have to debug the code yourself.

Now run this test again but this time with the debugger. The execution should stop just before one of the lock statement. If you step once you will be able to inspect tempAmount and Balance values and see the problem. Between the line where the debugger stop and the previous line another thread changed the Balance value. Now you can see the don’t match.

Of course in this case the solution is easy, we just have to put the tempAmount assignation inside of the lock block but in the next post you will see a case where the solution is not so obvious.

1 comment:

redsolo said...

IMHO, never test threads in unit test, its to much work. Make the thread run() method as small as possible and move all logic outside so it can be tested without running multiple threads in a unit test.