34

好的,我知道 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,它会按预期失败。

那么,我在第一个代码示例中遗漏了什么吗?你对在存根上做断言有什么想法?(这里有一些有趣的讨论,我个人很喜欢这个回应

4

3 回答 3

53

你的测试试图达到什么目的?

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

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

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

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

Roy Osherove 对模拟和存根有很好的解释。

请发布更多代码!

我们需要全面了解您如何创建存根(或模拟)以及如何将结果用于被测类。ListCount有输入参数吗?如果有,它代表什么?您是否关心它是否以特定值调用?您是否关心是否ListCount 返回某个值?

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

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

  1. 正在测试什么
  2. 在什么情况下?
  3. 预期的结果是什么?

比较: 使用模拟进行基于交互的测试。模拟呼叫测试。

[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 ,因为您不会有任何暴露的状态需要验证!

API 混乱。模拟不是存根。还是他们?

rhino mocks 中的 mock 和 stub 之间的区别是混乱的。传统上,存根并不意味着有期望——所以如果你的测试替身没有调用它的方法,这不会直接导致测试失败。

... 然而,Rhino Mocks API 功能强大,但令人困惑,因为它让您对存根设定期望,对我来说,这与公认的术语背道而驰。我也不在意这些术语。在我看来,如果消除这种区别并且在测试中调用的方法可以起到双重作用,那就更好了。

于 2010-01-15T23:17:48.757 回答
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");
于 2009-01-29T17:36:29.420 回答
0

您是否尝试过使用

data.AssertWasCalled(x => x.ListCount(1) = Arg.Is(EXPECTED_VALUE));
于 2010-01-15T22:23:32.300 回答