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

发布于 2024-10-13 09:21:33 字数 633 浏览 5 评论 0原文

我有一个如下所示的类:

class MyClass {

  private IDependency dep;

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

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

如何测试 Reset 方法是否在 Clear 方法中正确调用,同时忽略构造函数的作用?

我的 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));

它失败了,因为 Reset 已被调用两次(一次在构造函数中,一次在 Clear 方法中)。

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?

My Moq test looks like this:

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).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

↙温凉少女 2024-10-20 09:21:33

我希望有更好的方法来做到这一点,但是模拟将记录对 Reset 的所有调用,因此使用标准 Verify 调用将始终返回 2。以下维护单独的柜台,不太优雅。如果有一种内置的方式可以用 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.");

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. If there's a built-in way of doing this with Moq, I'd love to know.

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.");
避讳 2024-10-20 09:21:33

正如其他人所建议的,您可以推出自己的模拟,也可以对依赖项设置一些期望。

例如,您可以验证您的方法是否被调用:

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

subject.Clear();

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

但是值得指出的是,可以工作构造函数内有一种已知的代码气味,当您尝试编写测试时,这种气味会加剧。

事实上,您的构造函数需要在依赖项上调用此方法,这表明该对象知道有关依赖项的实现细节的太多信息。这违反了开闭原则,并且使您无法在初始化时调用 Reset 方法。

还要考虑到任何使用 MyClass 具体对象作为虚拟参数的类或测试都需要初始化 Mock,否则您将得到 NullReferenceException。这会增加编写测试的相当大的开销,并增加一定程度的脆弱性,相当于测试中的长期维护和漏报。解决这个问题的唯一方法是让一切都成为一个界面,虽然有效,但也不是最好的长期策略。

根据 http://googletesting.blogspot.com/2009/07/separation-anxiety .html,使用工厂会减少一些这种耦合,并使您能够更好地重用该对象。

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.

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. 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.

一向肩并 2024-10-20 09:21:33

我遇到了同样的问题。

执行以下操作以使模拟仅记录 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));

I came across the same problem.

Do the following to have the mock only recording the behavior of the Clear method:

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));
∞琼窗梦回ˉ 2024-10-20 09:21:33

您可以编写一个间谍,而不是使用模拟对象。需要更多的编码,但测试更容易阅读。

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

测试可以写成

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

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);
别理我 2024-10-20 09:21:33

您可以使用反射将私有字段 dep 设置为您的模拟对象。然后只需调用 Clear 方法并测试依赖项调用即可。

You can use reflection to set the private field dep to your mocked object. Then just call the Clear method and test the dependency call.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文