单元测试模板法设计模式



假设以下模板方法实现:

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(),因为这将调用已经单独测试过的父方法。

我只想测试覆盖方法。

有什么想法吗?

您已经将问题标记为"tdd",但我怀疑您在遇到这个"问题"时是否遵循了这一原则。

如果你真的遵循了tdd,你的工作流程会像一样

  1. 为一些尚未实现的逻辑编写测试
  2. 为该测试实现最简单的Impl,使其变为绿色(例如Conc1上的逻辑)
  3. Refactor
  4. 为一些尚未实现的其他逻辑编写测试
  5. 为该测试实现最简单的Impl,使其变为绿色(例如Conc2上的逻辑)
  6. Refactor

在"6"中,您可能会认为实现模板方法是个好主意,因为Conc1和Conc2共享一些逻辑。只要这样做,然后运行测试,看看逻辑是否仍然有效。

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

如果你开始考虑测试哪些代码行,那么你就完全失去了编写测试的很多好处。您希望确保您的逻辑正常工作——而是为此编写测试。

我认为"问题"的一部分是无法从类外部调用受保护的方法。一个从Conc派生并提供一个新的公共方法的mock类怎么样:

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

我不认为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支持)。

在Abs上粘贴一个接口并嘲笑它怎么样?忽略电话,还是对他们设定期望?

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

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

希望能有所帮助!

Brandon

最新更新