【问题标题】:How do I test a method that needs to be called to set up the object before the test is run using Rhino mocks?在使用 Rhino 模拟运行测试之前,如何测试需要调用以设置对象的方法?
【发布时间】:2009-08-21 10:38:39
【问题描述】:

我在测试这个场景时遇到了问题。

发票有两种状态 - 已完成和未完成 - 我想测试 Presenter.FinishInvoice() 方法调用 DAO.FinishInvoice() 然后调用 DAO.GetInvoice() 并使用结果设置 View.Invoice。问题是我首先需要调用 DAO.GetInvoice() 来完成发票,这是从 Presenter.InitializeView() 调用的(在另一个测试中测试)。

这是我的测试:

using (mocks.Record())
{
    SetupResult.For(view.Invoice).PropertyBehavior();
    SetupResult.For(DAO.GetInvoice(1)).Return(invoice);
    Expect.Call(DAO.FinishInvoice(1)).Return(true);
    Expect.Call(DAO.GetInvoice(1)).Return(invoice);
}
using (mocks.Playback())
{
    Presenter presenter = new Presenter(view, DAO);
    presenter.InitializeView(1);
    presenter.FinishInvoice();
}

当 InitializeView() 被调用时,DAO.GetInvoice() 将被调用并且 View.Invoice 被设置一次。它不是测试的一部分,但如果我不将 View.Invoice 设置为未完成的发票,则 FinishInvoice() 将失败,因此需要设置返回值。

对 DAO.GetInvoice() 的第二次调用是从 FinishInvoice() 调用的,测试的一部分。

如果我运行这个测试,DAO.GetInvoice(1); 会失败。预期 #1,实际 #0。我已经单步执行了代码,它在调用 FinishInvoice() 时确实调用了 DAO.GetInvoice(),所以它一定是我的测试代码有问题,而不是我的演示者代码。

如果我改变:

    SetupResult.For(DAO.GetInvoice(1)).Return(invoice);

到:

    Expect.Call(DAO.GetInvoice(1)).Return(invoice);

它可以工作,但这不应该是测试的一部分,因为它只是设置所需要的(但不能放在 SetUp 方法中,因为它不是所有测试都需要的)

我想我需要用 Expect.Call() 来做这不是一场灾难,但我想学习如何设置它。

【问题讨论】:

    标签: unit-testing rhino-mocks mvp


    【解决方案1】:

    由于您想测试您的 DAO 类的交互,您需要创建它as a mock and not as a stub。这意味着您不能为此使用 SetupResult。

    如果您不关心方法调用的顺序,您可以使用Repeat-syntax

    using (mocks.Record())
    {
        SetupResult.For(view.Invoice).PropertyBehavior();
        Expect.Call(DAO.FinishInvoice(1)).Return(true);
        Expect.Call(DAO.GetInvoice(1)).Return(invoice).Repeat.Any();
    }
    using (mocks.Playback())
    {
        Presenter presenter = new Presenter(view, DAO);
        presenter.InitializeView(1);
        presenter.FinishInvoice();
    }
    

    如果您确实关心方法调用的顺序,则必须使用 Ordered-syntax 明确指定每个期望:

    using (mocks.Record())
    {     
        SetupResult.For(view.Invoice).PropertyBehavior();
    
        using (mocks.Ordered())
        {  
           Expect.Call(DAO.GetInvoice(1)).Return(invoice);     
           Expect.Call(DAO.FinishInvoice(1)).Return(true);
           Expect.Call(DAO.GetInvoice(1)).Return(invoice);
        }
    }
    using (mocks.Playback())
    {
        Presenter presenter = new Presenter(view, DAO);
        presenter.InitializeView(1);
        presenter.FinishInvoice();
    }
    

    但是,如果您在自己的代码中调用 DAO.GetInvoice 两次,我会说这是代码异味,您可能应该考虑将其重构为一次调用。

    另外,这是AAA-syntax from 3.5 的样子:

    //Arrange
    DAO.Stub( x => x.GetInvoice(1) ).Return(true).Repeat.Any();
    
    //Act
    Presenter presenter = new Presenter(view, DAO);
    presenter.InitializeView(1);
    presenter.FinishInvoice();
    
    //Assert
    DAO.AssertWasCalled( x => x.FinishInvoice(1) );
    DAO.AssertWasCalled( x=> x.GetInvoice(1) );
    

    如您所见,这要好得多,您甚至可以将模拟用作模拟和存根。

    【讨论】:

    • GetInvoice() 被调用两次的原因是FinishInvoice() 调用了一个存储过程来更改发票,因此您需要重新获取它以显示更改。此外,这只是一个测试,并未显示两次调用 GetInvoice() 之间的时间线。您加载发票,查看它,更改它,完成它,然后重新加载它以查看完成它可能所做的更改。在我的测试代码中,视图和 dao 都是模拟 - 我不知道您不能在模拟上使用 SetupResult.For()。我会在这个主题上做更多的阅读。谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-04-11
    • 2014-08-15
    • 2020-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多