这是一个糟糕的设计吗?
我正在尝试行为驱动开发,但我发现自己在编写设计时再次猜测我的设计。 这是我的第一个绿地项目,可能只是我缺乏经验。 无论如何,这是我正在编写的类的简单规范。 它是用 BDD 风格的 NUnit 编写的,而不是使用专用的行为驱动框架。 这是因为该项目以 .NET 2.0 为目标,而所有 BDD 框架似乎都已接受 .NET 3.5。
[TestFixture]
public class WhenUserAddsAccount
{
private DynamicMock _mockMainView;
private IMainView _mainView;
private DynamicMock _mockAccountService;
private IAccountService _accountService;
private DynamicMock _mockAccount;
private IAccount _account;
[SetUp]
public void Setup()
{
_mockMainView = new DynamicMock(typeof(IMainView));
_mainView = (IMainView) _mockMainView.MockInstance;
_mockAccountService = new DynamicMock(typeof(IAccountService));
_accountService = (IAccountService) _mockAccountService.MockInstance;
_mockAccount = new DynamicMock(typeof(IAccount));
_account = (IAccount)_mockAccount.MockInstance;
}
[Test]
public void ShouldCreateNewAccount()
{
_mockAccountService.ExpectAndReturn("Create", _account);
MainPresenter mainPresenter = new MainPresenter(_mainView, _accountService);
mainPresenter.AddAccount();
_mockAccountService.Verify();
}
}
MainPresenter 使用的接口还没有任何真正的实现。 AccountService 将负责创建新帐户。 IAccount 可以有多个实现定义为单独的插件。 在运行时,如果有多个帐户,系统将提示用户选择要创建的帐户类型。 否则,AccountService 将简单地创建一个帐户。
让我不安的事情之一是编写一个规范/测试需要多少次模拟。 这只是使用 BDD 的副作用还是我处理这件事的方式错误?
[更新]
这是 MainPresenter.AddAccount 的当前实现
public void AddAccount()
{
IAccount account;
if (AccountService.AccountTypes.Count == 1)
{
account = AccountService.Create();
}
_view.Accounts.Add(account);
}
欢迎任何提示、建议或替代方案。
I'm trying my hand at behavior driven development and I'm finding myself second guessing my design as I'm writing it. This is my first greenfield project and it may just be my lack of experience. Anyway, here's a simple spec for the class(s) I'm writing. It's written in NUnit in a BDD style instead of using a dedicated behavior driven framework. This is because the project targets .NET 2.0 and all of the BDD frameworks seem to have embraced .NET 3.5.
[TestFixture]
public class WhenUserAddsAccount
{
private DynamicMock _mockMainView;
private IMainView _mainView;
private DynamicMock _mockAccountService;
private IAccountService _accountService;
private DynamicMock _mockAccount;
private IAccount _account;
[SetUp]
public void Setup()
{
_mockMainView = new DynamicMock(typeof(IMainView));
_mainView = (IMainView) _mockMainView.MockInstance;
_mockAccountService = new DynamicMock(typeof(IAccountService));
_accountService = (IAccountService) _mockAccountService.MockInstance;
_mockAccount = new DynamicMock(typeof(IAccount));
_account = (IAccount)_mockAccount.MockInstance;
}
[Test]
public void ShouldCreateNewAccount()
{
_mockAccountService.ExpectAndReturn("Create", _account);
MainPresenter mainPresenter = new MainPresenter(_mainView, _accountService);
mainPresenter.AddAccount();
_mockAccountService.Verify();
}
}
None of the interfaces used by MainPresenter have any real implementations yet. AccountService will be responsible for creating new accounts. There can be multiple implementations of IAccount defined as separate plugins. At runtime, if there is more than one then the user will be prompted to choose which account type to create. Otherwise AccountService will simply create an account.
One of the things that has me uneasy is how many mocks are required just to write a single spec/test. Is this just a side effect of using BDD or am I going about this thing the wrong way?
[Update]
Here's the current implementation of MainPresenter.AddAccount
public void AddAccount()
{
IAccount account;
if (AccountService.AccountTypes.Count == 1)
{
account = AccountService.Create();
}
_view.Accounts.Add(account);
}
Any tips, suggestions or alternatives welcome.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
当进行自上而下的开发时,发现自己使用大量模拟是很常见的。 你需要的部分不存在,所以你自然需要嘲笑它们。 话虽如此,这确实感觉像是一个验收水平测试。 根据我的经验,BDD 或上下文/规范在单元测试级别开始变得有点奇怪。 在单元测试级别,我可能会做更多类似的事情...
您可能需要重新考虑 IAccount 接口的使用。 我个人坚持
通过域实体保留服务接口。 但这更多的是个人喜好。
其他一些小建议...
When doing top to down development it's quite common to find yourself using a lot of mocks. The pieces you need aren't there so naturally you need to mock them. With that said this does feel like an acceptance level test. In my experience BDD or Context/Specification starts to get a bit weird at the unit test level. At the unit test level I'd probably be doing something more along the lines of...
You may want to reconsider your usage of an interface for IAccount. I personally stick
with keeping interfaces for services over domain entities. But that's more of a personal preference.
A few other small suggestions...
如果您使用自动模拟容器,例如 RhinoAutoMocker(结构图)。 您使用自动模拟容器创建被测试的类,并向其询问测试所需的依赖项。 容器可能需要在构造函数中注入 20 个东西,但如果您只需要测试其中一个,则只需请求该一个即可。
从结构上讲,我更喜欢在单词之间使用下划线而不是 RunningThemAllTogether,因为我发现这样更容易扫描。 我还创建了一个以被测类命名的外部类和多个以被测方法命名的内部类。 然后,测试方法允许您指定被测方法的行为。 当在 NUnit 中运行时,这会给你一个如下的上下文:
The test life support is a lot simpler if you use an auto mocking container such as RhinoAutoMocker (part of StructureMap) . You use the auto mocking container to create the class under test and ask it for the dependencies you need for the test(s). The container might need to inject 20 things in the constructor but if you only need to test one you only have to ask for that one.
Structurally I prefer to use underscores between words instead of RunningThemAllTogether as I find it easier to scan. I also create an outer class named for the class under test and multiple inner classes named for the method under test. The test methods then allow you to specify the behaviors of the method under test. When run in NUnit this gives you a context like:
对于具有应该返还帐户的服务的演示者来说,这似乎是正确的模拟次数。
不过,这看起来更像是验收测试而不是单元测试 - 也许如果您降低断言复杂性,您会发现更少的关注点被嘲笑。
That seems like the correct number of mocks for a presenter with a service which is supposed to hand back an account.
This seems more like an acceptance test rather than a unit test, though - perhaps if you reduced your assertion complexity you would find a smaller set of concerns being mocked.
是的,你的设计有缺陷。 您正在使用模拟:)
更严重的是,我同意之前的海报,他建议您的设计应该分层,以便可以单独测试每一层。 我认为测试代码应该改变实际的生产代码在原则上是错误的——除非这可以自动且透明地完成,就像编译代码以进行调试或发布一样。
这就像海森堡不确定性原理 - 一旦你有了模拟,你的代码就会发生很大的变化,这会成为维护的难题,而且模拟本身有可能引入或掩盖错误。
如果您有干净的接口,那么我对实现一个模拟(或模拟)另一个模块的未实现接口的简单接口没有异议。 这种模拟可以像模拟一样用于单元测试等。
Yes, your design is flawed. You are using mocks :)
More seriously, I agree with the previous poster who suggests your design should be layered, so that each layer can be tested separately. I think it is wrong in principle that testing code should alter the actual production code -- unless this can be done automatically and transparently the way code can be compiled for debug or release.
It's like the Heisenberg uncertainty principle - once you have the mocks in there, your code is so altered it becomes a maintenance headache and the mocks themselves have the potential to introduce or mask bugs.
If you have clean interfaces, I have no quarrel with implementing a simple interface that simulates (or mocks) an unimplemented interface to another module. This simulation could be used in the same way mocking is, for unit testing etc.
您可能想要使用 MockContainers 为了摆脱所有的模拟管理,同时创建演示者。 它大大简化了单元测试。
You might want to use MockContainers in order to get rid of all the mock management, while creating the presenter. It simplifies unit tests a lot.
这没关系,但我希望那里有一个 IoC 自动模拟容器。 代码暗示测试编写者手动(明确)在测试中的模拟对象和真实对象之间切换,但事实不应该是这样,因为如果我们谈论的是单元测试(单元只是一个类),自动模拟所有其他类并使用模拟会更简单。
我想说的是,如果您有一个同时使用
mainView
和mockMainView
的测试类,那么您就没有严格意义上的单元测试这个词——更像是集成测试。This is okay, but I would expect an IoC automocking container in there somewhere. The code hints at the test writer manually (explicitly) switching between mocked and real objects in tests which should not be the case because if we are talking about a unit test (with unit being just one class), it's simpler to just auto-mock all other classes and use the mocks.
What I'm trying to say is that if you have a test class that uses both
mainView
andmockMainView
, you don't have a unit test in the strict sense of the word -- more like an integration test.我认为,如果您发现自己需要模拟,那么您的设计就是不正确的。
组件应该分层。 您单独构建和测试组件 A。 然后构建并测试 B+A。 一旦满意,您就构建 C 层并测试 C+B+A。
在您的情况下,您不需要“_mockAccountService”。 如果你的真实AccountService已经过测试,那么就使用它吧。 这样你就知道任何错误都在 MainPresentor 中,而不是在模拟本身中。
如果您的真实 AccountService 尚未经过测试,请停止。 返回并执行您需要的操作以确保其正常工作。 当你真正可以依赖它时,你就不再需要模拟了。
It is my opinion that if you find yourself needing mocks, your design is incorrect.
Components should be layered. You build and test components A in isolation. Then you build and test B+A. Once happy, you build layer C and test C+B+A.
In your case you shouldn't need a "_mockAccountService". If your real AccountService has been tested, then just use it. That way you know any bugs are in MainPresentor and not in the mock itself.
If your real AccountService hasn't been tested, stop. Go back and do what you need to ensure it is working correctly. Get it to the point where you can really depend on it, then you won't need the mock.