我正在尝试行为驱动的开发,我发现自己在我写作时第二次猜测我的设计。这是我的第一个绿地项目,可能只是我缺乏经验。无论如何,这是我正在编写的类的简单规范。它是用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);
}
欢迎任何提示,建议或替代方案。
答案 0 :(得分:3)
当进行自上而下的开发时,发现自己使用大量的模拟是很常见的。你需要的东西不是那么自然你需要嘲笑它们。据说这确实感觉像接受水平测试。根据我的经验,BDD或Context / Specification在单元测试级别开始有点奇怪。在单元测试级别,我可能会做更多的事情......
when_adding_an_account should_use_account_service_to_create_new_account should_update_screen_with_new_account_details
您可能需要重新考虑对IAccount接口的使用。我个人坚持 通过域实体保持服务接口。但这更多的是个人偏好。
其他一些小建议......
_mockAccountService.Expect(mock => mock.Create()) .Return(_account);
public class MainPresenterSpec { // Protected variables for Mocks [SetUp] public void Setup() { // Setup Mocks } } [TestFixture] public class WhenUserAddsAccount : MainPresenterSpec { [Test] public void ShouldCreateNewAccount() { } }
public void AddAccount() { if (AccountService.AccountTypes.Count != 1) { // Do whatever you want here. throw a message? return; } IAccount account = AccountService.Create(); _view.Accounts.Add(account); }
答案 1 :(得分:2)
如果使用自动模拟容器(如RhinoAutoMocker(StructureMap的一部分)),测试生命支持会更加简单。您可以使用auto mocking容器来创建测试中的类,并询问它是否需要测试所需的依赖项。容器可能需要在构造函数中注入20个东西,但如果你只需要测试一个,你只需要要求那个。
using StructureMap.AutoMocking;
namespace Foo.Business.UnitTests
{
public class MainPresenterTests
{
public class When_asked_to_add_an_account
{
private IAccountService _accountService;
private IAccount _account;
private MainPresenter _mainPresenter;
[SetUp]
public void BeforeEachTest()
{
var mocker = new RhinoAutoMocker<MainPresenter>();
_mainPresenter = mocker.ClassUnderTest;
_accountService = mocker.Get<IAccountService>();
_account = MockRepository.GenerateStub<IAccount>();
}
[TearDown]
public void AfterEachTest()
{
_accountService.VerifyAllExpectations();
}
[Test]
public void Should_use_the_AccountService_to_create_an_account()
{
_accountService.Expect(x => x.Create()).Return(_account);
_mainPresenter.AddAccount();
}
}
}
}
在结构上我更喜欢在单词之间使用下划线而不是RunningThemAllTogether,因为我发现它更容易扫描。我还创建了一个为测试中的类命名的外部类,以及为测试中的方法命名的多个内部类。然后,测试方法允许您指定被测方法的行为。在NUnit中运行时,会为您提供如下的上下文:
Foo.Business.UnitTests.MainPresenterTest
When_asked_to_add_an_account
Should_use_the_AccountService_to_create_an_account
Should_add_the_Account_to_the_View
答案 2 :(得分:1)
对于有服务的演示者而言,这似乎是正确的模拟数量,应该提交一个帐户。
这似乎更像是接受测试而不是单元测试 - 也许如果你减少了断言的复杂性,你会发现一些较小的问题被嘲笑。
答案 3 :(得分:1)
是的,你的设计存在缺陷。你正在使用模拟:))
更严重的是,我同意之前的海报,他建议您的设计应该是分层的,这样每个层都可以单独测试。我认为原则上测试代码应该改变实际的生产代码是错误的 - 除非这可以自动且透明地完成代码编译以便调试或发布的方式。
这就像海森堡的不确定性原则 - 一旦你在那里有嘲讽,你的代码就会被改变,这会让人头疼,而且嘲笑本身也有可能引入或掩盖瑕疵。
如果你有干净的接口,我没有争吵实现一个简单的接口,模拟(或模拟)到另一个模块的未实现的接口。对于单元测试等,此模拟可以与模拟相同的方式使用。
答案 4 :(得分:0)
在创建演示者时,您可能希望使用MockContainers来摆脱所有模拟管理。它大大简化了单元测试。
答案 5 :(得分:0)
这没关系,但我希望在那里有一个IoC自动插孔容器。代码提示测试编写者在测试中手动(显式)切换模拟和真实对象,但不应该是这种情况,因为如果我们讨论单元测试(单元只是一个类),只是自动模拟所有其他类并使用模拟更简单。
我想说的是,如果你有一个同时使用mainView
和mockMainView
的测试类,你就没有严格意义上的单元测试 - 更像是集成测试。
答案 6 :(得分:0)
我认为,如果你发现自己需要嘲笑,那么你的设计是错误的。
组件应该是分层的。您可以单独构建和测试组件A.然后你构建并测试B + A.一旦开心,你就构建了C层并测试了C + B + A.
在您的情况下,您不需要“_mockAccountService”。如果您的真实AccountService已经过测试,那么就使用它。这样你就知道任何错误都存在于MainPresentor中,而不是模拟本身。
如果您的真实AccountService尚未经过测试,请停止。返回并执行您需要的操作以确保其正常工作。得到它可以真正依赖它,然后你就不需要模拟了。