强制调用基本方法的虚方法模式

时间:2014-01-09 00:06:09

标签: c# inheritance .net-4.0 override virtual

我正在创建一系列带有'constructor'和'destructor'范例的类。

实例化派生类时。必须首先调用其所有基类的SetUp()方法,然后调用其SetUp()方法(如果实现了它)。

当派生类具有TearDown()方法时,它必须首先执行它的拆卸操作,然后调用其基类的TearDown()方法,然后该方法也必须调用base。TearDown()等等。


例如,如果我控制了可以从Base继承的每个类,我可以执行以下约定:

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();
    }
}

但是有一天,一些混蛋会出现并搞乱大会:

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

他们可能会在尝试自己的行为之前调用base.TearDown(),或者根本不调用基本方法,并且会造成严重的伤害。


因为我不相信我的抽象类的派生遵循约定,并且他们可能选择从我的任何一个不同复杂度的Base类派生,我能想到的唯一选择是密封每个类中的虚方法新的基类并公开一些新的抽象方法,其中派生者可以指定自己的行为,如果他们喜欢:

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
    }
}

然后当然

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
    }
}

好吧,至少现在无论有人试图从哪个班级派生出来,他们都不可能搞砸SetUpTearDown逻辑,但这种模式不需要很长时间才能变老)。

这是一种经典模式,只需要担心一个级别的继承,但在我的情况下,我们可能会逐渐获得更复杂的类,这些类都依赖于维护SetUpTearDown方法顺序。


我该怎么办?

请注意,仅仅在这些类的构造函数和析构函数中执行SetUpTearDown操作是不够的(尽管这样做可以保证我正在寻找的顺序。)如果您必须知道,这是单元测试套件的基础架构。 [TestInitialize][TestCleanup]Base方法指定了SetUpTearDown属性,这些方法用于所有派生单元测试类 - 这就是构造函数的原因无法使用和析构函数,以及为什么正确级联调用至关重要。

在这里使用'Virtual'和/或'Abstract'方法可能是错误的设计模式,但后来我不知道它是什么。我希望派生类从使用一个基类切换到另一个基类很容易,而不必更改任何方法名称。

2 个答案:

答案 0 :(得分:3)

我想出了这个整洁的模式,它将在施工中注册的动作存储在有序列表中。

优点:

  • 保证设置和拆卸的顺序
  • 实现其他设置和拆卸逻辑的明确方法。
  • 一致的模式,无论继承什么基类。

缺点:

  • 需要基本实例字段,因此在静态类需要此模式的情况下不起作用。 (幸运的是,这不是问题,因为VS单元测试只能在非静态类中定义。)

[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); }
}

就是这样。所有艰苦的工作现在由基类完成。

[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 }
}

使用任何基类的派生者可以自由地使用他们的判断,并且有很好的清除方法来执行某些任务,或者传递匿名委托,或者根本不定义自定义SetUp或TearDown操作:

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

答案 1 :(得分:1)

我认为继承不是你问题的答案。我有一个测试框架,具有多个级别的设置和拆卸。这是噩梦,特别是如果你继承了SetUpTearDown方法。现在我离开了。测试框架仍然取决于模板,但我不会覆盖拆卸和设置,而是为这些步骤中必须完成的任何操作提供一次调用方法。我的队友在设置和拆卸的顺序上遇到了完全相同的问题。一旦我停止呼叫他们SetUpTearDown,而是给他们一些有意义的名字,例如CreateDatabaseStartStorageEmulator,每个人都有了这个主意,生活变得更加容易。

我在这里看到的另一件事是测试气味。您的测试过多地进行了前期工作和后期工作。如果这些实际上是集成测试,那么就无法摆脱这种情况。但对于实际的单元测试来说,这绝对是一种测试气味,应该从另一个角度来看待。

对不起,对实际问题的帮助不大,但有时问题出在你的问题上 - )