TDD可以强制创建"虚假"​​依赖项

Mit*_*h A 4 c# oop tdd unit-testing dependency-injection

我在ASP.NET WebForms应用程序中使用了Model-View-Presenter的样板实现.My View有两个重要事件,一个表示用户已在域模型上填写了足够的字段以启动复制检查,另一个是常规的Save事件.我的伪代码看起来像这样:

public class ItemNewPresenter : PresenterBase<IItemNewView>
{
public IItemService Service { get; private set; }
public IItemNewView View { get; private set; }

public ItemNewPresenter(IItemService service, IItemNewView view)
{
    Service = service;
    View = view;
    View.OnSave += DoItemSave;
    View.OnItemIsDuplicateCheck+= DoItemIsDuplicateCheck;
}


private void DoItemIsDuplicateCheck(object sender, CheckItemDuplicateEventArgs e)
{
    CheckForItemDuplication(e.Item);
}

private void CheckForItemDuplication(Item item){

if (Service.IsDuplicateItem(item))
    {
        View.RedirectWithNotification(BuildItemUrl(item), "This item already exists");
    }
}
private void DoItemSave(object sender, SaveItemEventArgs e)
{
    DoItemIsDuplicateCheck(this, e.ToItemDuplicateEventArgs());
    Service.Save(e.Item);
}

}
Run Code Online (Sandbox Code Playgroud)

这是我的测试,用于确保从视图中引发OnItemIsDuplicateCheck时我的演示者行为正常:

[Test]
public void presenter_checking_for_existing_item_should_call_redirect_if_found()
{
    var service = new Mock<IItemService>();
    var view = new Mock<IItemNewView>();
    var presenter = new ItemNewPresenter (service.Object, view.Object);

    var onCheckExistingHandler = view.CreateEventHandler <CheckItemDuplicateEventArgs>();
    view.Object.OnExistingDenominatorCheck += onCheckExistingHandler;
    var eventArgs = new CheckItemDuplicateEventArgs();

    service.Setup(s => s.IsDuplicate(It.Is<CheckItemDuplicateEventArgs>(c => c.Equals(eventArgs)))).Returns(true);

    onCheckExistingHandler.Raise(eventArgs);

    view.Verify(v => v.RedirectWithNotification(It.IsAny<String>(), It.IsAny<string>()), Times.Once());
    service.Verify();
}
Run Code Online (Sandbox Code Playgroud)

为了保持一致性,我希望在View引发OnSave事件时触发相同的重复检查.我的问题是当我想要验证的一个方法(CheckForItemDuplication)在被测试的类上声明时,我应该如何编写测试.验证SUT上的方法调用(坏)的替代方法是编写包含大量重复代码的保存测试(我的所有模拟的设置和断言都将从上面的测试中复制),这也使得单元测试不那么集中.

   [Test]
    public void presenter_saving_item_should_check_for_dupe_and_save_if_not_one()    {
         //duplicate mocks/setups/asserts from duplicate check fixture
         //additional mocks/setups/asserts to test save logic
    }
Run Code Online (Sandbox Code Playgroud)

认为 TDD会建议将这个私有方法放到一个单独的类中,该类与我的Presenter合作并将通过DI注入.但是为我的Presenter添加另一个依赖项,看起来不值得成为一个独立的抽象*和*代表我的Presenter的内部实现细节似乎......好吧......疯了.我离开基地吗?必须有一些我可以应用的设计模式或重构,以避免将私有方法转换为依赖关系.

Mor*_*ten 5

我认为你陷入了TDD与信息隐藏之间永无止境的争论中,因为你接受注入可能是正确的事情(可能是这样),但也觉得外部互动不应该关心看似微不足道的事情.注射.

请不要因为我要说的话而对我的恶臭进行投票:-)

现在,当遇到这种困境时,有时我所做的就是提取函数,使用对象作为参数创建内部构造函数,而不使用公共构造函数.公共ctor通过新对象转发到内部,例如:

public class ClassThatUseInjection
{
    private readonly SomeClass _injectedClass;

    public ClassThatUseInjection(): this(new SomeClass()) {}

    internal ClassThatUseInjection(SomeClass injectedClass)
    {
        _injectedClass = injectedClass;
    }
}


public class SomeClass
{
    public object SomeProperty { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

因此,您可以使用外部的空构造函数,以及其他构造函数,以便在为testpurposes注入存根参数时使用.只要空构造函数只转发调用而没有任何自己的逻辑,你仍然可以测试它,就像它只有一个构造函数一样.

还是有点臭,是的,但不是臭臭的:-)或者您怎么看?

此致,莫滕