简体   繁体   English

构造函数使用模拟对象,如何单独测试方法?

[英]Constructor uses the mock object, how do I test a method in isolation?

I have a class that looks like this: 我有一个看起来像这样的课程:

class MyClass {

  private IDependency dep;

  public MyClass(IDependency dep) {
    this.dep = dep;
    this.dep.Reset();
  }

  public void Clear() {
    this.dep.Reset();
  }
}

How do I test that the Reset method gets called properly in the Clear method while ignoring what the constructor does? 如何测试在Clear方法中正确调用Reset方法而忽略构造函数的作用?

My Moq test looks like this: 我的Moq测试看起来像这样:

MockRepository mocks = new MockRepository(MockBehavior.Default);
var dep = mocks.Create<IDependency>();

dep.Setup(s => s.Reset());

MyClass myclass = new MyClass(dep.Object);
myclass.Clear():

state.Verify(s => s.Reset(), Times.Exactly(1));

It fails because Reset has been called twice (once in the constructor, and once in the Clear method). 它失败,因为重置已被调用两次(一次在构造函数中,一次在Clear方法中)。

I hoped there would be a better way of doing it, but the mock will record all calls to Reset so using a standard Verify call will always return 2. The following maintains a separate counter, which isn't very elegant. 我希望有更好的方法,但模拟会记录所有调用Reset所以使用标准的Verify调用将始终返回2.以下维护一个单独的计数器,这不是很优雅。 If there's a built-in way of doing this with Moq, I'd love to know. 如果有一个内置的方式与Moq这样做,我很想知道。

int clearResetCount = 0;

Mock<IDependency> dep = new Mock<IDependency>();

MyClass myclass = new MyClass(dep.Object);

dep.Setup(s => s.Reset()).Callback(() => clearResetCount++);

Assert.AreEqual(0, clearResetCount, "Expected clear reset count - before.");

myclass.Clear();

Assert.AreEqual(1, clearResetCount, "Expected clear reset count - after.");

As suggested by others, you can roll your own mock or you can set a number of expectations on the dependency. 正如其他人所建议的那样,你可以推出自己的模拟,或者你可以设置一些对依赖的期望。

For example, you can verify that your method was called: 例如,您可以验证您的方法是否被调用:

var mock = new Mock<IDependency>();
var subject = new MyClass(mock.Object);

subject.Clear();

mock.Verify( dep => dep.Reset(), Times.AtMost(2));

However it's worth pointing out that work within the constructor is a known code smell , and this smell is exacerbated when you try to write tests. 然而,值得指出的是, 构造函数中的工作是已知的代码气味 ,当您尝试编写测试时,这种气味会加剧。

The fact that your constructor needs to call this method on the dependency suggests that this object knows too much information about the implementation details of the dependency. 构造函数需要在依赖项上调用此方法这一事实表明此对象知道有关依赖项的实现细节的过多信息。 This violates the Open Closed Principle and closes you off from scenarios where you don't want the Reset method from being called when it's initialized. 这违反了开放封闭原则,并且在您不希望在初始化时调用Reset方法的情况下将您关闭。

Also consider that any class or test that uses the MyClass concrete object as an dummy parameter will need a Mock initialized or you'll get a NullReferenceException. 还要考虑使用MyClass具体对象作为伪参数的任何类或测试都需要初始化Mock,否则您将获得NullReferenceException。 This adds considerable overhead to writing your tests and adds a level of fragility that equates to long term maintenance and false negatives in your tests. 这会增加编写测试的开销,并增加一定程度的脆弱性,相当于测试中的长期维护和漏报。 The only way around this is to make everything an interface which although effective isn't the best long term strategy either. 解决这个问题的唯一方法是使一切尽管有效的界面也不是最好的长期策略。

As per http://googletesting.blogspot.com/2009/07/separation-anxiety.html , the use of a Factory would reduce some of this coupling and open you up to better reuse of this object. 根据http://googletesting.blogspot.com/2009/07/separation-anxiety.html ,使用Factory会减少一些耦合,并使您更好地重用此对象。

I came across the same problem. 我遇到了同样的问题。

Do the following to have the mock only recording the behavior of the Clear method: 执行以下操作以使模拟仅记录Clear方法的行为:

MockRepository mocks = new MockRepository(MockBehavior.Default);
var dep = mocks.Create<IDependency>();

MyClass myclass = new MyClass(dep.Object);

// setting up the mock just before calling the method under test
// will ignore any prior call to IDependency.Reset
int resetTimes = 0;
dep.Setup(s => s.Reset()).Callback(() => resetTimes++);

myclass.Clear();

mocks.VerifyAll();
Assert.That(resetTimes, Is.EqualTo(1));

Instead of using a mock object, you could write a spy. 你可以写一个间谍,而不是使用模拟对象。 Requires a bit more coding, but the test is easier to read. 需要更多编码,但测试更容易阅读。

class DependencySpy : IDependency {
    public int ResetCallCount { get; private set; }
    public void Reset() { ResetCallCount++; }
    public void ClearResetCallCount() { ResetCallCount = 0; }
}

The test could the be written as 测试可以写成

// Setup
var spy = new DependencySpy;
MyClass myclass = new MyClass(spy);
spy.ClearResetCallCount();
// Exercise
myclass.Clear();
// Validate
Assert.AreEqual(1, spy.ResetCallCount);

You can use reflection to set the private field dep to your mocked object. 您可以使用反射将私有字段dep设置为模拟对象。 Then just call the Clear method and test the dependency call. 然后只需调用Clear方法并测试依赖项调用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM