单元测试模板方法设计模式

时间:2012-09-24 16:44:04

标签: 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("Something went wrong here!");
        throw;
      }
    }

    protected abstract void DoYourThingInternal();

  }

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

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

我不想做conc.DoYourThing(),因为这将调用已经单独测试的父方法。

我想只测试重写方法。

有什么想法吗?

5 个答案:

答案 0 :(得分:5)

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

如果你真的关注tdd你的工作流程就像是

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

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

    如果你开始考虑你测试的代码行,IMO你可以完全放弃编写测试的好处。您希望确保您的逻辑正常工作 - 而不是为此编写测试。

答案 1 :(得分:3)

我承担了部分问题'是没有办法从类外调用受保护的方法。如何从Conc派生并提供新的公共方法的模拟类:

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

答案 2 :(得分:2)

我不认为DoYourThingInternal()DoYourThing()是分开的(例如,可以单独测试的两个独立代码模块),因为您将无法实例化您的抽象类无论如何,这两种方法总是一起运行。此外,DoYourThingInternal()可以访问您班级中所有受保护的成员,并且可以对其进行修改,并对DoYourThing()产生潜在的副作用。因此,我认为将DoYourThing()DoYourThingInternal()的具体实现完全隔离是很危险的。

然而,这并不意味着你不能对DoYourThing()的预期行为进行单独的测试,这些测试必须在所有Abs的实现中保持相同,并且DoYourThingInternal()的预期行为

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

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

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

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

不知道所有单元测试框架是否支持抽象测试类继承(我认为NUnit确实如此)。

答案 3 :(得分:0)

如何在Abs上粘贴界面并嘲笑它?忽略这些电话,或对它们设定期望?

答案 4 :(得分:0)

你可以采取多种方式,其中许多方法已在此处记录。以下是我通常采用的方法:让测试用例继承自具体类。

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

希望有所帮助!

布兰登