C#,nUnit和Rhino Mocks,如果证明是适用的话.
当我尝试围绕一个复杂的函数包装测试时,我对TDD的追求仍在继续.假设我正在编写一个表单,当保存时,还必须保存表单中的依赖对象...形成问题的答案,附件(如果可用)和"日志"条目(例如"blahblah更新表单")或"blahblah附上了一个档案.").此保存功能还会根据在保存功能期间表单状态的更改方式向各种人发送电子邮件.
这意味着为了完全测试表单的save函数及其所有依赖项,我必须注入五到六个数据提供程序来测试这一个函数,并确保以正确的方式和顺序启动所有内容.在为表单对象编写多个链式构造函数以插入模拟的提供程序时,这很麻烦.我想我错过了一些东西,无论是重构方式还是更好的方式来设置模拟数据提供者.
我是否应该进一步研究重构方法以了解如何简化此功能?观察者模式是如何发声的,以便依赖对象检测父表单何时被保存并自行处理?我知道人们会说要拆分这个函数以便进行测试...这意味着我测试了每个依赖对象的各个保存函数,而不是表单本身的保存函数,它决定了每个函数应该如何保存自己.第一名?
首先,如果您正在关注TDD,那么您不会围绕复杂的函数进行测试.您可以围绕测试包装函数.实际上,即使这是不对的.您将测试和功能交织在一起,几乎完全同时编写,测试只是稍微提前一点.参见 TDD的三个定律.
当你遵循这三条法则并且都在努力进行重构时,你就永远不会遇到"复杂的功能".相反,你结束了许多经过测试的简单功能.
现在,谈谈你的观点.如果你已经有了"一个复杂的功能"而你想围绕它进行测试,那么你应该:
明确地添加你的模拟,而不是通过DI.(例如像'test'标志和'if'语句那样可怕的东西选择模拟而不是真实对象.
编写一些测试以涵盖组件的基本操作.
无情地重构,将复杂的功能分解成许多简单的小功能,同时尽可能多地运行拼凑测试.
将'test'标志尽可能高.在重构时,将数据源传递给小的简单函数.除了最顶层的功能之外,不要让'test'标志感染任何标志.
重写测试.在重构时,重写尽可能多的测试来调用简单的小函数而不是大的顶级函数.您可以将模拟传递给测试中的简单函数.
摆脱'测试'标志并确定你真正需要多少DI.由于您可以在较低级别编写可以通过参数插入模拟的测试,因此您可能不再需要在顶层模拟许多数据源.
毕竟,如果DI仍然很麻烦,那么考虑注入一个包含所有数据源引用的对象.注入一件事而不是多件事总是比较容易.
使用AutoMocking容器.有一个是为RhinoMocks编写的.
想象一下,你有一个通过构造函数注入注入大量依赖项的类.以下是使用RhinoMocks设置它的样子,没有AutoMocking容器:
private MockRepository _mocks; private BroadcastListViewPresenter _presenter; private IBroadcastListView _view; private IAddNewBroadcastEventBroker _addNewBroadcastEventBroker; private IBroadcastService _broadcastService; private IChannelService _channelService; private IDeviceService _deviceService; private IDialogFactory _dialogFactory; private IMessageBoxService _messageBoxService; private ITouchScreenService _touchScreenService; private IDeviceBroadcastFactory _deviceBroadcastFactory; private IFileBroadcastFactory _fileBroadcastFactory; private IBroadcastServiceCallback _broadcastServiceCallback; private IChannelServiceCallback _channelServiceCallback; [SetUp] public void SetUp() { _mocks = new MockRepository(); _view = _mocks.DynamicMock(); _addNewBroadcastEventBroker = _mocks.DynamicMock (); _broadcastService = _mocks.DynamicMock (); _channelService = _mocks.DynamicMock (); _deviceService = _mocks.DynamicMock (); _dialogFactory = _mocks.DynamicMock (); _messageBoxService = _mocks.DynamicMock (); _touchScreenService = _mocks.DynamicMock (); _deviceBroadcastFactory = _mocks.DynamicMock (); _fileBroadcastFactory = _mocks.DynamicMock (); _broadcastServiceCallback = _mocks.DynamicMock (); _channelServiceCallback = _mocks.DynamicMock (); _presenter = new BroadcastListViewPresenter( _addNewBroadcastEventBroker, _broadcastService, _channelService, _deviceService, _dialogFactory, _messageBoxService, _touchScreenService, _deviceBroadcastFactory, _fileBroadcastFactory, _broadcastServiceCallback, _channelServiceCallback); _presenter.View = _view; }
现在,这与AutoMocking容器的内容相同:
private MockRepository _mocks; private AutoMockingContainer _container; private BroadcastListViewPresenter _presenter; private IBroadcastListView _view; [SetUp] public void SetUp() { _mocks = new MockRepository(); _container = new AutoMockingContainer(_mocks); _container.Initialize(); _view = _mocks.DynamicMock(); _presenter = _container.Create (); _presenter.View = _view; }
更容易,是吗?
AutoMocking容器自动为构造函数中的每个依赖项创建模拟,您可以访问它们进行测试,如下所示:
using (_mocks.Record()) { _container.Get().Expect(cs => cs.ChannelIsBroadcasting(channel)).Return(false); _container.Get ().Expect(bs => bs.Start(8)); }
希望有所帮助.我知道随着AutoMocking容器的出现,我的测试寿命变得更加轻松.
你是对的,它可能很麻烦.
模拟方法的支持者会指出代码写得不正确.也就是说,您不应该在此方法中构造依赖对象.相反,注入API应具有创建适当对象的函数.
至于模拟6个不同的对象,这是真的.但是,如果您还对这些系统进行单元测试,则这些对象应该已经具有可以使用的模拟基础结构.
最后,使用一个模拟框架为您完成一些工作.
我没有你的代码,但我的第一反应是你的测试试图告诉你你的对象有太多的协作者.在这样的情况下,我总是发现那里有一个缺失的构造应该被打包成更高级别的结构.使用自动锁定容器只会掩盖您从测试中获得的反馈.有关详细讨论,请参阅http://www.mockobjects.com/2007/04/test-smell-bloated-constructor.html.