Thứ Năm, 31 tháng 5, 2012

Use Mocking Frameworks to Improve Code Quality

In today's agile/TDD world, mocking has become an important tool in the development of a good, testable design. It's important to understand your mocking framework to make sure you can maximize its benefit. But before I talk about mocking frameworks, I want to review a little bit about subclassing in .NET. Even if you're familiar with subclassing, inheritance and polymorphism, I encourage you to read on.
As I'm sure many of you know, to override a method is to create a subclass of an object and change the behavior of a method. A simple example is an interest calculator. One class may define the calculation using simple interest (I = P*r*t). You may want a new design that uses compound interest (A = P(1+r)n). Let's see how this would look in code:
public class SimpleInterestCalculator
{
 public double Principal { get; set; }
 public double InterestRate { get; set; }
 public double Time { get; set; }

 public virtual double CalculateInterestAmount()
 {
  return Principal*InterestRate*Time;
 }
}
Note that we've made CalculateInterestAmount() virtual. This allows us to override (replace) the behavior of that method in a subclass. We'll do just that in a subclass that handles compound interest calculations:
public class CompoundInterestCalculator : SimpleInterestCalculator
{
 public override double CalculateInterestAmount()
 {
  var newPrincipal = Principal*Math.Pow((1 + InterestRate), Time);
  return newPrincipal - Principal;
 }
}
The key to this is having your members, be it methods, properties, delegates or events, marked as virtual so you can change (override) their behavior.
Of course, you don't always have to change the behavior. Suppose you simply want to make a log of whenever the CaclulateInterestAmount method is called? Sure, you could add that to the SimpleInterestCalculator class, but then you're hard-coding your logging implementation into the class. Instead, we'll take advantage of overriding and create a subclass. For this example, we'll use log4net to do our logging:
public class LoggedSimpleInterestCalculator : SimpleInterestCalculator
{
 private readonly ILog logger = LogManager.GetLogger("interestLogger");

 public override double CalculateInterestAmount()
 {
  logger.DebugFormat("Calculating Interest: P={0}, i={1}, t={2}",
Principal, InterestRate, Time);
  var interest = base.CalculateInterestAmount();
  logger.DebugFormat("Interest Calculated: {0}", interest);

  return interest;
 }
}
First we log that we're about to call the interest calculation method. We then call the base implementation, and then log the result before returning it to the caller. Now your application can use the LoggedSimpleInterestCalculator whenever it wants to have a simple debugging log of the interest calculation.
Tracking Method Calls
The LoggedSimpleInterestCalculator showed how we can add additional behavior without altering the contract of the class. This means we could get crazy and do stuff like tracking what time a method was called, how often it was called -- or even if it was called at all. This type of stuff could come in handy during testing.
Let's imagine a "Bank" class that uses our SimpleInterestCalculator. Every night, the bank's "RunProcessing" method is called to calculate interest on the account only if the principal is more than $500. It's the typical, goofy scenario you see in short demo code:
public class Bank
{
 private readonly SimpleInterestCalculator interestCalculator;

 public Bank(SimpleInterestCalculator interestCalculator)
 {
  this.interestCalculator = interestCalculator;
 }

 public void RunProcessing()
 {
  if( interestCalculator.Principal > 500)
  {
   var interest = 
interestCalculator.CalculateInterestAmount();
   // do something with interest
  }
 }
}
When it comes time to test my Bank class, I can take advantage of subclassing and create my own SimpleInterestCalculator, which will keep track of whether the CalculateInterestAmount was called:
public class TesterInterestCalculator : SimpleInterestCalculator
{
 public bool CalculateWasCalled { get; set; }

 public override double CalculateInterestAmount()
 {
  CalculateWasCalled = true;
  return base.CalculateInterestAmount();
 }
}
And here's how we can use this to test the "Bank" class:
[TestMethod]
public void DontCalcInterestIfAmountLessThan500()
{
 var calculator = new TesterInterestCalculator();
 calculator.Principal = 499.99;
 calculator.InterestRate = 0.05;
 calculator.Time = 2;
 var bank = new Bank(calculator);

 bank.RunProcessing();
 Assert.IsFalse(calculator.CalculateWasCalled);
}

[TestMethod]
public void CalcInterestIfAmountOver500()
{
 var calculator = new TesterInterestCalculator();
 calculator.Principal = 500.01;
 calculator.InterestRate = 0.05;
 calculator.Time = 2;
 var bank = new Bank(calculator);

 bank.RunProcessing();
 Assert.IsTrue(calculator.CalculateWasCalled);
}
Mocking Frameworks to the Rescue
In this example, we had one method to track. It wasn't too much effort to subclass it and keep track of whether that one method was called or not. In the real world, this approach would be too unwieldy to maintain. That's where mocking frameworks come in.
Most mocking frameworks automate what we just did. You tell them you want to create a mock of a specific type. In the background, the framework will actually create a subclass of your type and track when methods are called (and how many times). Some even allow you to define a specific return value from a method -- thus allowing you to totally bypass the call to the base class method. This is handy in situations where calling the base method may take a long time. You can use the mocking framework to simply return a canned response.
Do all mocking frameworks work this way? No. Most of the free ones (like Rhino.Mocks or NMock) use this method of overriding virtual members. Because of this, they are limited to only members that are virtual -- you can't mock static methods (since they aren't virtual) nor do anything with sealed classes as they can't be subclassed. Other frameworks (like TypeMock) utilize the .NET profiling API to do their interception and don't have these limitations. Since I prefer to avoid static methods and sealed classes, I do most of my mocking with Rhino.Mocks and it handles everything I need.
How would you use a mocking framework like we did above in our tests? Here are our tests rewritten with Rhino.Mocks and without the need to create a subclass to track method calls ourselves:
[TestMethod]
public void DontCalcInterestIfAmountLessThan500()
{
 var calculator =
MockRepository.GenerateMock();
 calculator.Principal = 499.99;
 calculator.InterestRate = 0.05;
 calculator.Time = 2;

 var bank = new Bank(calculator);

 bank.RunProcessing();
 
 calculator.AssertWasNotCalled(c => c.CalculateInterestAmount());
}

[TestMethod]
public void CalcInterestIfAmountOver500()
{
 var calculator =
MockRepository.GenerateMock();
 calculator.Principal = 500.01;
 calculator.InterestRate = 0.05;
 calculator.Time = 2;

 var bank = new Bank(calculator);

 bank.RunProcessing();

 calculator.AssertWasCalled(c => c.CalculateInterestAmount());
}
Very nice! No separate class to maintain. The mocking framework can track all virtual method calls and you can make assertions that those calls were, or were not, made.
Mocking frameworks are another tool in your developers toolbox that you can use to enhance the quality of your software (via testing), while reducing the amount of test code you need to write (via mocking). Play around with some of the free mocking frameworks and find one that fits for you.

Không có nhận xét nào: