【问题标题】:Rhino Mocks: fixing AssertWasCalled for disposed objectsRhino Mocks:为已处置的对象修复 AssertWasCalled
【发布时间】:2010-02-25 18:27:39
【问题描述】:

我有一个看起来有点像这样的测试(我从我的实际问题中简化了一点):

[Test]
public void Eat_calls_consumption_tracker_OnConsume()
{
   var consumptionTrackerStub = 
      MockRepository.GenerateStub<IConsumptionTracker>();
   var monkey = new Monkey(consumptionTrackerStub);
   var banana = new Banana();

   monkey.Eat(banana);

   consumptionTrackerStub.AssertWasCalled(x => x.OnConsume(banana));
}

这可以正常工作,只是 Monkey 在吃完之后会处理掉 Banana。因此,香蕉对象不再处于可用状态。特别是,Banana.Equals 实现在调用Dispose 后无法工作,因为它使用了已释放的非托管资源。

不幸的是AssertWasCalled 会导致Banana.Equals 被调用,从而导致测试失败。解决此问题的最佳方法是什么?

【问题讨论】:

    标签: unit-testing rhino-mocks


    【解决方案1】:

    我倾向于建议实例化对象的类应该处理它。将香蕉传给猴子,然后相信它会在食用后妥善处理,这绝不是一个好主意。

    您不能将实例化 Banana 所需的数据传递给 Monkey,而不是 Banana 本身吗?或者,你不能在你的调用类中处理它吗?

    【讨论】:

    • 一个对象的所有权可以转移——你只需要记录它发生在哪里。否则就不可能有一次性物品的工厂。
    • 是的,也许我的评论应该被表述为“请求实例化的类”或类似的东西。我同意可以实例化一次性的东西并将其扔回另一个类,释放你对它的控制(工厂风格)。我的问题是生成一个需要处置的类,然后将其传递给另一个类,尤其是在自己持有它的同时,这意味着两个类中的任何一个都可能尝试处置该对象。似乎很难调试问题。
    【解决方案2】:

    如果 Monkey.Eat 的参数是 Ibanana(或 IFood),并且 Ibanana 是 IDisposable 的后代,会怎样?然后你可以模拟 Ibanana,甚至验证是否调用了 Dispose。

    编辑:您可以使用Moq

    [Test]
    public void Eat_calls_consumption_tracker_OnConsume()
    {
        var consumptionTrackerStub = new Mock<IConsumptionTracker>();
        var monkey = new Monkey(consumptionTrackerStub.Object);
        var banana = new Banana();
        monkey.Eat(banana);
    
        consumptionTrackerStub.Verify(x => x.OnConsume(It.IsAny<Banana>()));
    }
    
    public class Banana
    {
        public override bool Equals(object obj)
        {
            throw new ObjectDisposedException("Banana");
        }
    }
    
    public class Monkey
    {
        public Monkey(IConsumptionTracker tracker)
        {
            _tracker = tracker;
        }
    
        public void Eat(object obj)
        {
            _tracker.OnConsume(obj);
        }
    
        private readonly IConsumptionTracker _tracker;
    }
    
    public interface IConsumptionTracker
    {
        void OnConsume(object obj);
    }
    

    【讨论】:

    • +1 我已经意识到除了被测试的类之外的所有内容都会使这个问题消失。不幸的是,MonkeyBanana 都是互操作包装器,无法被抽象掉——Monkey.Eat 方法在将香蕉的托管包装器传递给它的非托管等价物之前“剥离”它,因此它依赖于香蕉属于特定类类型。与现有非托管代码互操作的需求确实使正确的 TDD 变得复杂。
    【解决方案3】:

    几种可能的解决方案:

    1. 也许你可以放宽你的断言:你真的需要检查吃的是香蕉吗?
    2. 在 OnConsume 上使用内联回调并断言它是香蕉而不是其他水果。此回调将在香蕉被处理之前执行。

    顺便说一句:现在我想起来了,我认为你的代码有一个概念问题:你怎么知道 OnConsume 的某些实现不想保留香蕉皮?

    【讨论】:

    • 没有概念上的问题——banana 合约的一部分是它引发了一个 Disposing 事件,以便非所有者可以在它被处置之前放弃他们的引用权。
    • 好吧,引入回调仍然是解决问题的最简单方法。或者您可以实现一个 DummyConsumptionTracker 来检查方法调用中是否提供了香蕉。断言引发了 Disposing 事件也是如此。
    【解决方案4】:

    事实证明,您可以强制 Rhino Mocks 使用 object.ReferenceEquals 检查参数,如下所示:

    [Test]
    public void Eat_calls_consumption_tracker_OnConsume()
    {
       var consumptionTrackerStub = 
          MockRepository.GenerateStub<IConsumptionTracker>();
       var monkey = new Monkey(consumptionTrackerStub);
       var banana = new Banana();
    
       monkey.Eat(banana);
    
       consumptionTrackerStub.AssertWasCalled(
          x => x.OnConsume(
             Arg<Banana>.Matches(
                y => object.ReferenceEquals(y, banana))));
    }
    

    object.ReferenceEquals即使香蕉被丢弃也仍然有效,从而解决了问题。

    【讨论】:

      猜你喜欢
      • 2010-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多