【问题标题】:Unit testing void methods/mocking object tell-tale signs单元测试无效方法/模拟对象的迹象
【发布时间】:2010-02-12 23:22:14
【问题描述】:

在对代码库进行单元测试时,我需要使用模拟对象的迹象是什么?

这会像在代码库中看到对其他对象的大量调用一样简单吗?

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

模拟是针对外部依赖的,所以这就是一切,不是吗?文件系统、数据库、网络等……

【问题讨论】:

标签: unit-testing mocking


【解决方案1】:

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

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

例子:

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

对我来说,如果我尝试将大量类作为一个大块进行测试,那么通常会有大量的设置代码。当您试图了解所有依赖项时,这不仅读起来非常混乱,而且在需要进行更改时非常脆弱。

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

【讨论】:

  • 您最终将测试与内部实现细节与模拟联系起来,这意味着它们往往会更频繁地中断。使用基于状态的测试,您只关心输出。模拟通常设置起来比较麻烦,但它们在单元测试中肯定有自己的位置。
【解决方案2】:

模拟/存根/假货/测试替身/等。在单元测试中很好,并且允许单独测试被测类/系统。集成测试可能不使用任何模拟;他们实际上命中了数据库或其他外部依赖项。

您在必要时使用模拟或存根。通常这是因为您要测试的类依赖于接口。对于 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 建议的那样),并且我只是针对被测系统(我正在测试的类的实例)断言。如本例所示,我可能会检查属性值(状态)或方法的返回值。

我建议阅读The Art of Unit Testing,尤其是如果您使用的是 .NET。

【讨论】:

    【解决方案3】:

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

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

    简而言之,最好在您等待他人并且需要一些东西来完成您的工作时使用模拟。

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

    【讨论】:

    • -1 - 这是基于对模拟和存根的误解。虽然您在执行 TDD 时肯定会删除功能,但这不是模拟对象的用途。你不模拟被测对象,你模拟它的依赖关系。
    • 是的,我想存根和模拟之间是有区别的。我想你误解了我说的话。在第一段中,我描述了什么是单元测试,以及如何使用您创建的对象来模拟您当前无法访问或不想访问的真实对象。在第二段中,我开始描述模拟用于集成测试......但我错了。集成测试是在单元测试之后完成的,并且是用真实的对象完成的,而不是模拟。无论哪种方式,当您想在集成之前进行单元测试时,最好使用模拟。
    猜你喜欢
    • 2014-06-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多