Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

unit-test the template method design pattern

Suppose the following template method implementation:

public abstract class Abs
  {
    void DoYourThing()
    {
      log.Info("Starting");
      try
      {
        DoYourThingInternal();
        log.Info("All done");
      }
      catch (MyException ex)
      {
        log.Error("Something went wrong here!");
        throw;
      }
    }

    protected abstract void DoYourThingInternal();

  }

Now, there are plenty of info around on how to test the Abs class, and make sure that DoYourThingInternal is called.
However, suppose I want to test my Conc class:

 public class Conc : Abs
  {
    protected override void DoYourThingInternal()
    {
      // Lots of awesome stuff here!
    }
  }

I wouldn't want to do conc.DoYourThing(), since this will invoke the parent method, which was already tested separately.

I would like to test only the overriding method.

Any ideas?

like image 571
J. Ed Avatar asked Sep 24 '12 16:09

J. Ed


3 Answers

You have labeled the question "tdd" but I doubt you've followed that principle when you encountered this "problem".

If you truly followed tdd your work flow would have been something like

  1. Write a test for some logic not yet implemented
  2. Impl the easiest possible impl for this test to make it green (logic on Conc1 for example)
  3. Refactor
  4. Write a test for some other logic not yet implemented
  5. Impl the easiest possible impl for this test to make it green (logic on Conc2 for example)
  6. Refactor

In "6" you might think that it would be a great idea to implement a template method because Conc1 and Conc2 share some logic. Just do it, and run your tests to see that the logic still works.

Write tests to verify the logic, don't base them how the implementation look like (=start with the tests). In this case, start writing tests verifying that the logic works (the logic later placed in your concrete types). Yes, that means that some code lines (the one in your abstract class) are tested multiple times. But so what? One of the point of writing tests is that you should be able to refactor your code but still be able to verify that it works by running your tests. If you later don't want to use template method pattern, in a ideal world your shouldn't need to change any tests, but just change the implementation.

If you start to think which code lines you test, IMO you loose much of the benefits of writing tests at all. You want to ensure that your logic works - write tests for this instead.

like image 62
Roger Avatar answered Sep 23 '22 08:09

Roger


I assume part of the 'problem' is that there is no way to call a protected method from outside the class. How about a mock class which derives from Conc and provides a new public method:

public class MockConc: Conc
  {
    void MockYourThingInternal()
    {
      DoYourThingInternal()
    }
  }
like image 39
quamrana Avatar answered Sep 23 '22 08:09

quamrana


I wouldn't consider DoYourThingInternal() to be separate from DoYourThing() (as in, two separate modules of code that can be tested in isolation) since you won't be able to instantiate your abstract class alone anyways and the 2 methods will always be run together. Besides, DoYourThingInternal() has access to all protected members of your class and could modify them, with potential side effects on DoYourThing(). So I think it would be dangerous to test DoYourThing() in complete isolation from a concrete implementation of DoYourThingInternal().

However, that doesn't mean you can't have separate tests for DoYourThing()'s expected behavior, which has to remain the same across all implementations of Abs, and DoYourThingInternal()'s expected behavior.

You could use an (abstract) base test class where you define a test for the general behavior expected from DoYourThing(). Then create as many test subclasses as there are implementations of Abs, with unit tests for the specifics of each implementation.

The test from the base test class will be inherited, and when you run any subclass's tests, the inherited test for DoYourThing() will also run :

public abstract class AbsBaseTest
{
  public abstract Abs GetAbs();

  [Test]
  public void TestSharedBehavior() 
  {
    getAbs().DoYourThing();

    // Test shared behavior here...
  }
}

[TestFixture]
public class AbsImplTest : AbsBaseTest
{
  public override Abs GetAbs()
  {
    return new AbsImpl();
  }

  [Test]
  public void TestParticularBehavior()
  {
    getAbs().DoYourThing();

    // Test specific behavior here
  }
}

See http://hotgazpacho.org/2010/09/testing-pattern-factory-method-on-base-test-class/

Don't know if abstract test class inheritance is supported by all unit test frameworks though (I think NUnit does).

like image 40
guillaume31 Avatar answered Sep 24 '22 08:09

guillaume31