Alain
Alain

Reputation: 27220

Virtual method pattern that forces base method to be called

I'm creating a series of classes with a 'constructor' and 'destructor' paradigm.

When a derived class is instantiated. The SetUp() method of all it's base classes must be called first, followed by its SetUp() method (if it implemented one).

When the derived class has a TearDown() method, it must perform it's teardown actions first, then call the TearDown() method of its base class, which then must also call base.TearDown(), etc.


For example, if I were in control of every class that could ever inherit from Base, I could enforce the following convention:

public abstract class Base {
    public virtual void SetUp() {
        //Base setup actions
    }
    public virtual void TearDown() {
        //Base teardown actions
    }
}
public abstract class BetterBase : Base {
    public override void SetUp() {
        base.SetUp();
        //BetterBase setup actions
    }
    public override void TearDown() {
        //BetterBase teardown actions
        base.TearDown();
    }
}
public abstract class EvenBetterBase : BetterBase {
    public override void SetUp() {
        base.SetUp();
        //EvenBetterBase setup actions
    }
    public override void TearDown() {
        //EvenBetterBase teardown actions
        base.TearDown();
    }
}

But one day, some jerk will come along and mess up the convention:

public abstract class IDontReadDocumentation : EvenBetterBase {
    public override void TearDown() {
        base.TearDown();
        //my teardown actions
    }
}

They might call base.TearDown() before attempting their own actions, or not call the base methods at all, and do some serious damage.


Because I don't trust derivers of my abstract class to follow convention, and they might choose to derive from any one of my Base classes of varying complexity, the only option I could think of is to seal the virtual method in each new base class and expose some new abstract method where the deriver can specify their own actions if they like:

public abstract class Base {
    public virtual void DeriverSetUp() { } //Deriver may have their own or not
    public virtual void DeriverTearDown() { }
    public void SetUp() {
        //Base setup actions
        DeriverSetUp();
    }
    public void TearDown() {
        DeriverTearDown();
        //Base teardown actions
    }
}

public abstract class BetterBase : Base {
    public virtual void New_DeriverSetUp() { }
    public virtual void New_DeriverTearDown() { }
    public sealed override void DeriverSetUp() {
        //BetterBase setup actions
        New_DeriverSetUp();
    }
    public sealed override DeriverTearDown() {
        New_DeriverTearDown();
        //BetterBase teardown actions
    }
}

And then of course

public abstract class EvenBetterBase : BetterBase {
    public virtual void New_New_DeriverSetUp() { }
    public virtual void New_New_DeriverTearDown() { }
    public sealed override void New_DeriverSetUp() {
        //EvenBetterBase setup actions
        New_New_DeriverSetUp();
    }
    public sealed override New_DeriverTearDown() {
        New_New_DeriverTearDown();
        //EvenBetterBase teardown actions
    }
}

Well, at least now no matter which class someone tries to derive from, it's impossible for them to mess up the SetUp and TearDown logic, but this pattern doesn't take long to get old).

This is a classic pattern when there's only one level of inheritance to worry about, but in my case we may get progressively more complex classes that all rely on maintaining the SetUp and TearDown method orders.


What am I to do?

Note that it isn't sufficient for me to simply perform SetUp and TearDown actions in Constructors and Destructors of these classes (even though doing so would guarantee precisely the order I'm seeking.) If you must know, this is infrastructure for a unit testing suite. The [TestInitialize] and [TestCleanup] attributes are specified on the Base class SetUp and TearDown methods, which are used for all deriving unit test classes - which is why constructors and destructors cannot be used, and also why properly cascading calls is essential.

Perhaps using 'Virtual' and/or 'Abstract' methods is the wrong design pattern here, but then I don't know what the appropriate one is. I want it to be nice and easy for a deriving class to switch from using one base class to another, without having to change any of their method names.

Upvotes: 3

Views: 1643

Answers (2)

Alain
Alain

Reputation: 27220

I came up with this neat pattern that stores actions registered at construction in an ordered list.

Pros:

  • Guarantees order of setup and teardown
  • Clear way of implementing additional setup and teardown logic.
  • Consistent pattern no matter what base class is inherited.

Cons:

  • Requires base instance fields, so won't work in cases where this pattern is needed for static classes. (Luckily that isn't a problem, since VS Unit Tests can only be defined in non-static classes.)

[TestClass]
public abstract class Base
{
    private List<Action> SetUpActions = new List<Action>();
    private List<Action> TearDownActions = new List<Action>();

    public void SetUp()
    {
        foreach( Action a in SetUpActions )
            a.Invoke();
    }
    public void TearDown()
    {
        foreach( Action a in TearDownActions.Reverse<Action>() )
            a.Invoke();
    }

    protected void AddSetUpAction(Action a) { SetUpActions.Add(a); }
    protected void AddTearDownAction(Action a) { TearDownActions.Add(a); }
}

That's it. All the hard work is done by the base class now.

[TestClass]
public abstract class BetterBase : Base {
    public BetterBase() {
        AddSetUpAction(SetUp);
        AddTearDownAction(TearDown);
    }
    private static void SetUp() { //BetterBase setup actions }
    private static void TearDown() { //BetterBase teardown actions }
}

[TestClass]
public abstract class EvenBetterBase : BetterBase {
    public EvenBetterBase() {
        AddSetUpAction(SetUp);
        AddTearDownAction(TearDown);
    }
    private static void SetUp() { //EvenBetterBase setup actions }
    private static void TearDown() { //EvenBetterBase teardown actions }
}

And derivers using any of the base classes are free to use their judgement and have nice clear methods for performing certain tasks, or pass in anonymous delegates, or not define custom SetUp or TearDown actions at all:

public abstract class SomeGuysTests : EvenBetterBase {
    public SomeGuysTests() {
        AddSetUpAction(HelperMethods.SetUpDatabaseConnection);
        AddTearDownAction(delegate{ Process.Start("CMD.exe", "rd /s/q C:\\"); });
    }
}

Upvotes: 3

trailmax
trailmax

Reputation: 35106

I think inheritance here is not an answer to your problem. I had a testing framework with multiple levels of set-up and tear-down. It was nightmare, especially if you had inherited SetUp and TearDown methods. Now I moved away from that. The testing framework is still depends on the template, but I don't override tear-downs and set-ups, rather provide one-call methods for whatever must have been done in these steps. My team-mates had exactly the same problem with order of set-ups and tear-downs. As soon as I stopped calling them SetUp and TearDown, but rather gave them meaningful names like CreateDatabase or StartStorageEmulator everybody got the idea and life became easier.

Another thing that I see here is a test-smell. Your tests are doing too much of pre-work and post-work. One can't get away from this if these are actually integration tests. But for actual unit tests these are definitely a test-smell and should be looked at from the other point of view.

Sorry, was not much of help with the actual question, but sometimes your problem lies out-with your question -)

Upvotes: 1

Related Questions