【问题标题】:Stub one method of class and let other real methods use this stubbed one存根类的一种方法并让其他真实方法使用这个存根方法
【发布时间】:2010-12-20 08:15:28
【问题描述】:

我有一个TimeMachine 类,它为我提供当前日期/时间值。该类如下所示:

public class TimeMachine
{
    public virtual DateTime GetCurrentDateTime(){ return DateTime.Now; };
    public virtual DateTime GetCurrentDate(){ return GetCurrentDateTime().Date; };
    public virtual TimeSpan GetCurrentTime(){ return GetCurrentDateTime().TimeOfDay; };
}

我想在我的测试中使用TimeMachine 存根,这样我就可以存根GetCurrentDateTime 方法,让其他两种方法使用存根GetCurrentDateTime 方法,因为我没有存根所有三种方法。我试着写这样的测试:

var time = MockRepository.GenerateStub<TimeMachine>();
time.Stub(x => x.GetCurrentDateTime())
    .Return(new DateTime(2009, 11, 25, 12, 0, 0));
Assert.AreEqual(new DateTime(2009, 11, 25), time.GetCurrentDate());

但是测试失败了。 GetCurrentDate 返回 default(DateTime) 而不是在内部使用 GetCurrentDateTime 存根。

有没有什么方法可以用来实现这种行为,或者它只是我目前没有掌握的 RhinoMocks 的一些基本概念特征?我知道我可以摆脱这两个 GetDate/Time 方法并内联 .Date/.TimeOfDay 用法,但我想了解这是否可能。

【问题讨论】:

    标签: c# mocking rhino-mocks stub


    【解决方案1】:

    存根只是为方法和属性调用提供固定答案,它对 TimeMachine 的实际实现一无所知。恐怕您必须为 3 种方法中的每一种(或您要测试的特定方法)设置结果。

    【讨论】:

    • 我认为你在存根真实类的情况下是不正确的(不是在接口的情况下)。存根只是从类派生并覆盖其方法。如果一个方法不是虚拟的,它就不能被覆盖,并保持由真实类实现的功能。
    【解决方案2】:

    将您的 TimeMachine 更改为抽象类:

    public abstract class TimeMachine
    {
        public abstract DateTime GetCurrentDateTime();
        public DateTime GetCurrentDate(){ return GetCurrentDateTime().Date; };
        public TimeSpan GetCurrentTime(){ return GetCurrentDateTime().TimeOfDay; };
    }
    

    出于生产目的,您可以像这样创建TimeMachine 的具体实现:

    public class SystemTimeMachine : TimeMachine
    {
        public override DateTime GetCurrentDateTime()
        {
            return DateTime.Now;
        }
    }
    

    所有使用TimeMachine 的类现在都可以注入抽象,但在生产环境中,您可以使用SystemTimeMachine 连接您的对象图。

    【讨论】:

      【解决方案3】:

      我刚刚发现可以通过不在这两种方法上使用 virtual 来实现这一点 - 它可以保护方法在生成存根时不被覆盖。

      public class TimeMachine
      {
          public virtual DateTime GetCurrentDateTime(){ return DateTime.Now; };
          public DateTime GetCurrentDate(){ return GetCurrentDateTime().Date; };
          public TimeSpan GetCurrentTime(){ return GetCurrentDateTime().TimeOfDay; };
      }
      

      现在测试通过了。

      【讨论】:

      • 这可能会使测试通过,但只有当该类旨在充当基类并且该方法专门设计为被覆盖时,才应将方法设为虚拟。它不应该仅仅为了单元测试而被虚拟化。
      • 为什么不呢?为了可扩展性,为什么不将所有方法都虚拟化呢? :) 即使我接受你的论点 GateCurrentDateTime 完全可以被覆盖。
      • 我同意。为什么不?好吧,几乎同意。在 Java 中,每个方法都是虚拟的。但是随后您没有获得编译时安全性,因为您覆盖了正确的方法。直到注释拯救了这一天。但是 .Net 不需要注释,因为它强制您将方法声明为虚拟方法并将覆盖方法声明为覆盖。但如果有更好的解决方案就好了。
      【解决方案4】:

      我不确定您使用的是哪个版本的 Rhino.Mocks,但我要做的只是将 GetCurrentDateTime() 方法设为虚拟(就像之前的海报所建议的那样),然后使用 PartialMock() 创建您的模拟对象.有很多设置方法,但以下应该可行:

      var mocks = new MockRepository();
      var time = mocks.PartialMock<TimeMachine>();
      using (mocks.Record())
      {
         Expect.Call(time.GetCurrentDateTime()).Return(new DateTime(2009, 11, 25, 12, 0, 0));
      }
      using (mocks.Playback())
      {
         Assert.AreEqual(new DateTime(2009, 11, 25), time.GetCurrentDate());
      }
      

      【讨论】:

      • PartialMocks 并且此记录/播放用法在 RhinoMocks 中已被 imo 弃用。但是这个解决方案可能相当于我之前的答案。
      【解决方案5】:

      如果方法被标记为virtual,即使您没有对方法进行存根,存根也不会调用原始方法。您可以强制 RhinoMocks 通过执行以下操作调用原始方法:

      var time = MockRepository.GenerateStub<TimeMachine>();
      time.Stub(x => x.GetCurrentDateTime()).Return(new DateTime(2009, 11, 25, 12, 0, 0));
      
      time.Stub(x => x.GetCurrentDate()).CallOriginalMethod(OriginalCallOptions.NoExpectation);
      
      Assert.AreEqual(new DateTime(2009, 11, 25), time.GetCurrentDate());
      

      这是使 RhinoMocks 调用底层原始方法的第三行(单独的)。

      【讨论】:

      • +1 是对这个问题的唯一真正答案(尽管已经过去了 4 年)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-10
      • 2013-01-31
      • 2014-01-31
      • 1970-01-01
      • 2018-10-01
      • 1970-01-01
      相关资源
      最近更新 更多