我正在尝试对执行相当复杂操作的方法进行单元测试,但是我已经能够将这个操作分解为可模拟接口上的许多步骤,如下所示:
public class Foo
{
public Foo(IDependency1 dp1, IDependency2 dp2, IDependency3 dp3, IDependency4 dp4)
{
...
}
public IEnumerable<int> Frobnicate(IInput input)
{
var step1 = _dependency1.DoSomeWork(input);
var step2 = _dependency2.DoAdditionalWork(step1);
var step3 = _dependency3.DoEvenMoreWork(step2);
return _dependency4.DoFinalWork(step3);
}
private IDependency1 _dependency1;
private IDependency2 _dependency2;
private IDependency3 _dependency3;
private IDependency4 _dependency4;
}
我正在使用模拟框架(Rhino.Mocks)来生成模拟以进行测试,并且以此处所示的方式构造代码到目前为止非常有效。但是,如何在没有需要每个模拟对象和每次期望设置的大型测试的情况下对此方法进行单元测试?例如:
[Test]
public void FrobnicateDoesSomeWorkAndAdditionalWorkAndEvenMoreWorkAndFinalWorkAndReturnsResult()
{
var fakeInput = ...;
var step1 = ...;
var step2 = ...;
var step3 = ...;
var fakeOutput = ...;
MockRepository mocks = new MockRepository();
var mockDependency1 = mocks.CreateMock<IDependency1>();
Expect.Call(mockDependency1.DoSomeWork(fakeInput)).Return(step1);
var mockDependency2 = mocks.CreateMock<IDependency2>();
Expect.Call(mockDependency2.DoAdditionalWork(step1)).Return(step2);
var mockDependency3 = mocks.CreateMock<IDependency3>();
Expect.Call(mockDependency3.DoEvenMoreWork(step2)).Return(step3);
var mockDependency4 = mocks.CreateMock<IDependency4>();
Expect.Call(mockDependency4.DoFinalWork(step3)).Return(fakeOutput);
mocks.ReplayAll();
Foo foo = new Foo(mockDependency1, mockDependency2, mockDependency3, mockDependency4);
Assert.AreSame(fakeOutput, foo.Frobnicate(fakeInput));
mocks.VerifyAll();
}
这看起来非常脆弱。对Frobnicate实现的任何更改都会导致此测试失败(例如将步骤3分解为2个子步骤)。这是一种一体化的东西,因此尝试使用多个较小的测试是行不通的。它开始接近未来维护者的只写代码,下个月当我忘记它是如何工作的时候,我自己也会这样做。一定有更好的方法!正确?
答案 0 :(得分:5)
单独测试IDependencyX的每个实现。然后,您将知道该过程的每个步骤都是正确的。单独测试时,测试每个可能的输入和特殊条件。
然后使用IDependencyX的实际实现进行Foo的集成测试。然后您就会知道所有单个部件都已正确插入。通常只需要测试一个输入,因为您只测试简单的胶水代码。
答案 1 :(得分:1)
许多依赖关系表明代码中存在隐含的中间概念,因此可能会打包一些依赖关系并使这些代码变得更简单。
或者,也许您所拥有的是某种处理程序链。在这种情况下,您为链中的每个链接编写单元测试,并进行集成测试以确保它们完全匹配。
答案 2 :(得分:0)
BDD试图通过继承来解决这个问题。如果你习惯它,那么编写单元测试真的是一种更简洁的方法。
一些好的链接:
问题是BDD需要一段时间才能掌握。
从上一个链接(Steve Harman)中窃取的快速示例。注意每个测试方法只有一个断言。
using Skynet.Core
public class when_initializing_core_module
{
ISkynetMasterController _skynet;
public void establish_context()
{
//we'll stub it...you know...just in case
_skynet = new MockRepository.GenerateStub<ISkynetMasterController>();
_skynet.Initialize();
}
public void it_should_not_become_self_aware()
{
_skynet.AssertWasNotCalled(x => x.InitializeAutonomousExecutionMode());
}
public void it_should_default_to_human_friendly_mode()
{
_skynet.AssessHumans().ShouldEqual(RelationshipTypes.Friendly);
}
}
public class when_attempting_to_wage_war_on_humans
{
ISkynetMasterController _skynet;
public void establish_context()
{
_skynet = new MockRepository.GenerateStub<ISkynetMasterController>();
_skynet.Stub(x =>
x.DeployRobotArmy(TargetTypes.Humans)).Throws<OperationInvalidException>();
}
public void because()
{
_skynet.DeployRobotArmy(TargetTypes.Humans);
}
public void it_should_not_allow_the_operation_to_succeed()
{
_skynet.AssertWasThrown<OperationInvalidException>();
}
}
答案 3 :(得分:0)
依赖关系是否也依赖于彼此,必须按照确切的顺序调用它们?如果是这种情况,您实际上是在测试控制器流程,这不是单元测试的实际目的。
例如,如果您的代码示例是用于GPS的软件,则您不会测试实际功能,例如导航,计算正确的路线等,而是用户可以打开它,输入一些数据,显示路线和再次关闭它。看到区别?
专注于测试模块功能,让更高级别的程序或质量保证测试执行您在此示例中尝试执行的操作。