【问题标题】:How do I mock a private field?如何模拟私有字段?
【发布时间】:2010-10-09 17:52:10
【问题描述】:

我对模拟真的很陌生,正在尝试用模拟对象替换私有字段。目前,私有字段的实例是在构造函数中创建的。我的代码看起来像...

public class Cache {
    private ISnapshot _lastest_snapshot;

    public ISnapshot LatestSnapshot {
        get { return this._lastest_snapshot; }
        private set { this._latest_snapshot = value; }
    }

    public Cache() {
        this.LatestSnapshot = new Snapshot();
    }

    public void Freeze(IUpdates Updates) {
        ISnapshot _next = this.LastestSnapshot.CreateNext();
        _next.FreezeFrom(Updates);
        this.LastestSnapshot = _next;
    }

}

我要做的是创建一个单元测试,断言ISnapshot.FreezeFrom(IUpdates) 是从Cache.Freeze(IUpdates) 中调用的。我猜我应该用模拟对象替换私有字段_latest_snapshot(可能是错误的假设?)。在仍然保留无参数构造函数并且不求助于公开LatestSnapshot 的集合的同时,我将如何做到这一点?

如果我完全以错误的方式编写测试,那么也请指出。

ISnapshot.FreezeFrom 的实际实现本身调用了具有深层对象图的其他方法的层次结构,因此我不太热衷于断言对象图。

提前致谢。

【问题讨论】:

    标签: c# unit-testing mocking


    【解决方案1】:

    我认为您不需要模拟私有成员变量。模拟对象的公共接口按预期工作的整个想法不是吗?私有变量是 mock 不关心的实现细节。

    【讨论】:

    • 如果被测类使用代表数据库的私有字段怎么办。如何模拟该数据库字段?
    • 您想了解存储库模式,它基本上将数据存储隐藏在一组接口后面,以便轻松模拟和测试它们
    • 仍然,您如何构建模拟存储库并将其传递给消费者?你需要一个setter还是一个constructor参数?
    • 您将独立于私有字段测试存储库以确保其正常工作
    • 我的意思是,如果消费者不模拟存储库,测试会很慢。
    【解决方案2】:

    我不确定你能做到这一点。如果您想测试_next,那么您可能必须将其作为参数传入,然后在单元测试中传入一个 Mock 对象,然后您可以使用 Expectation 对其进行测试。如果我在 Moq 中尝试这样做,我会这样做。

    作为我可能尝试使用 Moq 框架的示例:

    Mock<ISnapshot> snapshotMock = new Mock<ISnapshot>();
    snapshotMock.Expect(p => p.FreezeFrom(expectedUpdate)).AtMostOnce();
    Cache c = new Cache(snapshotMock.Object);
    c.Freeze(expectedUpdate);
    

    注意:我没有尝试编译上面的代码。它只是为了举例说明我将如何解决这个问题。

    【讨论】:

      【解决方案3】:

      我几乎引用了"Working Effectively with Legacy Code" 的技术:

      1. 在单元测试中对您的类进行子类化,并用其中的模拟对象取代您的私有变量(通过添加公共设置器或在构造函数中)。您可能必须使变量受保护。
      2. 为此私有变量创建一个受保护的 getter,并在测试子类中覆盖它以返回一个模拟对象而不是实际的私有变量。
      3. 创建一个受保护的工厂方法来创建ISnapshot 对象,并在测试子类中重写它以返回一个模拟对象的实例而不是真实的对象。这样,构造函数将从一开始就获得正确的值。
      4. 参数化构造函数以获取ISnapshot 的实例。

      【讨论】:

        【解决方案4】:

        这个答案可能很简单,但看看代码,有什么方法不会调用ISnapshot.FreezeFrom(IUpdates)?听起来你想断言一些永远正确的东西。

        正如 Jason 所说,模拟适用于您的类依赖 SomeInterface 来完成工作的情况,并且您希望独立于您在运行时实际使用的 SomeInterface 的任何实现来测试 YourClass

        【讨论】:

          【解决方案5】:

          要问的问题是:如果这有效,外部可见的效果是什么?

          所有这些快照会发生什么?一个选项可能是使用外部的第一个快照初始化缓存,例如在构造函数中。另一个可能是模拟缓存之外重要的快照调用的任何内容。这取决于您关心的情况。

          【讨论】:

            【解决方案6】:

            回复可能为时已晚。无论如何。我也有类似的问题。

            public class Model
            {
              public ISomeClass XYZ{
                  get;
                  private set;
                  }
            }
            

            我需要在我的测试用例中设置 XYZ 的值。我使用这个语法解决了这个问题。

            Expect.Call(_model.XYZ).Return(new SomeClass());
            _repository.ReplayAll();
            

            在上面的例子中,我们可以这样做

            Expect.Call(_cache.LatestSnapshot).Return(new Snapshot());
            _repository.ReplayAll();
            

            【讨论】:

              【解决方案7】:

              如下图所示,将 Cache 转为模板。

              template <typename T=ISnapshot>
              public class Cache {
                  private T _lastest_snapshot;
              
                  public T LatestSnapshot {
                      get { return this._lastest_snapshot; }
                      private set { this._latest_snapshot = value; }
                  }
              
                  public Cache() {
                      this.LatestSnapshot = new Snapshot();
                  }
              
                  public void Freeze(IUpdates Updates) {
                      T _next = this.LastestSnapshot.CreateNext();
                      _next.FreezeFrom(Updates);
                      this.LastestSnapshot = _next;
                  }
              
              }
              

              在生产代码中做:

              Cache<> foo;//OR
              Cache<ISnapshot> bar;
              

              在测试代码中做:

              Cache<MockSnapshot> mockFoo;
              

              【讨论】:

                【解决方案8】:

                您可能必须像这样重构您的类,以便为 ISnapshot 注入不同的依赖项。您的班级将保持不变。

                public class Cache {
                 private ISnapshot _lastest_snapshot;
                
                 public ISnapshot LatestSnapshot {
                  get { return this._lastest_snapshot; }
                  private set { this._latest_snapshot = value; }
                 }
                
                 public Cache() : this (new Snapshot()) {
                 }
                
                 public Cache(ISnapshot latestSnapshot) {
                  this.LatestSnapshot = latestSnapshot;
                 }
                
                 public void Freeze(IUpdates Updates) {
                  ISnapshot _next = this.LastestSnapshot.CreateNext();
                  _next.FreezeFrom(Updates);
                  this.LastestSnapshot = _next;
                 }
                
                }
                

                【讨论】:

                  【解决方案9】:

                  您可以简单地将“setSnapshot(ISnapshot)”方法与您的模拟类实例一起添加到缓存中。

                  您还可以添加一个采用 ISnapshot 的构造函数。

                  【讨论】:

                    猜你喜欢
                    • 2012-06-28
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2013-07-14
                    • 1970-01-01
                    • 1970-01-01
                    • 2012-12-06
                    • 1970-01-01
                    相关资源
                    最近更新 更多