我可以使用Rhino Mocks 3.6进行真正的Mock而不定义假值吗?

时间:2011-03-27 14:54:04

标签: c# mocking rhino-mocks

我正在使用Rhino Mocks 3.6。我见过很多类型的编码。有时使用静态GenerateMock方法,有时使用new MockRepository()。我不太了解发生了什么或什么更好。也许某些方法已经过时,但无论如何,让我们回到真正的问题。

我想更好地了解下面的代码中发生了什么,以及更好的测试需要什么。

    [TestMethod]
    public void TestingProperty()
    {
        Person repository = MockRepository.GenerateMock<Person>();
        // tell rhino.mocks when FirstName is called in the next time, it should return "Monica"
        repository.Expect(x => x.Title).Return("Monica");
        // get the mocking ready
        repository.Replay();

        repository.VerifyAllExpectations();

        // when getting repository.Title value, we should receive "Monica" defined previously in expectation
        Assert.AreEqual(repository.Title, "Monica");
    }

我注意到当我删除repository.Replay()时,一切都在继续工作。重播的目的是什么,是否需要?

还需要VerifyAllExpectations吗?它在内部做什么?

我可以避免手动输入“Monica”并为Person设置一个真实的模拟对象吗?

如果这是一个错误的代码,请告诉我你的建议!

2 个答案:

答案 0 :(得分:5)

听起来你没有使用过更新的模拟对象框架。

在较旧的“模拟对象”中,您必须手动对模拟对象的状态进行断言。例如,您将运行测试下的代码,该代码将项添加到模拟对象的列表中。在测试结束时,您将验证是否正确填充了该模拟对象上的列表。这是验证模拟的状态。

这种较旧的风格就像是Rhino存根的不太复杂的版本。

使用较新的模拟对象框架,您将停止验证模拟对象的状态,并开始验证行为。您可以断言您的测试代码如何调用模拟对象,而不是如何设置属性/成员。

您仍然会对正在测试的代码进行经典断言。但是你不会对你的模拟做经典断言。您将设置期望,并使用Rhino断言验证它们。

更正此代码的一些提示:

  • 这里使用的是Rhino Mocks API的两个部分。删除记录/回放部分,因为它更令人困惑,并坚持使用AAA(Arrange,Act,Assert)语法。请参阅the release notes from when AAA was added
  • 直接停止测试模拟对象
  • 测试使用模拟对象的代码,并添加对 代码将在模拟上调用的内容的期望
  • 在调用VerifyAllExepectations之前调用您的代码,并根据需要将模拟传递给它
  • 确保您要模拟的方法和属性标记为virtualabstract,或者您的模拟基于interface
  • 将测试用例拆分为验证mocks被调用的测试,以及验证状态/返回值在测试代码上是否正确的测试
  • 根本没有在模拟上声明状态,因为你直接设置它,而只是测试你的测试用例

以下是一些更正的示例代码:

public class ObjectThatUsesPerson
{
    public ObjectThatUsesPerson(Person person)
    {
        this.person = person;
    }

    public string SomeMethod()
    {
        return person.Title;
    }

    private Person person;
}

[TestMethod]
public void TestingPropertyGotCalled()
{
    // Arrange
    var mockPerson = MockRepository.GenerateMock<Person>();
    mockPerson.Expect(x => x.Title).Return("Monica");
    var someObject = new ObjectThatUsesPerson(mockPerson);

    // Act
    someObject.SomeMethod(); // This internally calls Person.Title

    // Assert
    repository.VerifyAllExpectations();
    // or: mockPerson.AssertWasCalled(x => x.Title);
}

[TestMethod]
public void TestingMethodResult()
{
    // Arrange
    var stubPerson = MockRepository.GenerateStub<Person>();
    stubPerson.Stub(x => x.Title).Return("Monica");
    var someObject = new ObjectThatUsesPerson(stubPerson);

    // Act
    string result = someObject.SomeMethod();

    // Assert
    Assert.AreEqual("Monica", result, "Expected SomeMethod to return correct value");
}

要测试这是否正常,请尝试这些(在每个之后更改代码):

  • 运行一次并确保通过
  • 删除Expect行,运行它,并确保它仍然通过(这不是严格的模拟)
  • 添加额外的Expect行,返回不同的值。运行它,确保它失败
  • 添加额外的SomeMethod电话。运行它,并确保它通过(不是严格的模拟)
  • 删除SomeMethod中的代码。运行它,确保它失败

答案 1 :(得分:2)

使用RhinoMocks编写测试有两种常用方法 - 具有期望的模拟对象和安排,行为,断言(AAA)。您应该阅读非常详细的http://ayende.com/Wiki/Rhino+Mocks+3.5.ashx文章。

Merlyn Morgan-Graham的回答涵盖了第一种方法。 Follwing是如何使用AAA模型编写相同的测试:

[TestMethod]
public void TestingPropertyUsingAAA()
{   
   // Arrange
   var mockPerson = MockRepository.GenerateStub<Person>();
   repository.Stub(x => x.Title).Return("Monica");

   // Act
   var someObject = new ObjectThatUsesPerson(mockPerson);
   someObject.SomeMethod(); // This internally calls Person.Title    

   // Assert
   repository.AssertWasCalled(x => x.Title);
}