C# 对“复杂”应用程序服务进行单元测试的正确方法

C# 对“复杂”应用程序服务进行单元测试的正确方法,c#,unit-testing,testing,methods,C#,Unit Testing,Testing,Methods,我有一个应用服务,即大型方法,负责协调多个业务对象之间的交互。本质上,它从一个包含客户信息和发票的系统中获取DTO,然后根据各种业务规则将其转换并导入到不同的系统中 public void ProcessQueuedData() { var queuedItems = _importServiceDAL.LoadQueuedData(); foreach (var queuedItem in queuedItems) {

我有一个应用服务,即大型方法,负责协调多个业务对象之间的交互。本质上,它从一个包含客户信息和发票的系统中获取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
           }
       }
    }
对于单元测试,假设服务中的整个过程都应该在粒度逐步的基础上进行测试,这是否正确,类似于以下内容

导入服务\u进程队列数据\u调用数据访问层\u加载队列

ImportService_ProcessQueuedData_与QueuedItemToProccess_ChecksIfAccountExists

使用发票调用导入服务\u处理队列数据\u LTOCEATEACCOUNTIFONE无文本列表

下面是一个典型的测试:

    [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();
    }

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

我对您的特定应用不太了解,因此我无法提出任何具体建议。这就是说,这种面向过程的测试听起来可能是采用技术的一个很好的候选者。我熟悉工具公平披露:不久前,我被聘为Java这个工具的开发人员,但是似乎有一个等价的工具叫做C,还有一本附带的书。我这样说的原因是,也许您可以随机地遍历整个过程,将您的设置代码全部放在一个地方,并允许抽象测试生成为您完成大部分剩余的艰苦工作。

我个人会尝试测试所有代码片段,但不必将每个部分作为自己的测试。一个测试检查具有有效帐户的发票是否通过。第二个测试检查具有无效帐户的发票是否创建了新帐户。当然,我会模拟DAL,这样就不会向数据库中添加任何数据。这也允许模拟异常和不应执行任何操作的情况—队列中没有任何内容,或者可能没有发票。

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


<>如果我想得到真正的幻想,我也可以考虑利用对象多态性来减少IF语句的数量并简化测试。例如,您可以为每个QueuedImportItemType使用不同的处理程序,并将处理该类型项的逻辑放在那里,而不是放在大流程方法中。将迭代逻辑和对每种类型项目的处理分开,使它们更易于单独测试。

谢谢Gian。。。您是否建议NModel b/c感知到的可能输入条件数量巨大?如果输入类型的组合更加有限,您还会推荐这种方法吗?我尝试测试的整个过程实际上只有6个条件分支和已知数量的条件,这些条件在if-then逻辑中起作用。再次感谢您的见解。更重要的是,这似乎是一个连续的过程,每一步都有可能捕捉到错误条件,并且可能通过这些操作有多个不同的路径。这就是基于模型的测试真正闪耀的地方——您只需对模型中的所有状态进行编码,让测试系统找出可能的路径。