Testable Code (Part 2)

Lets recap a bit from last time, even though you could just go back and re-read it. I’ll sum up. You want to reduce side-effects where possible.

Benefits of Limiting Side-Effects:

  1. out-of-order / parallel test execution
    • There’s nothing worse than having the order of your tests matter
  2. no tear-down
    • if you have real work going on in your test teardown you’re doing it wrong.
  3. less likely for other code to depend on those side-effects
    • This is subtle, but if you leave side effects hanging around, people might take them for granted and assume that they will always be there. This is important because if you later decide to change your code and remove the side-effects, you may break people.

Benefits of relying on abstractions:

Modular code design has many benefits but for testing the most obvious benefit is being able to use a test double in place of the real concrete implementation. This double allows for your tests to cleanly operate on just the class or method being tested, (the term SUT is often used to describe this) Leave yourself Test-Seams, even if you don’t use them right away. You don’t have to go full Dependency Injection with a framework like Ninject or Funq to reap the benefits of DI. Manual dependency injection works quite well in most cases where you only need to inject either a real concrete class or a test-double. The default for the property or constructor being injected can be the normal dependency for your production code, but leave yourself an area to stitch in the dependency later to isolate your class for the test. For example: (this is totally contrived, sorry, I work in finance.)

public class FinancialCalculator
   private readonly ICreditBureauProvider provider;

   public FinancialCalculator()
     : this(new CreditBureauProvider());

   // constructor injection
   internal FinancialCalculator(ICreditBureaProvider provider)
     this.provider = provider;

   public int GetRiskScore(string ssn)
     int score;

     // use the injected dependency.
     var fico = provider.GetFicoData(ssn);

     // some other calculations specific to the calculator
     // which we want to test in isolation.

     return score;

The default parameterless constructor handles your default dependency registration, but you’ve left yourself open to inject a dependency later for testing when you need it. You can mark this as internal and expose internals only to your UnitTest project using the InternalsVisibleToAttribute for extra assurance that this method will not be called by normal production code. You could also apply the Conditional attribute and #define a UNIT_TEST preprocessor symbol but that’s getting a bit paranoid. This works pretty well for single dependencies in the ideal case, but if you have more than one you might want to switch over to property injection instead for convenience. The hard-line unit tester in me wants to keep things as dogmatically strict as possible, but it’s not always convenient to follow the rules. Provided you aren’t using the other default dependencies in your tests by accident, injecting one or two dependencies into a class which depends on lots of things through properties seems reasonable. You certainly don’t want to run into this:

public class GodObject
    public GodObject()
       : this(new DatabaseLayer(), new ServiceController(), new LoggingModule(), new WidgetFactory, new OkayYouGetWhereIAmGoingWithThis();

That thing is far too dependent on other classes to accomplish the desired task, but if this is a brownfield project and you are in the process of backfilling tests this might be a reasonable approach. At the very least the setup constructor will be a constant reminder that you badly need to refactor that thing as soon as possible.

Okay, that’s all the good advice I can muster right now. Keep on testing and check back soon for more smug than you can handle. Tune in next time for “10 reasons you should stop using LINQ. Seriously. Stop it.”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.