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

C# 构造函数使用模拟对象,我如何单独测试一个方法?,c#,unit-testing,moq,C#,Unit Testing,Moq,我有一个类似这样的类: class MyClass { private IDependency dep; public MyClass(IDependency dep) { this.dep = dep; this.dep.Reset(); } public void Clear() { this.dep.Reset(); } } MockRepository mocks = new MockRepository(MockBehavior.De

我有一个类似这样的类:

class MyClass {

  private IDependency dep;

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

  public void Clear() {
    this.dep.Reset();
  }
}
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方法中被正确调用,同时忽略构造函数所做的事情

我的Moq测试如下所示:

class MyClass {

  private IDependency dep;

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

  public void Clear() {
    this.dep.Reset();
  }
}
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));
MockRepository mocks=newmockrepository(MockBehavior.Default);
var dep=mocks.Create();
dep.Setup(s=>s.Reset());
MyClass MyClass=新的MyClass(dep.Object);
myclass.Clear():
state.Verify(s=>s.Reset(),Times.justice(1));

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

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

我希望有更好的方法,但是mock将记录所有对
重置的调用,因此使用标准的
验证
调用将始终返回2。下面是一个单独的计数器,它不是很优雅。如果有一个内置的方式来做到这一点与最低起订量,我很想知道

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.");
int clearResetCount=0;
Mock dep=新Mock();
MyClass MyClass=新的MyClass(dep.Object);
dep.Setup(s=>s.Reset()).Callback(()=>clearResetCount++);
AreEqual(0,clearResetCount,“预期的清除重置计数-之前”);
myclass.Clear();
AreEqual(1,clearResetCount,“预期的clearResetCount-after.”);

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

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

正如其他人所建议的,您可以使用自己的mock,也可以对依赖项设置一些期望值

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

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

subject.Clear();

mock.Verify( dep => dep.Reset(), Times.AtMost(2));
var mock=new mock();
var subject=新的MyClass(mock.Object);
subject.Clear();
mock.Verify(dep=>dep.Reset(),Times.AtMost(2));
然而,值得指出的是,当您尝试编写测试时,这种气味会加剧

构造器需要对依赖项调用此方法这一事实表明,此对象知道太多有关依赖项实现细节的信息。这违反了打开-关闭原则,使您无法在初始化重置方法时调用重置方法

还认为使用MyCasic具体对象作为虚拟参数的任何类或测试都需要模拟初始化,否则将得到Null ReavyExchange异常。这为编写测试增加了相当大的开销,并增加了相当于测试中长期维护和错误否定的脆弱性水平。解决这个问题的唯一方法是让所有的东西都成为一个界面,虽然它很有效,但也不是最好的长期策略


根据,使用工厂将减少一些这种耦合,并使您能够更好地重用此对象

我遇到了同样的问题

执行以下操作,使模拟仅记录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));
MockRepository mocks=newmockrepository(MockBehavior.Default);
var dep=mocks.Create();
MyClass MyClass=新的MyClass(dep.Object);
//在调用测试中的方法之前设置模拟
//将忽略之前对IDependency.Reset的任何调用
int resetTimes=0;
dep.Setup(s=>s.Reset()).Callback(()=>resetTimes++);
myclass.Clear();
mocks.VerifyAll();
断言(resetTimes,Is.EqualTo(1));

如果我以后决定删除构造函数中的重置,那么我的测试将失败。有什么方法可以在调用Clear之前重置调用计数吗?@kalithlev我更新了一个Moq示例,该示例将专门测试调用
Clear
时的
reset
调用计数。这也是我即将编写的解决方案。我做了类似的操作,但最终从构造函数中删除了重置调用。也许测试它的困难意味着这个电话本来就不应该在那里。我的类在得到依赖项时会假定该依赖项已重置。@kalithlev在构造函数中进行大量设置工作可能会被视为有问题。也许您可以使用类似IsReset的属性来检查它?您也可以使用回调使用Moq来执行此操作,但这种方式更易于阅读。因此,您需要在初始化后重置字段?绕过构造函数中的工作的有趣方法,但这种方法存在一些问题:反射需要考虑继承,因为字段可能不在MyClass中;反射通常不利于重构,因此如果字段的名称被更改或删除,这会给测试增加摩擦。像这样的方法通常证明测试是在类构造之后编写的。@bryanbcook,是的,这绝对不是一个好方法。然而,要求是单独测试Clear方法。所以不管怎样都会有一些黑客。看看公认的解决方案。