正确的单元测试“复杂”应用程序服务的方法

时间:2010-07-21 21:19:56

标签: c# unit-testing testing methods

我有一个应用程序服务(即大方法)负责协调多个业务对象之间的交互。从本质上讲,它需要一个系统中的DTO,其中包含客户信息和发票,并根据各种业务规则对其进行翻译并将其导入不同的系统。

public void ProcessQueuedData()
    {
       var queuedItems = _importServiceDAL.LoadQueuedData();

       foreach (var queuedItem in queuedItems)
       {
           var itemType = GetQueuedImportItemType(queuedItem);

           if (itemType == QueuedImportItemType.Invoice)
           {
               var account = _accountDAL.GetAccountByAssocNo(queuedItem.Assoc);
               int agentAccountID;

               if (!account.IsValid)
               {
                   agentAccountId = _accountDAL.AddAccount(queuedItem.Assoc);
               }
               else
               {
                   agentAccountId = account.AgentAccountID;
               }

               /// Do additional processing TBD
           }
       }
    }

对于单元测试,假设服务中的整个过程应该逐步进行测试是否正确,类似于以下内容?

ImportService_ProcessQueuedData_CallsDataAccessLayer_ToLoadQueue

ImportService_ProcessQueuedData_WithQueuedItemToProccess_ChecksIfAccountExists

ImportService_ProcessQueuedData_WithInvoice_CallsDALToCreateAccountIfOneDoesNotExist

这是一个典型的测试:

    [TestMethod()]
    public void ImportService_ProcessQueuedData_WithInvoice_CallsDALToCheckIfAgentAccountExists()
    {
        var accountDAL = MockRepository.GenerateStub<IAccountDAL>();
        var importServiceDAL = MockRepository.GenerateStub<IImportServiceDAL>();

        importServiceDAL.Stub(x => x.LoadQueuedData())
            .Return(GetQueuedImportItemsWithInvoice());

        accountDAL.Stub(x => x.GetAccountByAssocNo("FFFFF"))
            .IgnoreArguments()
            .Return(new Account() { AgentAccountId = 0 });

        var importSvc = new ImportService(accountDAL, importServiceDAL);

        importSvc.ProcessQueuedData();

        accountDAL.AssertWasCalled(a => a.GetAccountByAssocNo("FFFFF"), o => o.IgnoreArguments());
        accountDAL.VerifyAllExpectations();
    }

我的问题是,我最终在每个测试中做了这么多设置,它变得很脆弱。这是正确的方法,如果是这样的话,有哪些指针可以避免在每个粒度测试中重复所有这些设置?

3 个答案:

答案 0 :(得分:0)

我对您的特定应用程序了解不多,所以我无法提出任何具体建议。也就是说,这种面向过程的测试听起来似乎是使用Model-based testing技术的一个很好的候选者。我熟悉ModelJUnit工具(公平披露:我曾经是这个工具的开发人员),但是对于C#,似乎有一个名为NModel的等效工具,随书,Model-based Software Testing and Analysis in C#。我说这个的原因是,也许你可以随机浏览整个过程,将你的设置代码放在一个地方,并允许抽象测试生成为你完成剩下的大部分工作。

答案 1 :(得分:0)

我个人会尝试测试所有代码,但不一定每个部分都是自己的测试。一项测试检查具有有效帐户的发票是否通过。第二个测试检查具有无效帐户的发票是否创建了新帐户。当然我会模拟dals,所以没有数据添加到数据库。这也允许模拟异常和不应该采取任何操作的情况(队列中没有任何内容,或者也许没有发票。)

答案 2 :(得分:0)

我同意佩德罗的观点。您的第一个示例测试(ImportService_ProcessQueuedData_CallsDataAccessLayer_ToLoadQueue)不是必需的,因为其他测试将隐式测试LoadQueuedData被调用 - 如果不是,它们将没有数据可以操作。您希望对每个路径进行测试,但不需要对方法中的每行代码进行单独测试。如果通过条件分支有六条路径,则需要进行六次测试。

如果我想变得非常花哨,我可能还会考虑利用对象多态来减少if语句的数量并简化测试。例如,您可以为每个QueuedImportItemType使用不同的“处理程序”,并在那里放置用于处理该类型项的逻辑,而不是您的大型Process方法。分解迭代的逻辑以及如何处理每种类型的项目使得它们更容易单独测试。