Friday, February 10, 2006

Creating an interface for testing

When following some common unit testing guidelines you will often need to create interfaces for the purpose of testing.

A common example of this occurs when you use constructor dependency injection. For example, if you have a Foo class that takes a Bar as a constructor argument.
class Foo...
public Foo(Bar bar)
{
this.bar = bar;
}
..
}
In the above example Foo cannot be tested without creating an instance of the Bar class. However, the dependency could be mocked or stubbed (depending upon intent) if Foo instead depended upon an interface.
class Foo...
public Foo(IBar bar)
{
this.bar = bar;
}
..
}
Unfortunately, this approach is often met with criticism.

The common complaints include.
  • Complaint: Creating extra code to support testing. Response: Code written to support testing should be minimized. However, any code that allows for better testing is well worth adding. It will need to be maintained, but this level of effort should be far less than the level of effort required when working with detestable code.
  • Complaint: The overhead of maintaining the class and the interface. Response: Static languages are great at alerting you when your class/interface relationship has become out of synch. Additionally, refactoring tools such as ReShaper ensure that little effort will be necessary to maintain a good relationship.
Using an interface has additional positives.
  • Easy mocking or stubbing. Interfaces dependencies allow you to create mocks or stubs that behave specifically as your test requires. This improves the durability of the tests you write.
  • Truly unit testable classes. Classes that have constructor arguments that are not interfaces require instances of concrete types with defined behavior. When this dependency exists any change to the behavior of a dependency can cause false test failures. Classes that have interfaces as dependencies can have their behavior tested in isolation.
  • Using other types (as long as they implement the interface) as dependencies. While developing applications, requirements change. Using the example above, today it may appear that Foo will always depend on Bar. However, in the future it may turn out that Foo depends on BarNorth and BarSouth. If BarNorth and BarSouth are nothing alike they could share a base class, but will more likely simply implement the same interface. When this occurs, if Foo depends on Bar, the constructor (and likely every place that uses type Bar) will need to be changed to use the interface instead. If an interface were used from the beginning, no changes to the existing codebase would be required.
This is a simple concept, but it's often a tripping point. Don't be afraid of the overhead required to use an interface when it will improve your ability to write quality software.
Post a Comment