代码之家  ›  专栏  ›  技术社区  ›  Rob Cooper

犀牛模拟-树桩。期待vs。断言已调用

  •  34
  • Rob Cooper  · 技术社区  · 16 年前

    好吧,我知道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 ,它按预期失败了。。我还认为它读起来更好,对正在测试的内容更清晰,最重要的是,如预期的那样通过和失败!

    所以, 我在第一个代码示例中遗漏了什么吗? 你对在存根上做断言有什么看法?(有一些有趣的讨论 here ,我个人喜欢 this response .

    3 回复  |  直到 6 年前
        1
  •  53
  •   Zodman    12 年前

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

    你正在验证什么行为或状态?具体来说,您是否正在验证合作者(数据)是否拥有 ListCount 方法称为(基于交互的测试),或者您只是想进行 列表计数 返回一个预设值来驱动被测类,同时在其他地方验证结果(传统的基于状态的测试)?

    如果你想设定一个期望,可以使用模拟和期望: 使用 MockRepository.CreateMock<IMyInterface>() myMock.Expect(x => x.ListCount())

    如果你想存根一个方法,使用 MockRepository.CreateStub<IMyInterface>() myStub.Stub(x => x.ListCount()) .

    (旁白:我知道你可以使用stup.AssertWasCalled()来实现与mock几乎相同的功能。期待并使用可以说更好的阅读语法,但我只是在深入探讨模拟和语法之间的区别;存根)。

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

    请发布更多代码!

    我们需要一个关于如何创建存根(或模拟)以及如何将结果用于被测类的完整图片。做 列表计数 有输入参数吗?如果是这样,它代表什么?你在乎是不是 呼叫与 某个值?你在乎吗 列表计数 返回 某个值?

    正如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!");
    }
    

    我个人倾向于在所有可能的情况下进行基于状态的测试,尽管对于使用以下方式设计的API,通常需要进行基于交互的测试 Tell, Don't Ask 记住,因为你不会有任何暴露的状态来验证!

    API混淆。嘲笑不是废话。还是他们?

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

    …然而,Rhino Mocks API功能强大,但令人困惑,因为它允许您在存根上设置期望值,对我来说,这违反了公认的术语。我也不太喜欢这个术语。在我看来,最好消除这种区别,让测试方法加倍设置角色。

        2
  •  1
  •   Simon Laroche    16 年前

    我认为这与你的经理有关。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");
    
        3
  •  0
  •   murki    15 年前

    你试过用吗

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