单元测试空方法/模拟对象的明显迹象

6

在对代码库进行单元测试时,有哪些明显的迹象表明我需要使用模拟对象?

这是否仅仅意味着在代码库中看到了许多调用其他对象的情况?

此外,如何对不返回值的方法进行单元测试?例如,如果一个方法返回void但是将内容打印到文件中,我只需检查文件的内容吗?

模拟是针对外部依赖项的,因此它涵盖了所有东西,包括文件系统、数据库、网络等等...


为了检查您的最后一点,请访问 http://martinfowler.com/articles/mocksArentStubs.html 确保您了解它们之间的区别。 - Finglas
这是一篇很好的文章,涉及了一些问题:http://xunitpatterns.com/TestStrategy.html - Frank Schwieterman
3个回答

2

如果说有什么的话,我可能会过度使用模拟。

每当一个类调用另一个类时,通常我都会将该调用模拟出来,并验证该调用是否使用了正确的参数。在其他地方,我会编写单元测试来检查被模拟对象的具体代码是否正确执行。

例如:

[Test]
public void FooMoo_callsBarBaz_whenXisGreaterThan5()
{
    int TEST_DATA = 6;
    var bar = new Mock<Bar>();
    bar.Setup(x => x.Baz(It.Is<int>(i == TEST_DATA)))
       .Verifiable();

    var foo = new Foo(bar.Object);

    foo.moo(TEST_DATA);

    bar.Verify();
}

...
[Test]
public void BarBaz_doesSomething_whenCalled()
{
   // another test
}

对我来说,如果我试图将许多类作为一个大的整体进行测试,那么通常会有大量的设置代码。不仅阅读起来很困惑,因为您需要理解所有依赖项,而且在需要进行更改时非常脆弱。

我更喜欢简洁明了的小测试。编写、维护和理解测试意图都更容易。


4
使用模拟对象(mocks)来测试程序会将测试和内部实现细节联系在一起,这意味着它们往往更容易出现故障。而使用基于状态的测试,你只需要关心程序的输出。虽然设置模拟对象通常比较麻烦,但是它们确实在单元测试中有其作用。 - Finglas

0

在单元测试中,模拟/存根/伪造/测试替身等是可以的,并且允许对类/系统进行孤立测试。集成测试可能不使用任何模拟; 它们实际上会影响数据库或其他外部依赖项。

当你必须使用模拟或存根时,你就需要这样做。通常情况下,这是因为你尝试测试的类具有接口依赖性。对于 TDD,您希望按接口而非实现编程,并使用依赖注入(一般而言)。

一个非常简单的例子:

public class ClassToTest
{
   public ClassToTest(IDependency dependency)
   {
      _dependency = dependency;
   }

   public bool MethodToTest()
   {
      return _dependency.DoSomething();
   }
}

IDependency是一个接口,可能包含昂贵的调用(数据库访问、Web服务调用等)。测试方法可能包含类似以下代码:

// Arrange

var mock = new Mock<IDependency>();

mock.Setup(x => x.DoSomething()).Returns(true);

var systemUnderTest = new ClassToTest(mock.Object);

// Act

bool result = systemUnderTest.MethodToTest();

// Assert

Assert.That(result, Is.True);

请注意,我正在进行状态测试(如@Finglas建议的那样),并且只针对正在测试的系统(即我正在测试的类的实例)进行断言。 我可能会检查属性值(状态)或方法的返回值,就像这个案例所示。
我建议阅读单元测试的艺术,特别是如果您正在使用.NET。

-1

单元测试仅适用于在其内部自主工作的一段代码。这意味着它不依赖于其他对象来完成工作。如果您正在进行测试驱动编程或测试优先编程,则应使用模拟。您将创建一个函数的模拟(或存根,我喜欢这样称呼它),并设置某些条件以使测试通过。最初,该函数返回false并且测试失败,这是预期的...然后您编写代码来执行实际工作,直到测试通过。

但我认为您所指的是集成测试,而不是单元测试。在这种情况下,如果您正在等待其他程序员完成他们的工作,并且当前无法访问他们创建的函数或对象,则应使用模拟。如果您知道接口,希望您确实知道,否则模拟就毫无意义,浪费时间,那么您可以创建一个简化版本,以期望未来获得的内容。

简而言之,当您在等待他人并需要某些东西才能完成工作时,最好利用模拟。

如果可能,您应始终返回值。有时会遇到问题,您已经返回了某些内容,但在C和C ++中,您可以具有输出参数,然后使用返回值进行错误检查。


-1 - 这是对mock对象和stub的误解。虽然在进行TDD时,您可以确实存根功能,但这不是mock对象的目的。您不需要mock测试对象本身,只需mock其依赖项即可。 - TrueWill
是的,我想存根和模拟之间有区别。我认为你误解了我的话。在第一段中,我描述了什么是单元测试以及如何使用您创建的对象来模拟您当前无法访问或不关心访问的真实对象。在第二段中,我开始描述模拟是用于集成测试的...但我错了。集成测试是在单元测试之后使用真实对象而不是模拟进行的。无论哪种方式,当您想要在集成之前进行单元测试时,最好利用模拟。 - Brian T Hannan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接