编写现有代码的测试


68

假设其中一个程序比较大(例如,在C#中为900k SLOC),所有程序都已进行了注释/记录,内容井井有条,运作良好。整个代码库由一个不再在公司工作的高级开发人员编写。所有代码都可以按原样进行测试,并且在整个过程中都使用IoC -出于某些奇怪的原因,他们没有编写任何单元测试。现在,您的公司希望分支代码,并希望添加单元测试以检测更改何时破坏了核心功能。

  • 添加测试是一个好主意吗?
  • 如果是这样,一个人怎么会开始这样的事情?

编辑

好的,所以我没想到答案会得出相反的结论。无论如何,这个问题可能不在我的掌控之中。我也阅读了“重复的问题”,并且普遍的共识是“编写测试很好” ...是的,但是在这种特殊情况下不太有用。

在考虑为遗留系统编写测试时,我并不孤单。我将保留有关花费多少时间以及新测试发现问题的次数(以及没有发现问题的次数)的指标。我会回来,并从现在开始大约一年后用我的结果进行更新。

结论

因此,事实证明,基本上不可能将任何形式的正统测试都添加到现有代码中。代码正常工作后,您显然无法对测试进行红灯/绿灯测试,通常通常不清楚哪些行为对测试很重要,也不清楚不清楚从何处开始,当然也不清楚何时完成。真的,甚至问这个问题都错过了编写测试的重点。在大多数情况下,我发现使用TDD重写代码实际上比解密预期的功能和追溯添加单元测试要容易得多。解决问题或添加新功能时,情况有所不同,我认为现在是添加单元测试的时候了(如下所述)。最终,大多数代码都会被重写,通常比您预期的要早-我采用这种方法


10
添加测试不会破坏现有代码。
Dan Pichelman 2013年

30
@DanPichelman您从未经历过schroedinbug- “程序中的设计或实现错误只有在有人阅读源代码或以异常方式使用该程序时,才发现它永远都不会起作用,直到出现该错误时,程序才会立即出现停止为每个人工作,直到修复为止。”

8
@MichaelT现在您提到它,我想我已经看过其中的一两个。我的评论应该读为“添加测试通常不会破坏现有代码”。谢谢
Dan Pichelman 2013年

3
仅围绕要重构或更改的内容编写测试。
史蒂文·埃弗斯

3
请参阅《有效使用旧版代码》一书:amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/…,Michael Feathers建议在修改任何旧版代码之前编写测试。
斯卡拉布

Answers:


68

尽管测试是一个好主意,但最初的编码器是在他构建应用程序时构建的,以捕获他对代码应该如何工作以及可能会破坏的知识,然后将这些知识转移给你。

采用这种方法时,您很有可能编写出最不可能破坏的测试,并且错过了在构建应用程序时会发现的大多数边缘情况。

问题在于,大多数价值将来自那些“陷阱”和不太明显的情况。没有这些测试,测试套件实际上将失去其所有效力。此外,该公司在其应用程序周围会产生错误的安全感,因为它不会提供更多的回归证明。

通常,处理这种类型的代码库的方法是为新代码编写测试,并为旧代码的重构编写测试,直到完全重构旧版代码库为止。

请参阅


11
但是,即使没有TDD,重构时的单元测试也很有用。
pdr

1
如果代码可以继续正常工作,那不是问题,但是最好的办法是在编写任何依赖于旧代码行为的代码时测试旧代码的接口。
deworde

1
这就是您测量测试覆盖率的原因。如果针对特定部分的测试未涵盖所有的if,else和所有边缘情况,那么您将无法安全地重构该部分。Coverage会告诉您是否所有行都已命中,因此您的目标是在重构之前尽可能增加覆盖率。
Rudolf Olah

3
TDD的主要缺点是,尽管套件可以运行,但对于不熟悉代码库的开发人员来说,这是一种错误的安全感。BDD在这方面要好得多,因为输出是纯英语代码的意图
罗比·迪

3
就像提到100%的代码覆盖率并不意味着您的代码在100%的时间内正常工作。您可以测试方法的每一行代码,但是仅仅因为它可以与value1一起使用并不意味着它可以保证与value2一起使用。
ryanzec

35

是的,添加测试绝对是个好主意。

您说它有据可查,并且位置优越。尝试使用该文档作为指南来创建测试,重点放在系统的关键部分或经常更改的部分上。

最初,与少量测试相比,代码库的庞大规模看起来似乎是压倒性的,但是没有大爆炸的方法,而在某个地方做一个开始比为最好的方法苦恼更重要。

我建议迈克尔·费瑟斯(Michael Feathers)的书《有效地使用旧版代码》,以获得一些很好的详细建议。


10
+1。如果您根据文档编写测试,则会发现工作代码与文档之间的任何差异,这是无价的。
卡尔·曼纳斯特

1
添加测试的另一个原因:发现错误后,您可以轻松添加测试用例以用于将来的回归测试。

基本上,这种策略是edX在线课程CS169.2x中关于遗留代码的章节中概述的策略。就像老师说的那样:这本书第9章中的“通过特征测试建立基础真相”:beta.saasbook.info/table-of-contents
FGM

21

并非所有单元测试都具有同等的优势。单元测试失败时会带来好处。失败的可能性越小,受益就越少。与在生产中经过良好测试的很少更改的代码相比,新的或最近更改的代码更有可能包含错误。因此,对新的或最近更改的代码进行单元测试更有可能更有益。

并非所有单元测试的成本都相等。对您今天自己设计的琐碎代码进行单元测试要比别人很久以前设计的复杂代码容易得多。另外,在开发过程中进行测试通常可以节省开发时间。在遗留代码上,不再节省成本。

在理想的世界中,您将一直有时间对遗留代码进行单元测试,但是在现实世界中,在某些情况下,有理由认为向遗留代码添加单元测试的成本将超过收益。诀窍是确定这一点。您的版本控制可以通过向您显示最近更改和最频繁更改的代码来提供帮助,您可以首先对它们进行单元测试。另外,当您进行更改时,请将这些更改和紧密相关的代码置于单元测试中。

按照这种方法,最终您将在最有利的领域获得不错的覆盖。如果您改为花费数月的时间在恢复创收活动之前进行单元测试,这可能是理想的软件维护决策,但这是一个糟糕的业务决策。


3
如果代码很少失败,则可能会花费大量时间和精力查找在现实生活中永远不会发生的神秘问题。另一方面,如果代码有错误且容易出错,则您可能会开始在任何地方进行测试并立即发现问题。
罗比·迪

8

添加测试是一个好主意吗?

绝对可以,尽管我很难相信该代码干净,运行良好,使用现代技术并且根本没有单元测试。您确定他们没有坐在单独的解决方案中吗?

无论如何,如果您要扩展/维护代码,那么真正的单元测试对于该过程而言是无价的。

如果是这样,一个人怎么会开始这样的事情?

一步一步来。如果您不熟悉单元测试,请学习一些。熟悉了这些概念之后,请选择一小段代码并为此编写测试。然后是下一个,然后是下一个。代码覆盖率可以帮助您找到您错过的景点。

最好先选择危险/危险/重要的东西进行测试,但是您可能更直接地测试某些东西以便更有效地进行测试-特别是如果您/团队不习惯代码库和/或单元测试。


7
“您确定他们没有坐在单独的解决方案中吗?” 是个好问题。我希望OP不要忽视它。
丹·皮切尔曼

不幸的是,这个应用程序是在TDD越来越受到关注的时候才启动的,所以目的是在某个时候进行测试,但是由于某种原因,一旦项目开始,他们就没有去做。
保罗

2
他们可能从来没有这样做,因为这会花费他们额外的时间,这是不值得的。一个好的开发人员独自工作肯定可以创建一个干净,清晰,组织良好且工作量较大的应用程序,而无需任何自动化测试,并且通常他们可以更快地完成它,并且与测试一样没有错误。由于全部由他们自己决定,因此与多个开发人员创建它相比,错误或组织问题的可能性大大降低。
李李

3

是的,进行测试是个好主意。它们将帮助记录现有代码库的预期工作,并捕获任何意外行为。即使测试最初失败,也可以让它们运行,然后在以后重构代码,以便它们通过并按预期运行。

开始为较小的类(没有依赖性且相对简单的类)编写测试,然后继续进行较大的类(具有依赖性且较复杂的类)。这将花费很长时间,但要耐心且持久,以便最终可以尽可能多地覆盖代码库。


您是否真的会向运行良好的程序添加失败的测试(OP表示)?
MarkJ 2013年

是的,因为它表明某些操作不符合预期,需要进一步检查。这将促进讨论,​​并有望纠正任何误解或以前未知的缺陷。
伯纳德

@Bernard-否则,测试可能会暴露您对代码应做的误解。在事实之后编写的测试冒着无法正确封装原始意图的风险。
Dan Pichelman 2013年

@DanPichelman:同意,但这完全不应该阻止任何人编写任何测试。
伯纳德

如果没有其他说明,则表明该代码尚未防御性地编写。
罗比·迪

3

好,我要提出相反的意见。

向现有的工作系统中添加测试将改变该系统,除非那是从一开始就考虑到系统编写的全部内容。我对此表示怀疑,尽管它很可能将所有组件很好地分隔开来,并且边界易于定义,您可以将模拟接口插入其中。但是,如果不是这样,那么您将不得不做出相当重要的更改(相对而言),这些更改很可能会破坏事情。在最好的情况下,您将花费大量时间编写这些测试,而花费大量时间来编写大量详细的设计文档,影响分析文档或解决方案配置文档可能会更好。毕竟,这就是老板要完成的工作,而不是单元测试。是不是

无论如何,我不会添加任何单元测试。

我将专注于外部的自动化测试工具,这些工具将为您提供合理的覆盖范围而无需进行任何更改。然后,当您进行修改时...那就是您可以开始在代码库中添加单元测试的时候了。


2

我经常遇到这种情况,在没有足够的测试覆盖或没有测试覆盖的情况下继承了很大的代码库,现在我负责添加功能,修复错误等。

我的建议是确保并测试添加的内容,如果您修复了错误或更改了当前代码中的用例,请进行作者测试。如果您必须触摸某些东西,请在此时编写测试。

当这种情况发生时,就是现有代码的结构不适合单元测试,因此您需要花费大量时间进行重构,以便可以为较小的更改添加测试。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.