正确处理单元测试“复杂”应用程序服务
我有一个应用程序服务(即大型方法)负责协调多个业务对象之间的交互。从本质上说,它需要一个包含客户信息和发票的系统的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();
}
我的问题是我最终在这些测试中做了这么多设置,它变得很脆弱。这是否是正确的方法,如果是的话,在每个粒度测试中避免重复所有这些设置的指针是什么?
我对您的特定应用程序了解不多,因此我无法真正提出任何具体建议。也就是说,这种面向过程的测试听起来像是可以成为采用技术的好选择。对于Java,我熟悉ModelJUnit工具(公平披露:我曾被聘为该工具的开发人员),但似乎有一个与C#相关的工具NModel,以及一本附带的书籍Model-based Software Testing and Analysis in C#。我这样说的原因是,也许你可以在整个过程中随意地散步,把你的设置代码放在一个地方,让抽象测试代为你完成大部分的辛苦工作。
就我个人而言,我会尝试测试所有的代码片段,但不一定每个部分作为自己的测试。一项测试会检查具有有效帐户的发票是否已通过。第二个测试检查具有无效帐户的发票是否创建新帐户。当然,我会嘲笑dals,因此没有数据被添加到数据库中。这也允许嘲笑不应该采取任何操作的例外和情况(队列中没有东西,也许没有发票)。
我同意佩德罗。您的第一个示例测试(ImportService_ProcessQueuedData_CallsDataAccessLayer_ToLoadQueue)不是必需的,因为其他测试将隐式地测试LoadQueuedData是否被调用 - 如果不是,它们将没有数据可操作。您希望对每条路径都进行测试,但不需要为方法中的每行代码单独进行测试。如果通过条件分支有六条路径,则需要六个测试。
如果我想变得很花哨,我可能会考虑利用对象多态来减少if语句的数量并简化测试。例如,对于每个QueuedImportItemType,您可以拥有一个不同的“处理程序”,并将处理该类型项目的逻辑放在该处,而不是您的大型Process方法。分解迭代的逻辑以及如何处理每种类型的项目使得它们更易于单独测试。
谢谢吉安...你建议NModel B/C可能的输入条件的感知数量是巨大的?如果输入类型的组合更受限制,您是否仍然会推荐这种方法?我试图测试的整个过程实际上只有6个条件分支和已知数量的条件在这个if-then逻辑中发挥作用。再次感谢您的洞察力。 – nerdn 2010-07-22 12:44:40
它更像是一个连续的过程,在每一步都有可能出现错误情况,并且可能有通过这些行为的多种不同途径。这就是基于模型的测试真正闪耀的地方 - 您只需对模型中的所有状态进行编码,并让测试系统找出可能的路径。 – Gian 2010-07-22 13:02:11