单元测试–成本与收益

下降趋势 我很长时间以来一直是单元测试的忠实拥护者。 我的博客到处都是关于它的文章 。 如果我看一下过去十年来我的项目中自动化的单元测试是如何进行的,我注意到进行单元测试意愿并不存在 。 最近,我遇到了马丁·福勒(Martin Fowler),肯特·贝克(Kent Beck)和DHH之间的播客系列 ,这是对DHH的TDD已死,万岁测试的回应。 播客并没有向我透露一些新内容,但它启发了我。 昨晚我花了很多时间来反思和思考我在项目中总体上如何进行TDD /单元测试,并且趋势是,我作为开发人员或高级开发人员甚至是建筑师的投入越来越大,要进行单元测试,但在上一个大型项目中,BUT单元测试(非TDD)效果不佳。 而且由于我承担了解决方案架构师的职责,并且像我的职责一样,我稍微远离了Code,所以我看到下一层并没有做太多的单元测试。

当我花时间思考可能是什么原因时,这就是Podcast系列提供了很多帮助的地方。 我可以归因于单元测试(不是TDD)的原因只是最近在项目上没有发生

  1. 编写自动单元测试并不像编码那样有趣
  2. 编写自动单元测试并不像编码那样有趣

您明白了! 我比较了当我在2004年第一次进行单元测试(我在IT咨询组织的第一个项目)以及2012年的最后一个项目时的案例,我知道为什么会这样。 我在2004年和2012年(相隔8年)进行单元测试的环境非常不同。 过去,我是企业/核心Java / .Net开发人员,我的工作是处理诸如进行数据库调用,编写服务(业务逻辑,验证,yada yada yada)之类的事情。 涉及编写用于Web层的自动测试用例的部分最少,因为进入Web控制器(我们使用的MVC框架多种)的代码仅是视图。 并且我们使用功能/系统测试方法来测试UI。 创建这些硒测试用例的责任不是QA功能,而是开发人员。 因此,整个测试是“有意义的” –我们在服务代码上进行了TDD,然后在前端进行了功能测试(后编码)。
但是最重​​要的是,作为开发人员,我应该确保在提交代码时没有任何问题。 CI工具在当时还不算先进,但在那里。 我们使用Ant进行构建,并且其中有一个任务负责运行测试用例,否则导致构建失败(我什至编写了ant构建脚本)。 我们无法集成Selenium测试运行器ant脚本,因此这是一个手动步骤,但是我们必须运行整个套件以确保一切正常。 存在“自测代码”现象,我们一直将其作为核心。 发生建筑中断的罚款地点-我们因中断而被罚款50印度卢比; 连续3次休息,价格上涨至500印度卢比; 引入的任何回归缺陷为20 INR(质量保证有自己的一套以保持我们的控制力)。 当我编写代码并提交代码时,我知道晚上没有人会打电话告诉我团队的其他成员都被困住了。 我在印度工作,我有一个团队在波士顿工作,我知道当我离开时和他们进来时,他们不会被关门,因为我搞砸了汇编或破坏了某些东西–他们可以继续工作并提高工作效率。 每个人都这样做,我们所有人都很高兴一群开发人员/团队。 我们没有持续交付,但是一旦完成工作,就进行了几轮质量检查测试,并且可以向客户进行演示。 外部测试覆盖率超过80%,我们拥有一套可靠的测试套件。 但是,该套件运行了2个小时(我很快就会谈到,因为这是使单元测试变得很愉快的一部分)。

那是最后一次,我看到工作流程非常好。 令人惊讶的是,这也是我第一次这样做。

快进2007-08; 我是一个由40人组成的团队(开发人员,质量检查人员,网站开发人员)的架构师,我们正在从事Web开发-一些繁重的Web开发。 它使用JBoss上的Backbase,JSF,通过休眠与MySQL通讯,所有内容都围绕Spring。 我们从项目开始,就像在我想进行全面的TDD(不仅仅是单元测试)之前。 我们开始了,但是很快(Sprint 1),开发人员反馈说单元测试太困难了,它花费的时间比编写代码要花费更多的时间。 我不是建筑师,而是制作了一份《黄金副本》作为团队使用的参考点。 因此反馈小组给我的建议毫无意义。 我让团队为获得另一个冲刺而奋斗,认为他们只需要时间来解决它,但是当它开始达到我们的速度点时,我被迫查看该单元测试部门的情况,而我所看到的震惊了灵魂脱离我。 自动化测试在那里是有形式的-测试确实测试了某些东西,它们提供了一定的覆盖范围,但是它们太可怕了。 他们到处都充满了嘲笑。 难怪团队花了这么多时间来编写测试用例。 对于需要测试的所有内容,他们必须创建一个模拟。 有点缺乏经验的地方是,团队没有创建帮助程序类来提供这些常见的模拟,而是尝试模拟诸如HTTPSession,HTTPRequest,MySQL逻辑的数据适配器之类的东西,也就是每个协作者都在那儿。 好吧,他们做了正确的事情(按照单元测试的定义)–测试单元本身,因此他们甚至嘲笑了对数据库进行调用的类。 测试功能变得非常困难。 我们Swift扭转了局面并制定了一些基本规则

  1. 我们不会使用模拟
  2. 如果发现无法避免模拟的情况,我们将不会为其编写自动化的单元测试

这意味着a)这不是最纯形式的单元测试(我什至不知道什么是最纯形式的单元测试,但是定义和我的前辈告诉我的是什么)和b)我们的覆盖面稍差一些。 但是,我对那些后果没事。 结果是,团队不需要编写仅用于测试的东西。 它使整个测试套件变慢了一点,因为它现在可以调用数据库了,但是数据库是本地的,所以它并没有那么慢-团队开始在单元测试上花费更少的时间,我们编写了更少的测试,并更好地覆盖了代码。 这不是单元测试,但我们确实实现了自测代码。 现在,我们的CI构建再次告诉我们一次提交是否破坏了其他内容。 乐趣又回到了游戏中,我们开始取得进步。 然后在2008年1月,我们决定退出Backbase,转而使用Flex,这不仅需要在前端进行重新设计,还需要在后端服务中进行重新设计。 当我们组成一个团队重新设计和重新实现服务时,他们得到了包含300个奇数测试的可靠测试套件。 该实现被丢弃,一些接口被更改。 我们仅修复测试套件(我们决定让我们的质量保证团队修复测试套件–需要质量保证人员来用Java编写代码/脚本,这导致了很多回退,但我赢得了这场战斗)接口部分,然后开始再次编写这些服务,而我们要做的就是使这300个测试用例通过。 轻风轻拂; 我们甚至不需要质量检查团队来测试我们的服务。 我们的测试套件足以做到这一点。

我们从未达到完美,但后来我得以实现目标。

快进2012-2013; 我是Adobe CQ 5.5项目的架构师,并且框架已更改。 没有我们正在编写的服务。 开发人员无法使用传统的MVC框架。 我看到了代码,并且JSP的怪异之处在于scriplet中的业务逻辑,单层Java类,该类完成了从获取数据,操作它们,验证输入,以非常流畅的格式(例如Maps(ValueMaps专门针对Sling实现)发送输出)进行的所有操作。 在所有这些之中,我们做出了一些艰难的设计决策,例如在JSP中没有脚本或业务逻辑。 因此,现在将有Java代码,但是我们不得不处理许多吊索和JCR依赖项。 我再次想做我以前做过的事-没有MOCKS / STUBS。 但是,那时,我尚无法使用能够做到这一点的框架。 他们存在,但那时周围的社区稀少,支持减少,现有的东西都行不通。 最后我决定做假人。 3个月了,这恰好是2008年的情况–没用。

模拟非常糟糕,我们可以更改逻辑,并且测试仍然可以通过。

发生了什么事,开发人员只是破坏了一切,而测试甚至没有测试状态或行为。 他们只是去报道。 这次的工具更好,我们有了声纳。 想法是编写一个测试用例,并使其通过并显示声纳覆盖的增加。 我们曾经使用过模仿,这在当时似乎是个好主意,但没有用。 我了解模拟,但我只是不明白。 我们设置一些状态,然后检查该状态–都有意义。 但是,当一切都来自协作者,并且您要测试的对象只是将数据聚合并将其组装到ValueObject(在我们的示例中为Map)时,测试该组装毫无意义。 这就像说开发人员可以编写以下错误代码一样。

outputData.put("athletes", athleteList);

好吧,如果开发人员无法正确使用一堆基本的Java语句,那么他们就没有业务编码。 让我们将他们送去接受训练,让自己摆脱痛苦的世界。

快进2014年,我不是解决方案架构师,也不是每个人都讲很多代码。 我喜欢编码,但这不是我的日常工作(我希望有一天可以将代码作为我的日常工作)。 但是,现在我看到团队为使甚至无需进行单元测试而付出的巨大努力。 环境与我在2012年时的环境相似,但是现在我有了解决方案,而且我认为没有Mocks可以解决此问题。 但是,斗争就在那里,开发人员不再觉得这很有趣–看起来像是负担。

在任何情况下,“成本与收益”论点都是有效的论证,而在我们的软件开发生命周期中,获得任何信心都太过困难,作为开发人员,需要依靠一些东西并知道我们在在下一次提交时不破坏系统是巨大的。 在我的开发生命周期中,我们可能仍然可以进行一些回归,但是随着我们达到应用程序成熟度,当我们开始达到稳定时,没有自测代码会使我们像猛mm象 (而不是大象)一样缓慢地移动。 在当前的营销世界中,我们有一些客户希望在接下来的2周内进行广告系列投放,因此,我们发布代码的速度不会太慢。 仅当您可以快速移动,实现连续交付或至少达到可以合理地将产品发布到生产中的水平时,您才可以与行业相关。 过去每6个月发布一次的日子已经过去; 或至少不在我经营的环境/市场中。

我们必须有自检代码,我们必须进行单元测试,我们必须有反馈回路,并且我们必须尽快有反馈回路。 不能回避。 让我们了解并拥抱或在​​几年内灭绝。

翻译自: https://www.javacodegeeks.com/2014/08/unit-testing-cost-vs-benefit.html