模拟对象引入了一种对某些程序单元进行深度行为测试的好方法。您只需将模拟的依赖项传递给被测单元并检查它是否像应做的那样与依赖项一起工作。
让你有2个A类和B类:
public class A
{
private B b;
public A(B b)
{
this.b = b;
}
public void DoSomething()
{
b.PerformSomeAction();
if(b.State == some special value)
{
b.PerformAnotherAction();
}
}
}
public class B
{
public BState State { get; private set; }
public void PerformSomeAction()
{
//some actions
State = some special value;
}
public void PerformAnotherAction()
{
if(State != some special value)
{
fail(); //for example throw new InvalidOperationException();
}
}
}
想象一下 B 类正在使用单元测试 TestB 进行测试。
要对类 A 进行单元测试,我们可以将 B 传递给它的构造函数(进行基于状态的测试)或将 B 的模拟传递给它(进行基于行为的测试)。
假设我们选择了第二种方法(例如,我们不能直接验证 A 的状态而可以间接验证)并创建了单元测试 TestA(不包含对 B 的任何引用)。
所以我们将引入一个接口 IDependency 和类将如下所示:
public interface IDependency
{
void PerformSomeAction();
void PerformAnotherAction();
}
public class A
{
private IDependency d;
public A(IDependency d)
{
this.d = d;
}
public void DoSomething()
{
d.PerformSomeAction();
if(d.State == some special value)
{
d.PerformAnotherAction();
}
}
}
public class B : IDependency
{
public BState State { get; private set; }
public void PerformSomeAction()
{
//some actions
State = some special value;
}
public void PerformAnotherAction()
{
if(State != some special value)
{
fail(); //for example throw new InvalidOperationException();
}
}
}
和单元测试 TestB 类似于:
[TestClass]
public class TestB
{
[TestMethod]
public void ShouldPerformAnotherActionWhenDependencyReturnsSomeSpecialValue()
{
var d = CreateDependencyMockSuchThatItReturnsSomeSpecialValue();
var a = CreateA(d.Object);
a.DoSomething();
AssertSomeActionWasPerformedForDependency(d);
}
[TestMethod]
public void ShouldNotPerformAnotherActionWhenDependencyReturnsSomeNormalValue()
{
var d = CreateDependencyMockSuchThatItReturnsSomeNormalValue();
var a = CreateA(d.Object);
a.DoSomething();
AssertSomeActionWasNotPerformedForDependency(d);
}
}
好的。这对开发人员来说是一个快乐的时刻 - 一切都经过测试,所有测试都是绿色的。万事皆安。
但!
当有人修改 B 类的逻辑时(例如将 if(State != some special value) 修改为 if(State != another value) )只有 TestB 失败。
这家伙修复了这个测试,并认为一切都恢复正常了。
但是,如果您尝试将 B 传递给 A 的构造函数,A.DoSomething 将会失败。
它的根本原因是我们的模拟对象。它修复了 B 对象的旧行为。当 B 改变其行为时,模拟并没有反映出来。
所以,我的问题是如何让 B 的模拟跟随 B 的行为变化?