在将团队转换为TDD以实现全面覆盖之后,编写所有可能的测试用例是一个好主意吗?


17

假设我们有一个大型企业级应用程序,而没有任何单元/功能测试。由于非常紧迫的期限,因此在开发过程中没有测试驱动的开发过程(我知道我们永远不能在不确定的情况下承诺任何紧迫的期限,但是已经完成了!)

既然所有的截止日期都过去了,事情变得平静了,每个人都同意将我们转变成一个富有成效的基于TDD / BDD的团队...是的!

现在的问题是关于我们已经拥有的代码:(1)停止大多数开发并从头开始编写所有可能的测试用例还是可以的,尽管一切都可以很好地进行(尽管!)。 ?或(2)最好等待一些不好的事情发生,然后在修复过程中编写新的单元测试,或者(3)甚至忘记以前的代码,而只为新代码编写单元测试,并将所有内容推迟到下一个主要重构中。

有几个好的,如相关的文章这一个。考虑到我们的时间非常有限,还有许多其他项目/作品正在等待我们,我仍然不确定是否值得为此进行投资。

注意:这个问题正在解释/想象开发团队中一个完全尴尬的情况。这与我或我的任何同事无关;这只是一个假想的情况。您可能会认为这种情况永远都不会发生,否则开发经理会造成这种混乱!但是无论如何,已经完成了。如果可能的话,请不要仅仅因为您认为这永远不会发生而投票。



6
您可能应该为下一个截止日期到来做准备,并且您不再被允许进行TDD。可能是通过告诉谁推动了最后一轮技术债务开发的人,为什么这不是一个好主意。
jonrsharpe

1
@gnat我认为这不是重复的问题。提到的团队没有任何测试(甚至没有集成测试)
Michel Gokan

1
@gnat的问题是:我们的新单元测试将发生什么?如果不为先前编写的代码编写所有单元测试,它们可能看起来不完整,甚至一文不值。您提到的问题未涵盖此特定问题。
米歇尔·高肯

1
不可能编写所有可能的测试用例。编写所有您关心的测试用例仅是有用的。例如,如果您需要一个将接受int值并返回特定值的函数,则不可能为每个可能的int值编写单元测试,但是测试一些有用的值可能会使功能失效,这很有意义。代码,例如负数(包括minint),零maxint等,以确保覆盖某些边缘情况。
Christopher Schultz '18

Answers:


36

由于非常紧迫的期限,在开发过程中没有测试驱动的开发过程

这句话非常令人担忧。不是因为这意味着您没有TDD进行开发,也不是因为您没有测试所有内容。这很令人担忧,因为它表明您认为TDD会使您放慢速度,并使您错过最后期限。

只要您以这种方式看到它,您就没有为TDD做准备。TDD并不是您可以逐渐缓解的问题。您要么知道如何做,要么不知道。如果您尝试进行到一半,您将使它变得很糟糕。

TDD是您首先应该在家中练习的东西。学习如何做,因为它现在可以帮助编码。不是因为有人告诉你这样做。并非如此,因为稍后您进行更改会有所帮助。当您因为急事要做某事时,就可以准备专业地做它了。

TDD是您可以在任何商店中执行的操作。您甚至不必上交测试代码。如果其他人不屑一顾,则可以自己保留。如果操作正确,即使没有其他人运行测试,这些测试也可以加快您的开发速度。

另一方面,如果其他人喜欢并运行您的测试,则应牢记即使在TDD商店中,签入测试也不是您的工作。这是为了创建经过验证的有效生产代码。如果碰巧是可测试的,请保持整洁。

如果您认为管理层必须相信TDD或您的其他编码人员必须支持您的测试,那么您就忽略了TDD为您做的最好的事情。它快速向您展示了您认为代码所执行的功能与实际执行的功能之间的区别。

如果您看不到它本身如何可以帮助您更快地完成截止日期,那么您就还没有准备好TDD上班。你需要在家练习。

就是说,当团队可以使用您的测试来帮助他们读取您的生产代码,以及管理层将购买功能强大的新TDD工具时,这是很好的。

在将团队转换为TDD之后编写所有可能的测试用例是一个好主意吗?

无论团队在做什么,编写所有可能的测试用例并不总是一个好主意。编写最有用的测试用例。100%的代码覆盖率是有代价的。不要仅仅因为很难做出判断就忽略收益递减的规律。

为有趣的业务逻辑节省测试能量。决策和执行政策的内容。测试一下。厌倦了显而易见的易于阅读的结构胶水代码,只需将东西连接在一起,几乎不需要那么糟糕的测试。

(1)即使一切都工作正常,但停止大多数开发并从一开始就开始编写整个可能的测试用例还是好的还是个好主意?要么

不,这是“让我们彻底重写”的想法。这破坏了来之不易的知识。不要要求管理层花时间编写测试。只是编写测试。一旦您知道自己在做什么,测试就不会让您慢下来。

(2)最好等待发生不好的事情,然后在修复过程中编写新的单元测试,或者

(3)甚至忘记了以前的代码,只为新代码编写单元测试,并将所有内容推迟到下一个主要重构。

我将以相同的方式回答2和3。当您出于任何原因更改代码时,如果可以参加测试,那将非常好。如果代码是旧代码,则当前不支持测试。这意味着在更改之前很难对其进行测试。好吧,由于无论如何都要对其进行更改,因此可以将其更改为可测试的东西并对其进行测试。

那是核选择。有风险。您无需测试即可进行更改。有一些创造性的技巧可以在更改旧代码之前对其进行测试。您正在寻找所谓的接缝,这些接缝可以在不更改代码的情况下更改代码的行为。您需要更改配置文件,构建文件。

迈克尔·费瑟斯(Michael Feathers)给了我们一本书:有效地使用旧版代码。读一读,您会发现不必烧掉所有旧的东西来制作新的东西。


37
“即使没有其他人运行这些测试,它们也可以加快您的开发速度。” -我发现这显然是错误的。这不是开始对此进行讨论的地方,但读者应记住,此处提出的观点并非一致。
马丁·巴

4
实际上,从长远来看,测试通常会增加您的开发速度,并且要使TDD真正有效,每个人都必须相信它,否则,您将花费团队的一半来修复被其他人破坏的测试。
hspandher

13
“您认为TDD会让您放慢脚步,使您错过最后期限。” 我认为可能这种情况。没有人使用TDD,因为他们希望TDD能够更快地满足他们的第一个截止日期。真正的好处(至少在我看来是这样)是未来测试所产生的持续收益,赶上回归并建立对安全实验的信心。我认为,这样做的好处超过了编写测试的前期成本,大多数人可能会同意,但是如果您必须紧迫紧迫的最后期限,那么您别无选择。
亚历山大-恢复莫妮卡

1
我觉得这类似于买房。如果您一次性付清了房款,那么您将节省很多利息,从长远来看,这将是非常好的。但是,如果您立即需要房子...则您被迫采取长期未达标的短期做法
亚历山大-恢复莫妮卡

3
如果测试和代码是并行开发的,那么TDD = can =可以提高性能,而功能却是开发人员可以想到的。代码审查会告诉您其他人是否认为代码正确。测试用例将告诉您是否正在实施包含在测试用例中的规范。否则,是的,TDD可能会拖累,尤其是在没有功能规范且测试编写者也在进行反向工程的情况下。
朱莉在奥斯汀,

22

停止大多数开发并从一开始就开始编写整个可能的测试用例还是好还是好主意?

给定旧版1代码,请在以下情况下编写单元测试:

  • 修复错误时
  • 重构时
  • 向现有代码添加新功能时

与单元测试一样有用,为现有的1个代码库创建完整的单元测试套件可能不是一个现实的想法。迫使您在紧迫的期限内交付的能力。他们没有让您有时间在开发时创建足够的单元测试。您认为他们会给您足够的时间为“有效程序”创建测试吗?

1 传统代码是没有单元测试的代码。这是传统代码的TDD定义。即使旧代码已新鲜交付(即使墨水还没有干),它也适用。


但是,那么我们针对新功能的新单元测试可能看起来不完整,甚至不丢失单元测试就一文不值。是不是
米歇尔·高坎

7
(1)不值得吗?当然不是。至少,他们会测试新功能。下次有人要修改此功能时,他们将重用许多现有测试。(2)不完整?也许吧,也许不是。如果您还创建了用于测试新功能所依赖的旧功能的单元测试,则这些测试对于实际应用而言可能已经足够完整。换句话说,请创建可渗透传统功能的其他单元测试。渗透到什么深度?这取决于计划的架构,可用资源和机构支持。
尼克·阿列克谢夫

“当您偶然发现需要时编写测试”的不利之处在于,最终风险会增加,因为最终开发人员将拼凑出由不同开发人员使用不同想法编写的测试。我并不是说这个答案是错误的,但是它确实需要坚定的手以保持测试质量和样式的统一。
扁平的

4
@更均匀提供错误的舒适感。我想要使​​生产代码易于阅读的测试。没有测试,看起来都一样。如果可以更轻松地理解生产代码的功能,我将原谅各种完全不同的测试框架。
candied_orange

2
@Flater我没有断言丑陋的生产代码。我认为测试的重点是使生产代码可读。我将很高兴接受各种折衷的测试,这些测试使生产代码更易于阅读。注意将均匀性本身作为目标。可读性为王。
candied_orange

12

以我的经验,测试并不需要总覆盖才能有所帮助。相反,随着覆盖范围的增加,您开始收获各种好处:

  • 覆盖率超过30%(又称几次集成测试):如果测试失败,则说明某些东西极度损坏(或者测试有些不稳定)。值得庆幸的是,测试迅速提醒您!但是发布仍然需要大量的手动测试。
  • 覆盖率超过90%(又称大多数组件都具有浅表单元测试):如果您的测试通过,则该软件可能大部分正常。未经测试的部分是边缘情况,这对于非关键软件很好。但是发布仍需要进行一些手动测试。
  • 功能/语句/分支/要求的覆盖面非常广:您正在实现TDD / BDD的梦想,而您的测试正是对软件功能的精确反映。您可以高度自信地进行重构,包括大规模的体系结构更改。如果测试通过,则您的软件即将发布。仅需要进行一些手动烟雾测试。

事实是,如果您不以BDD开头,那么您永远都不会到达那里,因为编码后进行测试所需的工作量过多。问题不是测试,而是更多的是要了解实际需求(而不是偶然的实现细节),并能够以一种既实用又易于测试的方式设计软件。当您首先编写测试或与代码一起编写时,这实际上是免费的。

由于新功能需要测试,但是测试需要设计更改,但是重构也需要测试,因此您会遇到一些麻烦。随着您的软件逐渐接近合理的覆盖范围,您必须在代码中出现新功能的部分中进行一些仔细的重构,以使新功能可测试。最初,这会使您放慢速度。但是,通过仅重构和测试需要新开发的部分,测试也可以集中在最需要它们的领域。稳定的代码无需测试即可继续:如果存在错误,则无论如何都必须进行更改。

当您尝试适应TDD时,比总项目覆盖率更好的指标是要更改的部分的测试覆盖率。尽管从一开始就测试所有受重构影响的代码部分是不可行的,但是这种覆盖率从一开始就应该很高。此外,您确实可以从被测组件中获得高测试覆盖率的大部分好处。那不是完美的,但仍然相当不错。

请注意,虽然单元测试似乎很常见,但是从最小的部分开始并不是测试遗留软件的合适策略。您将要从一次同时测试大量软件的集成测试开始。

例如,我发现从实际日志文件中提取集成测试用例很有用。当然,运行这样的测试会花费很多时间,这就是为什么您可能想要设置一个定期运行测试的自动服务器(例如,由提交触发的Jenkins服务器)的原因。与不定期运行测试相比,设置和维护这种服务器的成本非常小,前提是实际上可以快速修复所有测试失败。


“覆盖率超过90%(又称大多数组件具有浅层单元测试):如果您的测试通过了,则该软件可能大部分都很好。未经测试的部分是边缘情况,这对于非关键软件来说也很好。” 对于FWIW来说,这听起来有点不对劲,我宁愿拥有30%的覆盖率(主要由边缘情况组成),而不是90%的覆盖率完全由预期的路径行为组成(这对于手动测试人员而言很容易做到);我建议在编写测试时考虑“开箱即用”,并尽可能使它们基于手动发现的(异常)测试用例。
jrh

5

不要为现有代码编写测试。这不值得。

您所做的工作已经以完全非正式的方式进行了某种测试-您不断地手工进行尝试,人们进行了一些非自动化的测试,现在正在使用它。这意味着您不会发现很多错误

剩下的就是您没有想到的错误。但是这些正是您不希望为它们编写单元测试的那些,因此您可能仍然找不到。

另外,使用TDD的原因是让您在编写代码之前先考虑一下代码的确切要求。无论采用哪种方式,您都已经做到了。

同时,编写这些测试仍然和事先编写它们一样工作。这将花费大量时间,却收效甚微。

而且编写很多测试并且之间没有任何编码并且几乎找不到任何错误是非常无聊的。如果您开始这样做,那么TDD的新手会讨厌它。

简而言之,开发人员会讨厌它,而经理会认为它代价高昂,而发现的bug却很少。您将永远不会到达真正的TDD部分。

作为过程的正常部分,请在要更改的内容上使用它。


1
我强烈反对“不要为现有代码编写测试。这是不值得的”。如果代码正常运行,则可能只有测试规范。而且,如果代码正在维护中,则添加测试是确保看似无关的更改不会破坏那些起作用的功能的唯一方法。
朱莉在奥斯丁,

3
@JulieinAustin:另一方面,如果没有规范,您可能不知道代码应该做什么。而且,如果您还不知道代码应该做什么,那么您可能会编写无用的测试-或更糟糕的是,误导性测试会微妙地更改规格-现在就需要偶然和/或错误的行为。
cHao

2

测试是传达理解力的一种手段。

因此,仅针对您了解的内容编写测试应该是正确的。

您只能了解使用它时应该做的事情。

因此,仅针对正在使用的代码编写测试。

当您使用代码时,您将学习。

因此,编写和重写测试以捕获您所学的内容。

冲洗并重复。

让一个代码覆盖工具与您的测试一起运行,并且只接受不会减少覆盖范围的对主线的提交。最终,您将获得较高的覆盖率。

如果您有一段时间没有使用该代码,则需要制定业务决策。现在它很可能已经成为历史,以至于您的团队中没有人知道如何使用它。它可能具有过时的库/编译器/文档,这在几乎所有方面都是一个巨大的责任。

两种选择:

  1. 花时间阅读,学习,编写测试并进行重构。少量更改与频繁发布。
  2. 找到一种抛弃该软件的方法。无论如何,您都无法对其进行修改。

0

(1)即使一切都工作正常,但停止大多数开发并从一开始就开始编写整个可能的测试用例还是好的还是个好主意?
(2)最好等待发生不好的事情,然后在修复过程中编写新的单元测试

测试的主要目的之一是确保更改不会破坏任何内容。这是一个三步过程:

  1. 确认测试成功
  2. 让你改变
  3. 确认测试仍然成功

这意味着您实际更改某些东西之前需要进行工作测试。如果您选择第二条路径,则意味着您将不得不迫使开发人员在测试之前就编写测试。我强烈怀疑,当已经面对现实的变化时,开发人员将不会给予单元测试应有的重视。

因此,我建议将测试编写和更改制定工作分开进行,以避免开发人员牺牲一个测试质量和另一个测试质量。

即使一切都正常工作还可以吗?

只是要特别指出这一点,这是一个普遍的误解,即您仅在代码不起作用时才需要测试。您也需要在代码工作时进行测试,例如向某人证明[新近出现的错误]不是由于您的部分原因,因为测试仍在通过中。
确认一切正常仍可以像以前那样工作,这是测试的一个重要好处,当您隐含了在代码运行时不需要进行测试时,就无需进行测试。

(3)甚至忘记了以前的代码,只为新代码编写单元测试,并将所有内容推迟到下一个主要重构

理想情况下,所有现有源代码现在都应该进行单元测试。但是,有一个合理的论据认为,这样做所需的时间和精力(以及成本)与某些项目根本不相关。
例如,不再开发且不希望更改的应用程序(例如,客户端不再使用它,或者该客户端不再是客户端),您可以说不再测试此代码不再相关。

但是,画线的位置并不是那么清晰。这是公司在成本收益分析中需要考虑的问题。编写测试需要花费时间和精力,但是他们期望该应用程序有未来的发展吗?进行单元测试的收益是否超过编写它们的成本?

这不是您(作为开发人员)可以做出的决定。充其量,您可以估计在给定项目上实施测试所需的时间,并且由管理层决定是否有足够的期望来实际需要维护/开发该项目。

并将一切推迟到下一个重大重构

如果下一个主要重构是给定的,那么您确实确实需要编写测试。

但是在面临重大变化之前,请不要推迟。我的最初观点(没有结合编写测试和更新代码的观点)仍然存在,但是我想在此补充一点:您的开发人员目前对项目的了解程度要比六个月内花的时间更好。在其他项目上。利用开发人员已经热身的时间段,而无需弄清楚将来如何工作。


0

我的两分钱:

等待系统的重大技术升级,然后编写测试,然后在企业的支持下正式进行测试。

另外,假设您是SCRUM商店,您的工作量由容量表示,您可以将其中的一部分分配给单元测试,但是...

说您要回去编写测试是很幼稚的,您真正要做的是编写测试,重构,并在重构使代码更具可测试性之后编写更多测试,这就是最好开始的原因您已经知道要进行测试,并且...

对于原始作者而言,最好为他们先前编写的代码编写测试并对其进行重构,这并不理想,但是从经验来看,您希望重构使代码更好而不恶化。

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.