我如何模拟私人领域?

时间:2009-02-25 05:06:21

标签: c# unit-testing mocking

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

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本身的实际实现使用深层对象图调用其他方法的层次结构,所以我不太热衷于断言对象图。

提前致谢。

9 个答案:

答案 0 :(得分:20)

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

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

答案 1 :(得分:4)

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

答案 2 :(得分:3)

我不确定你能做到这一点。如果您想要测试_next,那么您可能不得不将其作为参数传递,然后在单元测试中传递Mock对象,然后您可以使用期望进行测试。如果我想在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 :(得分:1)

这个答案可能很简单,但是看一下代码,有没有什么方法可以调用ISnapshot.FreezeFrom(IUpdates)?听起来你想断言一直都是真的。

正如杰森所说,嘲讽是针对你的班级依赖于SomeInterface来完成它的工作的情况,并且你希望独立于YourClass的任何实现来测试SomeInterface在运行时使用。

答案 4 :(得分:1)

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

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

答案 5 :(得分:1)

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

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

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

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

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

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

答案 6 :(得分:0)

您可能必须像这样重构您的类,以便为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;
 }

}

答案 7 :(得分:0)

您可以使用模拟的类实例将“setSnapshot(ISnapshot)”方法添加到Cache中。

您还可以添加一个带有ISnapshot的构造函数。

答案 8 :(得分:-2)

将缓存转换为模板,如下所示。

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;