如何在单元测试中测试函数行为?

时间:2009-10-18 05:55:38

标签: unit-testing mocking

如果函数只调用另一个函数或执行操作。我该如何测试?目前,我强制执行所有函数应返回一个值,以便我可以断言函数返回值。但是,我认为这种方法会在生产代码中大量使用API​​。我不需要那些函数来返回值。有什么好的解决方案吗?

我认为模拟对象可能是一种可能的解决方案。我想知道何时应该使用assert以及何时应该使用mock对象?有没有一般的指导方针?

谢谢

4 个答案:

答案 0 :(得分:4)

让我们使用BufferedStream.Flush()作为不返回任何内容的示例方法;如果我们自己编写这个方法,我们将如何测试这个方法?

总有一些可观察效果,​​否则该方法将不存在。所以答案可以是测试效果:

[Test]
public void FlushWritesToUnderlyingStream()
{
   var memory = new byte[10];
   var memoryStream = new MemoryStream(memory);
   var buffered = new BufferedStream(memoryStream);

   buffered.Write(0xFF);
   Assert.AreEqual(0x00, memory[0]); // not yet flushed, memory unchanged
   buffered.Flush();
   Assert.AreEqual(0xFF, memory[0]); // now it has changed
}

诀窍是构建代码,以便在测试中不太难以观察到这些影响:

  • 显式传递协作者对象, 就像memoryStream的传递方式一样 到构造函数中的BufferedStream。 这称为dependency injection
  • 针对界面的程序,只是 比如BufferedStream的编程方式 针对Stream界面。这使得 您可以传递更简单,测试友好的实现(在本例中为MemoryStream)或使用模拟框架(如MoQRhinoMocks),这对于单元测试非常有用。

答案 1 :(得分:2)

是的,如果你想测试调用某个函数并传入某些参数,那么mock通常是要走的路。

以下是Typemock(C#)中的操作方法:

Isolate.Verify.WasCalledWithAnyArguments(()=> myInstance.WeatherService("","", null,0));
Isolate.Verify.WasCalledWithExactArguments(()=> myInstance. StockQuote("","", null,0));

通常,您应尽可能使用Assert,直到您无法使用它为止(例如,当您必须测试是否正确调用外部Web服务API时,在这种情况下,您不能/不想直接与Web服务通信)。在这种情况下,您使用mock来验证是否使用正确的参数正确调用了某个Web服务方法。

答案 2 :(得分:2)

很抱歉没有直接回答,但是......你确定你的测试中有确切的平衡吗?

我想知道你是不是在测试太多? 你真的需要测试一个只委托给另一个人的函数吗?

仅返回测试

当你写这篇文章时,我同意你的意见,你不想添加仅对测试有用的返回值,而不是生产。这会使您的API变得混乱,使其变得不那么清晰,这最终会带来巨大的成本。

此外,您的返回值对于测试似乎是正确的,但没有任何内容表示实现返回与实现相对应的返回值,因此测试可能无法证明任何

费用

请注意,测试具有初始成本,即编写测试的成本。 如果实施非常简单,那么失败的风险是非常低的,但是测试的时间仍然累积(超过一百或几千个案例,最终会非常严重)。

但更重要的是,每次重构生产代码时,您可能还需要重构测试。因此测试的维护成本会很高

测试实施

测试方法的作用(它调用的其他方法等)受到批评,就像测试私有方法一样......有几点要做:

  1. 脆弱且代价高昂:任何代码重构都会破坏测试,因此会增加维护成本
  2. 测试私有方法对您的生产代码没有太大的安全性,因为您的生产代码没有进行该调用。这就像验证你实际上不需要的东西。
  3. 当代码有效地委托给另一个代码时,实现非常简单,错误的风险非常低,代码几乎不会改变,所以一次有效(当你写它时)永远不会破坏 ...

答案 3 :(得分:1)

“我想知道何时应该使用断言,何时应该使用模拟对象?是否有任何一般指导原则?”

有一个绝对的,固定的和重要的规则。

您的测试必须包含断言。断言的存在是您用来查看测试是通过还是失败的内容。测试是一种方法,它调用特定夹具中的“被测组件”(一个函数,一个对象,等等),并对组件的行为进行特定的断言

测试断言正在测试的组件。每个测试都必须有一个断言,或者它不是一个测试。如果它没有断言,则不清楚你在做什么。

模拟是组件的替代,以简化测试配置。它是替换真实组件的“模拟”或“模仿”或“虚假”组件。您可以使用模拟来替换某些内容并简化测试。

假设您要测试功能a。并且函数调用函数b。

函数a的测试必须有一个断言(或者它不是测试)。

a的测试可能需要模拟函数b。要隔离这两个函数,可以使用模拟函数b来测试a。

函数b的测试必须有一个断言(或者它不是测试)。

对b的测试可能不需要任何嘲弄。或者,也许b进行OS API调用。这可能需要嘲笑。或者也许b写入文件。这可能需要嘲笑。