【问题标题】:Asserting that a method is called exactly one time断言一个方法只被调用一次
【发布时间】:2010-09-22 02:44:42
【问题描述】:

我想断言一个方法只被调用了一次。我正在使用 RhinoMocks 3.5。

以下是我认为可行的方法:

[Test]
public void just_once()
{
    var key = "id_of_something";

    var source = MockRepository.GenerateStub<ISomeDataSource>();
    source.Expect(x => x.GetSomethingThatTakesALotOfResources(key))
        .Return(new Something())
        .Repeat.Once();

    var client = new Client(soure);

    // the first call I expect the client to use the source
    client.GetMeMyThing(key);

    // the second call the result should be cached
    // and source is not used
    client.GetMeMyThing(key);
}

如果GetMeMyThing() 的第二次调用调用source.GetSomethingThatTakesALotOfResources(),我希望此测试失败。

【问题讨论】:

  • 上面唯一的错误是使用 GenerateStub 而不是 GenerateMock 并且缺少最后一行:client.VerifyAllExpectations();
  • ...我已经编辑了接受的答案...

标签: tdd mocking rhino-mocks assertions


【解决方案1】:

这是我验证方法是否被调用一次的方式。

[Test]
public void just_once()
{
    // Arrange (Important to GenerateMock not GenerateStub)
    var a = MockRepository.GenerateMock<ISomeDataSource>();
    a.Expect(x => x.GetSomethingThatTakesALotOfResources()).Return(new Something()).Repeat.Once();

    // Act
    // First invocation should call GetSomethingThatTakesALotOfResources
    a.GetMeMyThing();

    // Second invocation should return cached result
    a.GetMeMyThing();

    // Assert
    a.VerifyAllExpectations();
}

【讨论】:

  • +1 for using 'using (mocks.Record()) { .. } 它增加了很多可读性。
  • -1 这不起作用。如果多次调用 GetSomethingThatTakesALotOfResources,它不会抛出异常。
  • 我很确定这在我 2 年前写这篇文章的时候是有效的。我将更新帖子以包含新的方法。
  • -1 +1 谢谢!
  • 但是,第二个 a.Expect(...) 应该是 a.Stub(...) 或者如果 x.GetSomethingThatTakesALotOfResources(key) 仅是对 a.VerifyAllExpectations() 的调用将失败调用一次。
【解决方案2】:

如果你想确保一个方法只被调用一次,你可以创建严格的模拟。

var mock = MockRepository.GenerateStrictMock<IMustOnlyBeCalledOnce>();
mock.Expect(a => a.Process()).Repeat.Once();
var helloWorld= new HelloWorld(mock);

helloworld.Process()

mock.VerifyAllExpectations();

【讨论】:

    【解决方案3】:

    您可以将委托传递给 WhenCalled 以计算调用次数:

    ...
    uint callCount = 0;
    source.Expect(x => x.GetSomethingThatTakesALotOfResources(key))
        .Return(new Something())
        .WhenCalled((y) => { callCount++; });
    ...
    Assert.AreEqual(1, callCount);
    

    此外,您应该使用模拟而不是存根,并验证对模拟的期望。

    【讨论】:

      【解决方案4】:

      我一直在使用 AssertWasCalled 扩展来解决这个问题。这是我能找到/想出的最好的方法,但如果我不必指定两次调用会更好。

          [Test]
          public void just_once()
          {
              var key = "id_of_something";
      
              var source = MockRepository.GenerateStub<ISomeDataSource>();
      
              // set a positive expectation
              source.Expect(x => x.GetSomethingThatTakesALotOfResources(key))
                  .Return(new Something())
                  .Repeat.Once();
      
              var client = new Client(soure);
              client.GetMeMyThing(key);
              client.GetMeMyThing(key);
      
              source.AssertWasCalled(x => x.GetSomethingThatTakesALotOfResources(key),
                                     x => x.Repeat.Once());
              source.VerifyAllExpectations();
          }
      

      【讨论】:

      • 谢谢,唯一对我有用的答案。对我来说,一个不同之处在于,为了避免内部异常(nullref)并确保我得到了一个不错的 Rhino 期望异常,我不得不 Repeat.Twice() 来克服“失败”的情况。这就是为什么这个解决方案很好,这没关系,AssertWasCalled 准确地定义了需要什么
      • Excpect 有什么作用?我认为VerifyAllExpectations 不适用于 Stub,它仅适用于 Mock。但就算能行,又有什么用呢?您的 AssertWasCalled 包含它需要的所有断言。我会将Expect 更改为Stub 并放弃Repeat.Once,并删除VerifyAllExpectations
      • 如果您将 .GenerateStub 更改为 .GenerateMock ,您可以删除 AssertWasCalled 并依赖 VerifyAllExpectations - 请参阅已接受的答案以获得最简单的解决方案 - 我对其进行了编辑。
      【解决方案5】:

      拥有一个名为“Exactly”的功能可以方便地对可能陷入无限循环的代码编写测试。我很想编写一个测试,这样对方法的第二次调用就会引发异常。

      一些 Python 库允许您对期望进行排序,因此第一个返回 false,第二个引发异常。

      Rhino 不会那样做。带有 .Once 的部分模拟将拦截第一个调用,其余的将传递给原始方法。所以这很糟糕,但这是真的。

      您必须创建一个手模。派生一个“可测试”类,并赋予它在第一次跟注后加注的能力。

      【讨论】:

        【解决方案6】:

        您可能对 Rhino Mocks 3.5 文档中的this bit 感兴趣(引用如下)。看起来你需要模拟这个类,而不是存根它,它才能以你期望的方式工作。

        存根和模拟之间的区别

        ...

        模拟是我们可以设置的对象 期望,这将验证 预期的行动确实 发生了。存根是一个对象 使用以传递给下的代码 测试。您可以设置期望 它,所以它会以某种方式行动, 但那些期望永远不会 已验证。存根的属性将 自动表现得像平常一样 属性,你不能设置 对他们的期望。

        如果你想验证的行为 被测代码,您将使用 以适当的期望模拟, 并验证。如果你只想 传递一个可能需要在 某种方式,但不是重点 这个测试,你将使用一个存根。

        重要提示:存根永远不会导致 测试失败。

        【讨论】:

          【解决方案7】:

          这是我刚刚做的(由Ray Houston 推荐)。我仍然希望有一个更优雅的解决方案...

          [Test]
          public void just_once()
          {
              var key = "id_of_something";
          
              var source = MockRepository.GenerateStub<ISomeDataSource>();
          
              // set a positive expectation
              source.Expect(x => x.GetSomethingThatTakesALotOfResources(key))
                  .Return(new Something())
                  .Repeat.Once();
          
              var client = new Client(soure);
          
              client.GetMeMyThing(key);
          
              // set a negative expectation
              source.Expect(x => x.GetSomethingThatTakesALotOfResources(key))
                  .Return(new Something())
                  .Repeat.Never();
          
              client.GetMeMyThing(key);
          }
          

          【讨论】:

          • 这只有在您的调用代码处于足够高的级别以使您可以访问重复的方法时才有效。就我而言,我正在调用 1 个方法,其中在该方法内部进行了 2 个调用,因此我无法在测试主体内重复调用。无论如何我仍然尝试过,但是 Never() 只是覆盖了曾经,我只是得到 Expect 1 got 0
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2023-03-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-05-30
          • 1970-01-01
          相关资源
          最近更新 更多