Unit testing 单元测试模板法设计模式

Unit testing 单元测试模板法设计模式,unit-testing,language-agnostic,tdd,Unit Testing,Language Agnostic,Tdd,假设以下模板方法实现: public abstract class Abs { void DoYourThing() { log.Info("Starting"); try { DoYourThingInternal(); log.Info("All done"); } catch (MyException ex) { log.Error("Somethin

假设以下模板方法实现:

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

  }
现在,关于如何测试
Abs
类,并确保调用
doyourthingininternal
有很多信息。
但是,假设我想测试我的
Conc
类:

 public class Conc : Abs
  {
    protected override void DoYourThingInternal()
    {
      // Lots of awesome stuff here!
    }
  }
我不想执行
conc.DoYourThing()
,因为这将调用已经单独测试过的父方法

我只想测试重写方法


有什么想法吗

在Abs上粘贴一个界面并模仿它怎么样?忽略这些调用,或者对它们设置期望值?

我认为部分“问题”在于无法从类外部调用受保护的方法。从
Conc
派生并提供新公共方法的mock类如何:

public class MockConc: Conc
  {
    void MockYourThingInternal()
    {
      DoYourThingInternal()
    }
  }

您已将问题标记为“tdd”,但我怀疑您在遇到此“问题”时是否遵循了该原则

如果你真的遵循tdd,你的工作流程会是这样的

  • 为一些尚未实现的逻辑编写测试
  • Impl此测试最简单的Impl,使其变为绿色(例如Conc1上的逻辑)
  • 重构
  • 为一些尚未实现的其他逻辑编写测试
  • Impl此测试最简单的Impl,使其变为绿色(例如Conc2上的逻辑)
  • 重构
  • 在“6”中,您可能认为实现模板方法是一个好主意,因为Conc1和Conc2共享一些逻辑。只要这样做,并运行您的测试,看看逻辑仍然有效

    编写测试以验证逻辑,不要以实现的样子为基础(=从测试开始)。在这种情况下,开始编写测试,验证逻辑是否有效(稍后将逻辑放入具体类型中)。是的,这意味着一些代码行(抽象类中的代码行)要经过多次测试。那又怎样?编写测试的要点之一是,您应该能够重构代码,但仍然能够通过运行测试来验证代码是否正常工作。如果您以后不想使用模板方法模式,在理想情况下,您不需要更改任何测试,只需更改实现即可


    如果您开始考虑测试哪些代码行,那么您就失去了编写测试的许多好处。你想确保你的逻辑工作-为这个而写测试。

    < P>我不认为<代码> doYuthInIngalAudio()/代码>与<代码> doyththin()/代码>(如,在两个单独的代码模块中,可以单独测试)因为无论如何你都不能单独实例化你的抽象类,这两个方法总是一起运行。此外,
    doyourthingininternal()
    可以访问类中所有受保护的成员,并可以修改它们,这可能会对
    DoYourThing()
    产生副作用。因此,我认为与
    DoYourThingInternal()
    的具体实现完全隔离地测试
    DoYourThingInternal()
    是危险的

    但是,这并不意味着您不能对
    DoYourThing()
    的预期行为进行单独的测试,这在所有Abs实现中必须保持不变,而
    DoYourThingInternal()
    的预期行为也必须保持不变

    您可以使用一个(抽象)基本测试类,在该类中,您可以为
    DoYourThing()
    中预期的一般行为定义一个测试。然后创建与Abs实现一样多的测试子类,并对每个实现的细节进行单元测试

    来自基本测试类的测试将被继承,当您运行任何子类的测试时,
    DoYourThing()
    的继承测试也将运行:

    公共抽象类AbsBaseTest
    {
    公共摘要Abs GetAbs();
    [测试]
    public void TestSharedBehavior()
    {
    getAbs().DoYourThing();
    //在此处测试共享行为。。。
    }
    }
    [测试夹具]
    公共类测试:AbsBaseTest
    {
    公共覆盖Abs GetAbs()
    {
    返回新的AbsImpl();
    }
    [测试]
    公共无效测试特定行为()
    {
    getAbs().DoYourThing();
    //在这里测试特定的行为
    }
    }
    


    但是,我不知道抽象测试类继承是否受到所有单元测试框架的支持(我认为NUnit是这样的)。

    您可以通过几种方法来实现,其中许多方法已经在这里进行了说明。以下是我通常采用的方法:让测试用例从具体类继承

    public ConcTest : Conc
    {
        [Test]
        public void DoesItsThingRight()
        {
             var Result = DoItsThingInternal();
             // Assert the response
        }
    }
    
    希望有帮助


    布兰登

    我明白你的意思。这是有道理的。但是,如果我按照您的建议多次测试某些代码行,这意味着如果我更改这些代码行,我需要更改多个测试。这可能是一个很高的代价…(假设我们现在讨论的是TDD…)您不只是更改代码行。添加新功能意味着添加一个或多个新测试。要通过这些测试,您需要更改实现的代码。如果这意味着打破了旧的测试,则意味着一些旧的规范/测试不再有效。首先进行测试是件好事->您现在有机会知道新代码改变了旧的行为。新代码是否打破了旧代码?旧的测试/规范是否不再有效?您是否需要询问您的客户/产品经理“旧规范如何?不再有效?”