我正在开发一个用Java编写的旧项目。我们有超过1000万个LOC,更糟糕的是,有4000多个功能测试。
由哈德森(Hudson)安排的测试由于每次较大的代码更改而疯狂,但都失败了。验证测试失败-如果是产品或测试中的问题,则需要几个月的时间。我们无法删除旧测试,因为我们不知道它们在测试什么!
我们能做什么?如何进行如此大量的传统测试?
我正在开发一个用Java编写的旧项目。我们有超过1000万个LOC,更糟糕的是,有4000多个功能测试。
由哈德森(Hudson)安排的测试由于每次较大的代码更改而疯狂,但都失败了。验证测试失败-如果是产品或测试中的问题,则需要几个月的时间。我们无法删除旧测试,因为我们不知道它们在测试什么!
我们能做什么?如何进行如此大量的传统测试?
Answers:
丢下他们
我知道很难放弃显然需要付出很多努力的东西,但是测试对您没有用,它们正在对您不利。测试套件应该可以使您确信系统可以完成预期的工作。如果不这样做,那就是负债而不是资产。系统或测试是否有故障都没有关系-只要运行测试套件会发出大量错误,它就无法实现其目的。
你现在需要的是测试新套件与运行没有错误。这意味着它最初将几乎没有覆盖,实际上几乎没有覆盖。每次修复或花时间彻底了解系统知识时,您都会在测试中获得该知识。随着时间的流逝,这将产生一个新的安全网,您可以在将来建立它。试图修补一个旧的,难以理解的安全网是一个时间浪费,几乎是不值得的。
我什至提倡不要将测试从旧套件转移到新套件。当然,其中一些可能现在会成功,但这是因为他们正在准确地测试他们应该测试的东西,还是仅仅因为某些随机镜头总是能够击中目标?显然,您必须对可用的努力可以做什么和不能做什么进行务实,但是您不能妥协一个原则,即测试套件必须干净地运行才能完成其工作。
去修复测试。
您最大的错误是您允许测试失败,并且您显然忽略了一段时间。您拥有的不是“旧式测试”-您正在处理旧版代码。而且我认为编写的每个没有测试的代码都是遗留的。
验证测试失败-如果产品或测试存在问题,则需要花费数月的时间。我们无法删除旧测试,因为我们不知道它们在测试什么!
看起来您的组织中存在更大的问题,因为您没有明确的要求。我无法理解您(或其他人)无法确认正确的行为。
测试很有价值。至少,他们记录到有人认为他们应该花时间来编写它们,因此大概他们一次对某人有价值。幸运的是,它们将包含团队曾经研究过的所有功能和错误的完整记录,尽管它们也可能只是一种无需仔细考虑即可达到任意测试覆盖率的方法。除非您查看它们,否则您将不知道这是什么情况。
如果您的大多数测试大部分时间都通过了,那么就硬着头皮投入时间,弄清楚几个失败的测试正在尝试做的事情,并进行修复或改进,以便下次的工作会更容易。在这种情况下,请跳至“ 确定每个测试的意图”部分,以获取有关处理少量失败测试的一些建议。
另一方面,您现在可能会面临Red版本,数百个甚至数千个测试都没有通过一段时间,而Jenkins很久没有成为绿色了。此时,Jenkins的构建状态已变得无用,并且签入问题的关键指标不再起作用。您需要解决此问题,但是在整理客厅中的混乱情况时,您无力阻止所有前进的进度。
为了保持理智,同时执行必要的考古以确定可以从失败的测试中恢复什么价值,我建议执行以下步骤:
您可以通过多种方式来执行此操作,具体取决于您的环境,但您并未明确描述,因此我无法真正推荐任何特定的方式。
一些框架支持预期失败的概念。如果您这样做了,那就太好了,因为您将看到该类别中还剩下多少测试的倒计时,甚至还可以通知您其中一些测试是否意外通过。
一些框架支持测试组,并允许您告诉Hudson仅运行某些测试,或跳过一组测试。这意味着您偶尔可以手动运行测试组,以查看是否正在通过测试。
一些框架允许您注释或以其他方式标记要忽略的单个测试。在这种情况下,将它们作为一个团队来运行比较困难,但是这会阻止它们分散您的注意力。
您可能会将测试移至构建中通常不包含的源树。
在极端情况下,您可以从版本控制系统的HEAD删除代码,但这将使第三阶段完成时更难识别。
目标是让詹金斯(Jenkins)尽快进入绿色,以便您可以尽快朝正确的方向发展。
解决添加或修改代码时添加新测试的问题,并致力于使所有通过测试通过。
测试可能会由于各种原因而失败,包括它们并非一开始就编写得当的测试。但是,一旦让詹金斯(Jenkins)变得绿色,保持这种方式确实非常重要。
习惯于编写好的测试,如果测试开始失败,则要花很多时间。
一次通过禁用测试。从影响您最经常更改的模块的模块开始。确定测试的目的以及失败的原因。
它是否测试了故意从代码库中删除的功能?然后,您可以删除它。
是否正在捕获尚未有人注意到的错误?恢复测试并修复错误。
是否由于做出不必要的假设而失败(例如,假设按钮文本始终为英语,但是现在您已将应用程序本地化为多种语言)?然后找出如何使测试专注于单个事物,并将其与无关的更改尽可能地隔离开。
测试是否扩展到整个应用程序并代表系统测试?然后将其从您的主要Jenkins测试套件中删除,并将其添加到运行频率较低的回归套件中。
该应用程序的体系结构是否已发生了不可识别的变化,因此测试不再有用吗?删除它。
是否添加了测试以人为地增加代码覆盖率统计信息,但实际上无非是确认代码正确编译且不会进入无限循环?否则,测试仅确认您选择的模拟框架返回了您刚刚告诉它的结果?删除它。
结果,一些测试将继续进行,一些测试将被修改,一些将被分成多个独立的,小块大小的块,而另一些将被删除。只要您仍然在新要求方面取得进展,就留出一点时间来处理技术债务,这是负责任的事情。
4000次测试是一个棘手的问题。40次测试更容易处理。随机选择可管理数量的测试以运行和分析。将结果分类为:
如果很多测试属于第一类,那么可能是时候丢弃当前的测试套件,并为当前代码组合一个有用的套件了。
如果许多测试都以告诉您代码中的问题的方式失败,则您需要通过失败的测试来解决问题。您可能会发现修复一个或两个错误会使大量测试运行。
如果这句话是真的,
随着更大的代码更改,测试……像疯了一样失败了。
这意味着如果在“更大的代码更改”之前回滚到代码,则许多测试将再次通过。之后,获取一小部分更改,然后查看哪些测试最近失败。这将帮助您更好地隔离哪些代码更改导致哪些测试失败。对于每个测试,一旦找出问题所在,就应该能够确定新代码是否存在缺陷,或者测试是否存在缺陷。如果新代码有问题,请确保将其与最新版本进行比较,以防特定错误已得到修复。
重复直到获得最新的代码库。
这似乎是一项艰巨的任务,但是很可能一旦您走上这条道路并开始隔离某些问题,就会出现一种模式,这可能会大大加快这一过程。例如:
如果您不知道他们正在测试什么,请删除它们,直到您知道为止。测试是不稳定的事情,如果您删除不再需要的功能,则应该期望必须更改测试该功能的测试!因此,除非您知道测试在测试什么,否则您就没有希望在适当的位置更改代码库。
您可以在开发人员的计算机上设置测试系统并在其中运行,以便开发人员可以查看与测试交互的部分,希望提供缺少的文档,并更加熟悉可能未正确更改或没有更改的代码库不再正确测试。
简而言之-如果您的旧测试在更改时失败,那么您的代码更改就不好了。使用这些测试作为系统工作方式的教育手段。
@Ignore
注释的原因-您可以保留测试,但不能执行它们。然后,只需重新启用它们并一次修复它们即可。它使您可以将重点一次缩小到仅进行少量测试,而不会因成千上万次失败而感到不知所措。
我要做的最重要的事情是回到测试应该做的基础以及业务需要保持前进的基础。测试的工作是在问题变得昂贵以后加以修复之前确定问题。我认为那句话的关键词是“昂贵”。这些问题需要业务解决方案。现场是否出现昂贵的问题?如果是这样,则测试将彻底失败。
您的管理层和您需要进行现实检查。您会发现,由于进行了一系列的测试,开发成本正在飞涨。这些成本与由于禁用测试而交付有缺陷产品的成本相比如何?与实际找出用户需要哪些行为(应测试的事物)的繁重任务相比,它们又如何呢?
这些都是需要业务解决方案的问题,因为它们涉及到工作的业务方面。您正在向客户交付产品,这是企业非常感兴趣的边界。他们可能能够确定您作为开发人员无法解决的解决方案。例如,为他们提供两种产品可能是合理的:一种针对那些需要可靠性并愿意放弃新功能的人的“传统”产品,而一种“远见”的产品可能有更多的缺点,但仍处于领先地位。这将使您有机会开发两组独立的测试:一组旧的具有4000项测试,另一组包含您认为需要完成的更多测试(并记录它们,因此该过程不再重复)。
然后,艺术就开始了:如何管理这种两头野兽,以便一个分支的发展也可以帮助另一个分支?尽管有严格的测试要求,但对“普通”分支的更新又如何又流回“旧”分支。如果您最终合并了产品,那么在“旧版”分支上继续出现的客户请求又如何能更好地帮助您理解旧客户的需求?