什么是使用Stubs和Mocks的正确方法?

时间:2011-09-06 16:14:04

标签: c# .net unit-testing testing mocking

这是我的例子:

[TestMethod]
public void NewAction_should_return_IndexAction()
{
    NewViewModel viewModel = new NewViewModel()
    {
        Name = "José Inácio Santos Silva",
        Email = "joseinacio@joseinacio.com",
        Username = "joseinacio"
    };

    //IsUserRegistered is used to validate Username, Username is unique.
    _mockAuthenticationService.Setup(x => x.IsUserRegistered(viewModel.Username )).Returns(false);

    //IsUserRegistered is used to validate Email, Email is unique.
    _mockUsuarioRepository.Setup(x => x.GetUserByEmail(viewModel.Email));
    _mockDbContext.Setup(x => x.SaveChanges());
    _mockUsuarioRepository.Setup(x => x.Add(It.IsAny<User>()));

    _userController = new UserController(_mockUsuarioRepository.Object, _mockDbContext.Object, _mockAuthenticationService.Object);

    ActionResult result = _userController.New(viewModel);

    result.AssertActionRedirect().ToAction("Index");

    _mockAuthenticationService.VerifyAll();
    _mockUsuarioRepository.VerifyAll();
    _mockDbContext.VerifyAll();
}

我已经阅读了一些教程,他们说我们应该使用每个测试只有一个模拟

但看看我的测试,它使用3次嘲笑,检查我的行动是否正常工作我需要检查这3次嘲讽,不同意?

如何以正确的方式进行此测试?

4 个答案:

答案 0 :(得分:4)

每个单元测试应该只测试一件事。

在您的单元测试中,您正在测试三个模拟对象。如果mockAuthenticationService失败,将报告此信息并且单元测试将在那里停止。其他Mock对象的任何错误都不会报告,并且会被有效隐藏。

在这种情况下,您应该创建三个单元测试,并在每个测试中仅验证一个Mock对象。其余的只是用作存根。 (存根与Mock对象完全相同,除非你最后没有在它上面调用VerifyAll)

为了避免重复和浪费精力,您应该重构该单元测试,以便大多数代码都在一个单独的方法中。三个单元测试中的每一个都调用此方法,然后验证单个模拟。

您还需要进行测试以确保调用正确的重定向。这也应该是一个单独的测试。

很简单:

[TestMethod]
public void NewAction_should_checkUserRegistered()
{
    SetupTest();
    _mockAuthenticationService.VerifyAll();
}

[TestMethod]
public void NewAction_should_GetUserByEmail()
{
    SetupTest();
    _mockUsuarioRepository.VerifyAll();
}

[TestMethod]
public void NewAction_should_SaveDBContext()
{
    SetupTest();
    _mockDbContext.VerifyAll();
}

[TestMethod]
public void NewAction_should_return_Redirects_Action()
{
    var novoActionResult = SetupTest();
    novoActionResult.AssertActionRedirect().ToAction("Index");
}

答案 1 :(得分:2)

简短回答:“每次测试只有一个模拟。”很暧昧。根据需要使用尽可能多的伪造将被测代码隔离到测试一个条件的“单元”。 应该措辞:每次测试只测试一件事。如果您正在检查多个模拟对象的状态,那么您可能会测试不止一件事。


答案很长:

根据我遇到的最佳实践,这里有很多要回答的单元测试。

(单元测试的艺术)的常用术语,我希望它会变得很普遍:

- 将测试中的代码与应用程序的其余部分隔离开来的对象。
Stub - 一个简单的假象。
模拟 - 存储传递给它的内容的假对象,您可以检查以验证测试。
Stubs和Mocks都是假的。

“每次测试只有一个模拟。”是错误的。您可以根据需要使用尽可能多的伪造,以将测试中的代码与应用程序的其余部分完全隔离。如果一个方法没有参数,那就没什么可假的了。如果方法采用简单的数据类型,例如intstring,没有任何复杂行为,您不需要伪造它。如果您有2个存储库,上下文,传入的服务对象,则伪造所有这些,因此不会调用其他生产方法。

@Mongus Pong说,你应该每次测试一个条件

测试命名约定: MethodUnderTest_Condition_ExpectedBehaviour 在这种情况下,由于您测试了多个条件,因此无法执行此操作。

测试模式:安排,行动,断言。从您的测试来看,这似乎就是您所做的,但您正在安排使用私人成员。您应该在每个测试中用变量替换它们,因为测试的运行顺序并不总是强制执行,这些变量的状态无法保证,使您的测试不可靠。

购买“单位测试的艺术”http://artofunittesting.com/的副本,它将回答你的很多问题并且是一项很好的投资;如果办公室起火,我会抓住其中一本书。

答案 2 :(得分:1)

恕我直言的模拟和存根并不是唯一定义的 - 每个作者使用它们略有不同。

当我使用 mocks 来理解存根“模拟”行为或“输出”时,例如检查模拟对象/接口中的“输入”(如验证-MOODs in MOQ)。

如果你这样看,那么我也认为你应该只使用一个Mock因为你应该只测试一件事 - 如果你看到它更像是存根以注入可测试的接口那么就不可能做到。

如果确实需要VerifyAll,你确实使用了3次嘲讽,但我不认为它们是固定的。

答案 3 :(得分:0)

使用Dev Magic Fake使用模拟和存根的最佳方法,因此您可以模拟UI和数据库以获取更多信息,请参阅Dev Magic假冒codePlex

http://devmagicfake.codeplex.com/

由于

M.Radwan