Rhino Mocks - Stub .Expect vs .AssertWasCalled

时间:2009-01-25 16:53:11

标签: rhino-mocks

好的,我知道在Rhino Mocks中对新的AAA语法存在很多困惑,但我必须诚实,从我到目前为止看到的,我喜欢。它读得更好,并节省了一些按键。

基本上,我正在测试一个ListController,它基本上将负责一些事情列表:)我已经创建了一个最终将成为DAL的接口,这当然是现在的存根

我有以下代码:

manager是被测系统,data是存根数据接口)

    [Fact]
    public void list_count_queries_data()
    {
        data.Expect(x => x.ListCount(1));
        manager.ListCount();
        data.VerifyAllExpectations();
    }

此测试的主要目的是确保经理实际查询DAL。请注意,DAL实际上并不存在,因此没有“实际”值返回..

然而,这是失败的,因为我需要将期望值更改为具有返回值,例如:

        data.Expect(x => x.ListCount(1)).Return(1);

然后运行正常,测试将通过,然而 - 让我感到困惑的是,此时返回值意味着没有。我可以将它改为100,50,42,无论如何,测试总是会通过?

这让我很紧张,因为测试应该是明确的,如果不能满足预期条件,那么应该完全失败吗?

如果我将测试更改为(“1”是计数所链接的预期ID):

    [Fact]
    public void list_count_queries_data()
    {
        manager.ListCount();
        data.AssertWasCalled(x => x.ListCount(1));
    }

这一切都很顺利,如果我将测试切换到AssertWasNotCalled,它会按预期失败。我也认为它看起来好多了,对于测试的内容更清楚,最重要的是PASSES和预期的失败!

那么,我在第一个代码示例中遗漏了什么?您对在存根上进行断言有什么想法? (有一些有趣的讨论here,我个人喜欢this response

3 个答案:

答案 0 :(得分:53)

您尝试实现的测试是什么?

 

您正在验证哪种行为或状态?具体来说,您是在验证协作者(数据)是否正在调用其ListCount方法(基于交互的测试),或者您是否只想让ListCount返回一个预设值以驱动正在测试的类在其他地方验证结果(传统的基于状态的测试)?

如果您想设置期望,请使用模拟和期望: 使用MockRepository.CreateMock<IMyInterface>()myMock.Expect(x => x.ListCount())

如果要存根方法,请使用MockRepository.CreateStub<IMyInterface>()myStub.Stub(x => x.ListCount())

(除此之外:我知道你可以使用stub.AssertWasCalled()来实现与mock.Expect相同的东西,并且可以更好地阅读语法,但我只是深入研究了模拟和存根之间的区别)。

Roy Osherove has a very nice explanation of mocks and stubs.

请发布更多代码!

我们需要一个完整的图片,说明如何创建存根(或模拟)以及如何使用结果来测试被测试的类。 ListCount是否有输入参数?如果是这样,它代表什么?你是否在乎调用某个值?如果ListCount 返回某个值,您是否在意?

正如Simon Laroche指出的那样,如果Manager实际上没有对ListCount的模拟/存根返回值做任何事情,那么测试将不会因为它而通过或失败。所有测试都会期望调用mocked / stubbed方法 - 仅此而已。

为了更好地理解这个问题,请考虑三条信息,您很快就会明白这一点:

  1. 正在测试什么
  2. 在什么情况下?
  3. 预期结果是什么?
  4. 比较: 基于交互的测试与模拟。模拟上的调用是测试。

    [Test]
    public void calling_ListCount_calls_ListCount_on_DAL()
    {
       // Arrange
       var dalMock = MockRepository.Mock<IDAL>();
       var dalMock.Expect(x => x.ListCount()).Returns(1);
       var manager = new Manager(dalMock);
    
       // Act
       manager.ListCount();
    
       // Assert -- Test is 100% interaction based
       dalMock.VerifyAllExpectations();   
    }
    

    使用存根进行基于状态的测试。存根驱动测试,但不是期望的一部分。

    [Test]
    public void calling_ListCount_returns_same_count_as_DAL()
    {
       // Arrange
       var dalStub = MockRepository.Stub<IDAL>();
       var dalStub.Stub(x => x.ListCount()).Returns(1);
       var manager = new Manager(dalMock);
    
       // Act
       int listCount = manager.ListCount();
    
       // Assert -- Test is 100% state based
       Assert.That(listCount, Is.EqualTo(1),
           "count should've been identical to the one returned by the dal!");
    }
    

    我个人赞成尽可能基于状态的测试,尽管在设计时考虑到Tell, Don't Ask的API通常需要基于交互的测试,因为您不会有任何暴露状态需要验证!< / p>

    API混淆。模拟不是存根。或者是他们?

    犀牛嘲笑中的模拟和存根之间的区别是混乱的。传统上,存根并不意味着期望 - 所以如果你的测试double没有调用它的方法,这不会直接导致测试失败。

    ...然而,Rhino Mocks API功能强大,但令人困惑,因为它可以让你设置存根的期望,对我来说,这违背了公认的术语。我也不会考虑很多术语。在我看来,如果区分被消除并且在测试中调用的方法设置了角色会更好。

答案 1 :(得分:1)

我认为这与你的manager.ListCount()使用返回值做的事情有关。

如果它没有使用它,那么你的DAL可以返回任何无关紧要的东西。

public class Manager
{
    public Manager(DAL data)
    { 
        this.data = data
    }
    public void ListCount()
    {
        data.ListCount(1); //Not doing anything with return value
        DoingSomeOtherStuff();
    }    
}

如果您的列表计数正在使用该值执行某些操作,那么您应该将断言置于其正在执行的操作上。例如

Assert.IsTrue(manager.SomeState == "someValue");

答案 2 :(得分:0)

您是否尝试过使用

data.AssertWasCalled(x => x.ListCount(1) = Arg.Is(EXPECTED_VALUE));