如何测试具有复杂数据交互的函数

3

目前,我正在开发一个系统,它执行相当多的报告功能,消耗许多不同的数据点并将它们转换成更大的,有时是扁平化的输出。我的应用程序大部分建立在存储库模式的变体之上。由于这个原因,我有一套用于测试场景的模拟存储库。我遇到的问题是,这些数据点之间的交互非常复杂,以至于维护“模拟数据”很快就成为了一场维护噩梦。以下是一个模拟示例:

public class SomeReportingEntity
{
private IProductRepo ProductRepo;
private IManagerRepo ManagerRepo;
private ILocationRepo LocationRepo;
private IOrdersService OrdersService;
private IEmployeeRepo EmployeeRepo;

public ReportingEntity(IProductRepo ipr, IManagerRepo imr, ILocationRepo ilr, IOrdersService ios, 
    IEmployeeRepo ier){
        //Load these to private vars...
}

    //This is the function that I want to test...
public SomeReportingEntity GetManagerSalesByRegionReport()
{
    //Make a complex join on all sub collections.  These
    //sub collections are all under test individually.
    var MangerSalesByRegionItems = From x in ProductRepo.CurrentProducts()
                              Join y in OrdersService.FutureOrders() On ...
                              Join z in EmployeeRepo.ActiveEmployees() On  ...
                              Join a in LocationRepo.GetAllRegions() On ...
                              Join b In ManagerRepo.GetActiveManagers On ...
                              Select new SomeReportingEntity() With { ... }

    return MangerSalesByRegionItems.ToList();       
}

无可否认,这只是一个非常假造的例子,但我想强调的基本思想是,我有几个要加入的存储库,并且需要创建许多测试以确保此复杂查询按预期执行。由于连接操作非常复杂,使得模拟数据非常难以保持一致 - 特别是当我不得不添加更多关联并测试其他点时。此外,我需要能够将特定记录状态输入到模拟中(例如缺少分配经理的员工),以验证查询是否适当处理这些情况。

所以这里是我的问题:

  1. 最佳方法是什么,可以“模拟”这些数据,以避免维护的噩梦?许多人建议构建支持此的内存数据库。
  2. 我真的在这里遭受架构问题吗?在报告方案中,我经常发现自己处于这种模式中,其中我将许多不相关的数据点合并成一个新的混合实体。随着Linq的出现,这很容易做到,并且具有高度的意图清晰度,但有时感觉有点作弊。
3个回答

1

首先,您需要创建一个集中式对象,该对象知道如何检索不同存储库的数据。由于这仅涉及报告,因此更容易,因为您不必担心更改跟踪。

从后勤角度考虑,我会考虑创建一个本地数据库来保存远程数据(使用代理定期更新)。这将消除调用远程服务并即时聚合其数据的某些问题。您还可以在开始时预处理一些数据。

当我使用存储库模式时,我将其与工作单元模式相结合。工作单元是为您完成所有繁重工作的人。理论上,您的UoW可以从多个服务中获取数据,并根据配置将其呈现给存储库。

对于测试,您可以使用InMemoryUnitOfWork在一个地方提供所有数据。


1
我一直在自己的数据密集型项目上工作。对我们有效的方法是使用存储库本身来填充对象,然后将它们序列化为XML。我们将XML文件拉入测试项目中,并将其用作自动化测试的起点。这很好,因为它确保您的模拟数据看起来像真实数据。
我们的测试通常是这样的...
var object1 = XmlUtil.LoadObject1("filename1");
var object2 = XmlUtil.LoadObject2("filename2");

var result = SomeConverter.Convert(object1, object2);

Assert("somevalue", result.Property1);

如果需要进行内联查找,您可以添加一个模拟存储库,提供相同级别的依赖注入。
这种方法的缺点是如果数据模式发生更改。 有时,如果数据模式已更改,则测试可能变得过时。 如果您的模式仍在大量变化中,则应将自动化测试保持较小,直到模式稳定下来。 在知道模式相对稳定之前,专注于单元测试。

-1

你必须确定你想要测试什么。

一种方法是假装你在使用TDD。假设你的GetManagerSalesByRegionReport方法不存在(或者实际上删除它)。你需要:

  1. 编写一个失败的单元测试。最简单的测试是什么:你可以调用该方法,并且当数据没有问题时不会抛出异常。
  2. 你需要创建一个空的方法,它应该返回void,因为你的测试不需要它返回任何东西。
  3. 现在你的测试应该通过了。
  4. 添加一个测试以确保返回适当类型的List,即使没有子存储库的数据。
  5. 你需要更改方法以返回你的列表类型,并将其更改为返回null。你的测试仍然会失败,所以将其更改为返回一个空的List,这样它就会通过。

还剩什么?这些是INNER连接,除非所有存储库都至少包含一行数据,否则您将不会收到任何数据。因此,请进行测试:创建一个测试,其中每个存储库都包含一行,并确保返回的列表包含适当数量的行。然后,测试每个返回行的适当属性。然后测试如果任何存储库不包含行,则不返回任何数据。

然后,也许测试一下如果某些存储库包含多行会发生什么。

然后,我不知道还有什么需要测试的了。


重点不在于要测试什么,而是如何进行测试。为了获得良好的测试结果,我需要创建几种不同类型的模拟数据,并且这些模拟数据是有关联的。我发现模拟实体之间的关系变得越来越复杂,很难维护。我想知道人们如何维护复杂的模拟数据以进行测试,或者我是否在这种方法上犯了架构错误。 - Nathan
我也遵循TDD并使用测试基础设施。结果是我的当前模拟数据是硬编码到适当类型的数组测试中的。我想把这些数组序列化成XML,然后在运行时读取XML。 - John Saunders

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接