草庐IT

c# - 单元测试大块代码(映射、翻译等)

coder 2023-07-11 原文

我们对大部分业务逻辑进行了单元测试,但仍坚持如何最好地测试我们的一些大型服务任务和导入/导出例程。例如,考虑将工资单数据从一个系统导出到第 3 方系统。要以公司需要的格式导出数据,我们需要打 ~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
}

我们在这个特定的导出类中只有一个公共(public)方法 - ExportPaychecks()。对于调用此类的人来说,这确实是唯一有意义的操作……其他所有内容都是私有(private)的(约 80 个私有(private)函数)。我们可以将它们公开进行测试,但随后我们需要模拟它们以分别测试每个(即,如果不模拟 WriteHeaderRow 函数,您就无法在真空中测试 ExportPaychecks。这也是一个巨大的痛苦。

由于这是单个导出,对于单个供应商而言,将逻辑移至域中没有意义。该逻辑在这个特定类之外没有域意义。作为测试,我们构建了接近 100% 代码覆盖率的单元测试……但这需要将大量测试数据输入到 stub /模拟对象中,加上 7000 多行代码,因为 stub /模拟我们的许多依赖项.

作为 HRIS 软件的制造商,我们有数百种导出和进口产品。其他公司真的对这类事情进行单元测试吗?如果是这样,是否有任何捷径可以减轻痛苦?我有点想说“没有对导入/导出例程进行单元测试”,只是稍后实现集成测试。

更新 - 感谢大家的回答。我很想看到一个例子,因为我仍然没有看到有人可以将大文件导出之类的东西变成易于测试的代码块,而不会将代码弄得一团糟。

最佳答案

这种(尝试的)单元测试风格,您试图通过一个公共(public)方法覆盖整个庞大的代码库,这总是让我想起外科医生、牙医或妇科医生,他们通过小开口执行复杂的操作。可能,但并不容易。

封装 是面向对象设计中的一个古老概念,但有些人将其极端化,导致可测试性受到影响。还有另一个 OO 原则称为 Open/Closed Principle这更适合可测试性。封装仍然很有值(value),但不会以可扩展性为代价 - 事实上,testability is really just another word for the Open/Closed Principle .

我并不是说您应该公开您的私有(private)方法,但我的意思是您应该考虑将您的应用程序重构为可组合的部分 - 许多协作的小类而不是一个大类 Transaction Script .您可能认为针对单个供应商的解决方案这样做没有多大意义,但现在您正在受苦,这是一种出路。

当您在复杂的 API 中拆分单个方法时,通常会发生的情况是您还会获得很多额外的灵 active 。最初的一次性项目可能会变成可重用的库。


以下是关于如何对手头的问题执行重构的一些想法:每个 ETL 应用程序都必须执行至少这三个步骤:

  1. 从源头提取数据
  2. 转换数据
  3. 将数据加载到目的地

(因此,名称 ETL)。作为重构的开始,这给了我们至少三个具有不同职责的类:ExtractorTransformerLoader。现在,您拥有三个具有更有针对性职责的类,而不是一个大类。没有什么乱七八糟的,而且已经更容易测试了。

现在放大这三个领域中的每一个领域,看看您可以在哪些方面进一步分担责任。

  • 至少,您需要在内存中很好地表示源数据的每一“行”。如果源是关系数据库,您可能想使用 ORM,但如果不是,则需要对此类进行建模,以便它们正确保护每一行的不变量(例如,如果字段不可为空,则该类应保证这通过在尝试空值时抛出异常来实现)。此类类具有明确定义的用途,可以单独进行测试。
  • 目的地也是如此:您需要一个好的对象模型。
  • 如果在源头进行高级应用程序端过滤,您可以考虑使用 Specification 实现这些设计模式。这些往往也很容易测试。
  • 转换步骤是许多操作发生的地方,但现在您已经拥有源和目标的良好对象模型,转换可以由 映射器 执行 - 同样是可测试的类。

如果您有很多“行”的源数据和目标数据,您可以在映射器中为每个逻辑“行”等进一步拆分。

它永远不需要变得凌乱,而且额外的好处(除了自动化测试之外)是对象模型现在更加灵活。如果您需要编写涉及这两个方面之一的另一个 ETL 应用程序,您已经编写了至少三分之一的代码。

关于c# - 单元测试大块代码(映射、翻译等),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2076398/

有关c# - 单元测试大块代码(映射、翻译等)的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby - i18n Assets 管理/翻译 UI - 2

    我正在使用i18n从头开始​​构建一个多语言网络应用程序,虽然我自己可以处理一大堆yml文件,但我说的语言(非常)有限,最终我想寻求外部帮助帮助。我想知道这里是否有人在使用UI插件/gem(与django上的django-rosetta不同)来处理多个翻译器,其中一些翻译器不愿意或无法处理存储库中的100多个文件,处理语言数据。谢谢&问候,安德拉斯(如果您已经在ruby​​onrails-talk上遇到了这个问题,我们深表歉意) 最佳答案 有一个rails3branchofthetolkgem在github上。您可以通过在Gemfi

  3. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  4. ruby-on-rails - Rails 源代码 : initialize hash in a weird way? - 2

    在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has

  5. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  6. ruby - Ruby 的 Hash 在比较键时使用哪种相等性测试? - 2

    我有一个围绕一些对象的包装类,我想将这些对象用作散列中的键。包装对象和解包装对象应映射到相同的键。一个简单的例子是这样的:classAattr_reader:xdefinitialize(inner)@inner=innerenddefx;@inner.x;enddef==(other)@inner.x==other.xendenda=A.new(o)#oisjustanyobjectthatallowso.xb=A.new(o)h={a=>5}ph[a]#5ph[b]#nil,shouldbe5ph[o]#nil,shouldbe5我试过==、===、eq?并散列所有无济于事。

  7. ruby - RSpec - 使用测试替身作为 block 参数 - 2

    我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere

  8. ruby - Sinatra:运行 rspec 测试时记录噪音 - 2

    Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/

  9. ruby-on-rails - 迷你测试错误 : "NameError: uninitialized constant" - 2

    我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test

  10. ruby - 即使失败也继续进行多主机测试 - 2

    我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r

随机推荐