And*_*rew 28 c# unit-testing etl
我们对大部分业务逻辑进行单元测试,但仍然坚持如何最好地测试我们的一些大型服务任务和导入/导出例程.例如,考虑将工资单数据从一个系统导出到第三方系统.要以公司需要的格式导出数据,我们需要达到~40个表,这会产生一个创建测试数据和模拟依赖关系的噩梦.
例如,请考虑以下(~3500行导出代码的子集):
public void ExportPaychecks()
{
var pays = _pays.GetPaysForCurrentDate();
foreach (PayObject pay in pays)
{
WriteHeaderRow(pay);
if (pay.IsFirstCheck)
{
WriteDetailRowType1(pay);
}
}
}
private void WriteHeaderRow(PayObject pay)
{
//do lots more stuff
}
private void WriteDetailRowType1(PayObject pay)
{
//do lots more stuff
}
Run Code Online (Sandbox Code Playgroud)
我们在这个特定的导出类中只有一个公共方法 - ExportPaychecks().这真的是唯一一个对调用这个类的人有意义的行为......其他一切都是私有的(约80个私有函数).我们可以将它们公开用于测试,但是我们需要模拟它们来单独测试每个(即你不能在没有模拟WriteHeaderRow函数的情况下在真空中测试ExportPaychecks.这也是一个巨大的痛苦.
由于这是单个导出,对于单个供应商而言,将逻辑移入域中是没有意义的.逻辑在此特定类之外没有域重要性.作为测试,我们构建了接近100%代码覆盖率的单元测试......但是这需要将大量的测试数据输入到存根/模拟对象中,加上超过7000行代码,因为存根/模拟我们的许多依赖项.
作为HRIS软件的制造商,我们拥有数百种进出口产品.其他公司真的对这种类型的东西进行单元测试吗?如果是这样,是否有任何捷径可以减少痛苦?我很想说"没有单元测试导入/导出例程",只是稍后实现集成测试.
更新 - 感谢所有答案.我喜欢看到的一件事就是一个例子,因为我还没有看到有人可以将像大文件导出这样的东西变成一个易于测试的代码块,而不会把代码弄得一团糟.
Mar*_*ann 18
这种(尝试)单元测试的方式,你试图通过一个公共方法覆盖整个庞大的代码库总是让我想起外科医生,牙医或妇科医生通过小开口执行复杂的操作.可能,但不容易.
封装是面向对象设计中的一个古老概念,但是有些人认为封装是可测试性受到影响的极端情况.还有另一个称为开放/封闭原理的 OO原则,它更适合可测试性.封装仍然是有价值的,但不是在可扩展性为代价的-事实上,可测试性实际上只是打开/关闭原则另一个词.
我不是说你应该让你的私有方法公开,但我要说的是,你应该考虑重构你的应用程序到组合的部分-即合作,而不是一个大的很多小类别交易脚本.你可能会认为它没有多大意义,为解决单一供应商做到这一点,但现在你的痛苦,这是一个出路.
当您在复杂的API中拆分单个方法时经常会发生的事情是您还获得了很多额外的灵活性.最初的一次性项目可能会变成一个可重复使用的库.
下面是关于如何在手执行重构时对这个问题的一些想法:每个ETL程序必须执行至少以下三个步骤:
(因此,名称ETL).至于重构一个开始,这给我们至少有三个班级,不同的职责:Extractor,Transformer和Loader.现在,你有三个更有针对性的责任,而不是一个大班.没有什么比这更麻烦了,而且已经有点可测试了.
现在放大这三个区域中的每一个区域,看看你可以将责任分得更多.
如果您有很多"行"的源和目标数据,您可以在Mappers中为每个逻辑"行"等进一步拆分它.
它永远不需要变得混乱,并且额外的好处(除了自动化测试)是对象模型现在更灵活.如果您需要编写另一个涉及双方之一的ETL应用程序,您已经阅读了至少三分之一的代码.
关于重构的一些常见问题:
重构并不意味着你把你的3.5k LOC分成n部分.我不建议将80种方法中的一些公开或类似的东西.这更像是垂直切片代码:
因此,您根本不必编写覆盖整个3.5k LOC的单元测试.在一次测试中,只有一小部分被覆盖,并且您将有许多彼此独立的小测试.
编辑
这是一个很好的重构模式列表.其中一个显示了我的意图:分解条件.
在该示例中,某些表达式被分解为方法.不仅使代码更容易阅读,而且您还有机会对这些方法进行单元测试.
更好的是,您可以将此模式提升到更高的级别,并将这些表达式,算法,值等分解为方法,而不仅仅是方法,还要分解给自己的类.
你最初应该拥有的是集成测试.这些将测试函数是否按预期执行,您可以为此命中实际的数据库.
一旦你拥有了这个存储网络,你就可以开始重构代码,使其更易于维护并引入单元测试.
正如serbrech Workign提到的那样,有效地使用Legacy代码将帮助您永无止境,我强烈建议您阅读它,即使是绿地项目.
http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052
我要问的主要问题是代码经常变化多久?如果不经常尝试引入单元测试真的值得,如果它经常更换,那么我肯定会考虑清理一下.
这是嘲笑一切的概念失败的领域之一。当然,单独测试每个方法将是一种“更好”的做事方式,但是将制作所有方法的测试版本的工作量与将代码指向测试数据库的工作量进行比较(如果需要,在每个测试运行开始时重置) )。
这就是我在组件之间有很多复杂交互的代码中使用的方法,并且效果很好。由于每个测试将运行更多代码,因此您更有可能需要使用调试器单步执行以准确找到出错的位置,但您无需付出大量额外的努力即可获得单元测试的主要好处(知道出了问题) 。
| 归档时间: |
|
| 查看次数: |
2945 次 |
| 最近记录: |