2009-02-25 119 views
12

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

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

在此先感谢。

回答

16

我几乎是从"Working Effectively with Legacy Code"援引技术:

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

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

+4

如果被测试的类使用表示数据库的私有字段会怎么样?我如何模拟该数据库字段? – Vanuan 2011-05-11 19:50:15

+0

你想知道存储库模式,它基本上隐藏了一组接口后面的数据存储,所以它们可以很容易地进行模拟和测试。 – Jason 2011-05-12 14:53:51

+1

但是,你是如何构建模拟存储库并将其传递给消费者的?你需要一个setter还是一个构造函数参数? – Vanuan 2011-05-12 18:29:24

3

我不确定你能做到这一点。如果你想测试_next,那么你可能需要将它作为参数传入,然后在单元测试中传递一个Mock对象,然后使用Expectation进行测试。如果我在Moq中尝试这样做,那就是我所要做的。

至于什么,我可能会尝试使用起订量框架的例子:

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

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

1

这个答案可能很简单,但看着代码,有没有什么办法可以让ISnapshot.FreezeFrom(IUpdates)不会被调用?听起来像你想断言永远是真的东西。

贾森说,嘲讽意味着对于有些情况下你的等级取决于SomeInterface做的工作,要在从哪个实施SomeInterface你实际上是在运行时使用隔离测试YourClass

1

问的问题是:如果这种方法奏效,外部可见效应是什么?

所有这些快照会发生什么?一种选择可能是从外部初始化缓存并使用它的第一个快照,比如在构造函数中。另一种可能是嘲笑快照在缓存之外的调用。这取决于你在乎的是什么。

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(); 
-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; 
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; 
} 

} 
0

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

您还可以添加一个构造函数,它接受ISnapshot。