我的任务是为现有应用程序编写单元测试。在完成第一个文件后,我有717行测试代码与419行原始代码。

我对单元测试的理解是测试类中的每个方法,以确保每个方法都能按预期工作。但是,在请求请求中,我的技术负责人指出我应该专注于更高级别的测试。他建议测试该类中最常用的4-5个用例,而不是详尽地测试每个功能。

我相信技术负责人的评论。他比我拥有更多的经验,并且在设计软件时具有更好的直觉。但是,多人团队如何针对这种含糊的标准编写测试;也就是说,我怎么知道我的同龄人和我对“最常见的用例”有相同的想法? 50%,我们知道那50%中的100%被覆盖。否则,为每个文件的一部分编写测试将留出很大的作弊空间。

评论

这取决于。您是在打井字游戏,还是在编写代码来管理核反应堆?

有了足够多的单元测试,您就可以检测到奇特的硬件实现问题,例如Pentium FDIV错误或加密原语中的相关性,因此似乎没有任何进一步的单元测试有用的硬限制。仅在价格太昂贵时实际限制。

更高级别的测试将为您提供更好的实际覆盖范围。实际覆盖范围是指在系统的常规使用期间更可能发生这种情况。那就是您要首先实现的覆盖范围。在最后达到的50%中,YAGNI或死代码一旦被删除也将有助于增加总体覆盖率。

如果测试过多(目前似乎还没有),则最有可能的问题是所测试的代码执行过多。因此,不遵守单一责任。如果对代码进行了很好的划分,则测试也不会造成太大的负担。如果上课太多,会有很多副作用等,那将成为一场噩梦。

sqlite测试文档非常有趣:sqlite.org/testing.html。 Quote:“ SQLite库包含大约122.9 KSLOC的C代码。相比之下,该项目具有745倍的测试代码和测试脚本-91596.1 KSLOC。”

#1 楼

是的,覆盖率100%,您将编写一些不需要的测试。不幸的是,确定不需要的测试的唯一可靠方法是编写所有测试,然后等待10年左右,看看哪些测试从未失败。

维护很多测试并不是通常有问题。许多团队都在100%单元测试覆盖率的基础上进行了自动集成和系统测试。

但是,您还没有进入测试维护阶段,而是在追赶。在50%的测试覆盖率下让100%的班级比在100%的测试覆盖率下让50%的班级要好得多,而且您的潜在客户似乎正试图让您分配时间。有了该基准之后,下一步通常是将100%的文件向前推送。

评论


感谢您的回答。这有助于我理解我的问题并解决了真正的问题-我的态度! +1

–user2954463
17年5月3日,19:27

@astra你的态度还不错。质疑为什么很好。要回答另一个问题,一个很好的问题是:“我怎么知道我的同龄人和我对“最常见的用例”有相同的想法?您可以让他们查看测试。代码审查测试很少浪费时间,尽管我倾向于在码头而不是会议室

–candied_orange
17年5月3日在21:37

十年内从未失败的测试甚至不能保证它是不必要的,最终可能会在第11年失败。

–法老王
17年5月4日13:30

务实地,您可以采取相反的方法。编写您认为涵盖常见情况的测试。但是,每当您遇到故障时,都要编写测试覆盖该区域。

–stantanus
17年5月4日在17:49

@Pharap这个答案的唯一问题是隐含的假设,即测试仅在失败时才能增加价值。良好的单元测试还提供了丰富的生活文档。当您编写测试时,它还通过迫使您考虑可重用性/可组合性/封装性来增加价值。根据我的经验,未经测试的代码往往是不灵活的整体野兽。

– ArT
17年5月5日在1:51



#2 楼

如果您使用的是使用“测试驱动开发”创建的大型代码库,那么您已经知道可能存在太多的单元测试。在某些情况下,大多数开发工作包括更新低质量测试,最好在运行时将其作为相关类中的不变,前提和后置条件检查来实现(即,作为高级测试的副作用) )。

另一个问题是,使用基于货物的驱动设计技术来创建质量低下的设计,这会导致要测试的事物(更多的类,接口等)泛滥。在这种情况下,负担似乎是在更新测试代码,但真正的问题是质量差的设计。

评论


为了指出先决条件,后继条件和不变式而提出的建议应视为单元测试。这样,当该调试代码处于活动状态时,每次使用都是一个单元测试。

– Persixty
17年5月3日在20:35

这是一个很好的答案,完全符合我的经验。

–托尼·恩尼斯(Tony Ennis)
17年5月5日在12:41

还有一个问题:如果您签入的登机手续(您确实应该这样做!)具有大量的低质量,那么即使长时间运行的测试也可能使所有功能变慢,而没有提供任何实际好处。然后很明显,有趣的事实是,您在类中更改一件事,并且数百个测试失败。

– Voo
17年5月5日在20:07

这是一个比公认的更好的答案! “在某些情况下,大多数开发工作都包括更新低质量的测试”-我经历了这一点,这很糟糕。在某些方面,完全没有测试。

–本杰明·霍奇森(Benjamin Hodgson)
17年5月5日23:15



#3 楼

对您问题的回答


是否存在太多的单元测试之类的事情?


确定...例如,您可以乍看之下,有多个测试似乎有所不同,但实际上测试的是同一件事(从逻辑上讲,这取决于测试中“有趣的”应用程序代码的同一行)。

或者您可以测试代码的内部结构,这些内部结构永远不会向外浮出水面(即,它不是任何类型的接口契约的一部分),在那里人们可以争论一下这是否有意义。例如,内部日志消息的确切措辞或其他内容。


我受命为现有应用程序编写单元测试。在完成第一个文件后,我有717行测试代码与419行原始代码。


我觉得这很正常。在实际测试的基础上,您的测试在设置和拆卸方面花费了大量代码。该比率可能会提高,也可能不会提高。我本人的测试相当繁重,并且经常在测试上花费比实际代码更多的时间和时间。 />

比例没有考虑太多。测试还有其他一些性质,往往使其难以管理。如果在对代码进行相当简单的更改时经常需要重构大量测试,则应仔细查看原因。那不是您拥有多少行,而是您如何进行测试的编码。


我对单元测试的理解是测试类中的每个方法,以确保每个方法如预期般运作。


从严格意义上来说,这对于“单元”测试是正确的。在这里,“单位”类似于方法或类。 “单元”测试的重点是仅测试一个特定的代码单元,而不是整个系统。理想情况下,您将删除整个系统的其余部分(使用double或诸如此类)。


但是,在请求请求中,我的技术负责人指出我应该专注于更高级别的测试。


然后,当人们说单元测试时,您就陷入了假设人们实际上是指单元测试的陷阱。我遇到了许多程序员,他们说“单元测试”,但含义却大不相同。详尽地测试每个功能。


当然,仅专注于重要代码的前80%也会减少负载。让我成为最佳选择。


对我来说,100%的单元测试覆盖率是一个崇高的目标,但是即使我们只达到50%,我们也会知道那50%中的100%已覆盖%。


我不知道什么是“单元测试范围”。我假设您的意思是“代码覆盖率”,即运行测试套件后,每行代码(= 100%)至少执行了一次。远不是一个可以达到的最佳标准。仅仅执行代码行并不是全部。例如,这并不说明通过复杂的嵌套分支的不同路径。它更像是一种指标,将其手指指向测试过少的代码段(显然,如果一个类的代码覆盖率为10%或5%,则出问题了);另一方面,100%的覆盖率不会告诉您您是否已经测试足够或测试是否正确。

集成测试

当人们默认情况下,今天经常谈论单元测试。我认为(和经验),单元测试对于库/ API非常有用;在更多面向业务的领域(我们在这里讨论用例,例如眼前的问题),它们不一定是最佳选择。

对于一般应用程序代码和一般业务(在这方面,赚钱,按时完成任务和提高客户满意度很重要,并且您主要是想避免直接出现在用户面前或可能导致真正灾难的错误-我们不是

这些与行为驱动开发或功能驱动开发紧密结合;根据定义,那些不适用于(严格)单元测试。

为了简短起见,集成/功能测试对整个应用程序堆栈进行了测试。在基于Web的应用程序中,它就像浏览器点击该应用程序一样(而且,显然,不必这么简单,有非常强大的框架可以执行此操作-请访问http:// cucumber。 io,例如)。

哦,回答您的最后一个问题:通过确保仅在实施一项功能测试之后才对新功能进行编程,可以使整个团队具有较高的测试覆盖率失败了是的,这意味着所有功能。这样可以保证100%(正)的功能覆盖率。根据定义,它可以保证您应用程序的功能永远不会“消失”。它不能保证100%的代码覆盖率(例如,除非您主动编程否定功能,否则您将不会执行错误处理/异常处理)。应用;当然,您将需要针对明显或非常危险的错误情况,错误的用户输入,黑客行为(例如,周围的会话管理,安全性等)编写功能测试;但是即使只对肯定的测试进行编程也具有巨大的好处,并且对于现代,功能强大的框架来说是完全可行的。功能/集成测试显然具有其自身的蠕虫能力(例如,性能;对第三方框架的冗余测试;由于您通常不使用double,因此根据我的经验,它们也往往更难编写),但是d每天都要使用经过100%正面功能测试的应用程序,而不是经过100%经过代码覆盖率单位测试的应用程序(不是库!)。

评论


集成测试很棒,但不能替代单元测试,也不能替代业务应用程序。它们有多个问题:a)从定义上讲,它们需要很长时间才能运行(这也意味着增量测试几乎没有用),b)它们使查明实际问题变得异常困难(哦,50个集成测试刚刚失败,是什么变化导致的?)和c)它们反复覆盖相同的代码路径。

– Voo
17年5月5日在20:18

a)是一个问题,因为它使在门控的签入程序上运行测试变得麻烦,并使程序员在开发过程中重复运行测试的可能性降低,再加上b)降低了效率,并且无法快速诊断错误。 c)意味着更改一件小事很容易导致数十或数百个集成测试失败,这意味着您将花费大量时间修复它们。这也意味着集成测试主要只测试幸福的道路,因为针对这些目标编写测试很麻烦或不可能。

– Voo
17年5月5日在20:20

@Voo,您所写的所有内容都是真实的,据我所知,我已经提到您在答案中指出的所有问题...

– AnoE
17年5月7日在19:59

如果您同意该总结,那么我真的看不到如何得出结论,您更喜欢集成测试而非单元测试。大型程序的综合集成测试套件需要花费数小时甚至数天才能运行,它们虽然很棒,但在实际开发中几乎没有用。而且您的验收测试(每个人都在做,对吗?)将遇到许多集成测试发现会被单元测试遗漏的相同问题-事实并非如此。

– Voo
17年5月8日在20:14



@Voo,我很想知道您对验收测试和集成测试的定义。谢谢

– jrahhali
20 Mar 18 '20 at 22:41

#4 楼

是的,可能有太多的单元测试。例如,如果您具有100%的单元测试覆盖率,而没有集成测试,则说明问题很明显。
某些情况:


您将测试过度设计为特定的实施。然后,您必须在重构时放弃单元测试,而不是在更改实现时放弃(执行性能优化时经常遇到的痛点)。
在单元测试和集成测试之间取得良好的平衡可以减少此问题而又不会损失很多


使用20%的测试,您可以合理地覆盖每次提交,其余的80%用于集成或至少单独的测试通过;在这种情况下,您看到的主要负面影响是更改缓慢,因为您必须等待大量时间才能执行测试。例如,我已经看到很多IoC在不需要修改的组件上的滥用,或者至少将它们泛化为代价高且优先级低的组件,但是人们花了很多时间对它们进行泛化和重构以进行单元测试。


我特别同意关于100%文件覆盖率达到50%而不是50%文件覆盖率达到100%的建议。将您的最初工作重点放在最常见的肯定案例和最危险的否定案例上,不要在错误处理和异常路径上投入过多,不是因为它们并不重要,而是因为您的时间有限且测试范围无限,因此您需要优先考虑任何情况。

评论


这对单元测试不是问题,但是对于组织来说,其优先级是错误的,因为它要求特定数量的单元测试覆盖范围,而没有花费资源来创建和执行其他级别的适当测试。

– jwenting
17年5月4日在11:11

强烈同意#3,并且还将其扩展为将较低级别的类的实例手动传递给较高级别的类。如果高级事物依靠一些低级事物来完成某事,那没关系。如果它对调用者隐藏了详细信息,我会称其为好设计。但是,如果您然后将低级内容作为高级内容接口的一部分并让呼叫者通过它,因为它可以使您的测试变得漂亮,那么尾巴就会摇晃它。 (如果低级别的东西在很多地方都被重复使用,并且发生了很多变化,那将会改变事情。以我的经验,这不是典型的。)

– johncip
17年5月4日在19:41



我喜欢你的描述@johncip,绝对是一个常见的例子,说明如何通过向构造函数添加一些不必要的必需参数来使一个好的类变得可怕...

–布鲁诺·瓜迪亚(Bruno Guardia)
17年5月5日在16:20

#5 楼

请记住,每个测试既有成本,也有好处。缺点包括:


必须编写测试;
测试需要(通常很少)时间来运行;
必须维持测试使用代码-测试必须在API进行测试时更改;
您可能必须更改设计才能编写测试(尽管通常这些更改会更好)。如果成本超过收益,则最好不要编写测试。例如,如果功能很难测试,API经常更改,正确性相对不重要并且测试发现缺陷的机会很低,那么最好不编写它。

至于您的测试与代码的特定比率,如果代码具有足够的逻辑密度,则可以保证该比率。但是,在整个典型应用程序中保持如此高的比率可能不值得。

#6 楼

是的,有太多的单元测试。

虽然每个单元测试都很好,但它却是:


紧密耦合的潜在维护负担API上的时间
可以花在其他事情上的时间
单元测试套件中的一小段时间
可能并没有增加任何实际价值,因为它实际上与
另一些测试则有机会通过其他测试,但失败的可能性很小。

争取100%的代码覆盖率是明智的,但这远非意味着一套测试可以独立提供100个测试在某些指定的入口点(函数/方法/调用等)上的代码覆盖率百分比。 “错误的单元测试”与“太多的单元测试”一样多。

大多数代码的语用指示:入口点数(所有内容都经过测试,因此方法),力求接近“非错误”路径的100%代码覆盖率。
测试任何相关的最小/最大值或大小
测试任何您认为有趣的特殊情况,尤其是“奇数”值。
发现错误时,添加一个可以发现该错误的单元测试,然后考虑是否应该添加任何类似的情况。 >

对更多案例进行一些批量测试。
将结果与“蛮力”实现进行比较并检查不变量。
使用某种方法生成随机测试案例并进行检查例如,检查带有一些随机输入的排序算法,并通过扫描对数据进行排序以验证其是否排序。

我想说您的技术负责人建议进行“最小裸露”测试。我正在提供“最高价值的质量测试”,两者之间存在着光谱。

也许您的前辈知道您要构建的组件将被嵌入到更大的部件中,并且在集成时将对其进行更彻底的单元测试。

关键课是发现错误时添加测试。这给了我关于单元测试开发的最好的教训:

关注单元而不是子单元。
如果您是用子单元构建单元,请为子单元编写非常基本的测试-units,直到它们看起来合理,并通过通过其控制单元测试子单元达到更好的覆盖范围。使用基本测试启动并运行符号表,然后继续(说)填充表的声明解析器。如果发现符号表“独立”单元中的错误,则仅对其进行进一步测试。否则,通过在声明解析器以及整个编译器上进行单元测试来增加覆盖范围。完善,因为在测试中仅使用了“外部”接口,这往往更加稳定。最小的测试实现。

评论


我不会说100%的覆盖率是务实的。 100%的覆盖率是一个很高的标准。

–布莱恩·奥克利(Bryan Oakley)
17年5月3日在17:58

不幸的是,即使是随机方法也会遗漏错误。即使非正式,也无法替代证明。

–弗兰克·希勒曼
17年5月3日在18:07

@BryanOakley点已采取。这是一个夸大的说法。但是,接近人们比给予别人信任更重要。 “我测试了简单的方法,这一切都很好”总是会在以后引起问题。

– Persixty
17年5月3日在18:11

@FrankHileman问题不是“单元测试是否可以替代仔细设计软件,静态检查逻辑和证明算法”,那么答案是否定的。两种方法都不能自行产生高质量的软件。

– Persixty
17年5月3日在18:19

#7 楼

首先,拥有比生产代码更多的测试行不一定是问题。测试代码是线性的(或应该是线性的),并且易于理解-无论生产代码是否正确,测试代码的必要复杂度非常非常低。如果测试的复杂性开始接近生产代码的复杂性,那么您可能确实有问题。

是的,可能有太多的单元测试-一个简单的思想实验表明您可以继续添加没有附加价值的测试,并且所有这些添加的测试至少可以抑制某些重构。

我认为仅测试最常见情况的建议是有缺陷的。这些可以作为冒烟测试以节省系统测试时间,但真正有价值的测试可以捕获整个系统中难以执行的案例。例如,内存分配故障的受控错误注入可用于执行恢复路径,否则恢复路径的质量可能完全未知。或传递零作为您知道的将被用作除数的值(或负数将平方根),并确保您不会遇到未处理的异常。

有价值的测试是那些行使极限或边界点的测试。例如,接受一年中(从1开始)月份的函数应使用0、1、12和13进行测试,因此您知道有效无效的转换在正确的位置。还要对这些测试使用2..11进行过度测试。

您处于困境中,因为您必须为现有代码编写测试。在编写(或即将编写)代码时,更容易识别边缘情况。

#8 楼


我对单元测试的理解是测试类中的每种方法,以确保每种方法都能按预期工作。


这种理解是错误的。

单元测试可以验证被测单元的行为。

从这个意义上讲,单元不一定是“类中的方法”。我喜欢Roy Osherove在“单元测试的艺术”中对单元的定义:


单元是所有具有相同更改理由的生产代码。


基于此,单元测试应验证代码的每个所需行为。





但是,在请求请求中,我的技术主管指出我应该专注于更高级别的测试。 br />

他是正确的,但与他想像的方式不同。 br />
最大的误解是他希望您编写单元测试(与“使用单元测试框架进行测试”相反)。
编写nit测试是开发人员而非测试人员的责任。 (在理想的世界中,我知道...)。另一方面,您用TDD标记了这个问题,这恰好暗示了这一点。

您作为测试人员的工作是(或手动执行)模块和/或应用测试。这种测试应主要验证所有单元是否可以顺利协作。这意味着您必须选择测试用例,以便每个单元至少执行一次。而检查是运行。实际结果不太重要,因为它可能会随将来的要求而变化。

再次强调自卸汽车的类比:装配线末端的汽车要进行多少次测试?恰好一个:它必须自己开车到停车场...

这里的重点是:

我们需要意识到“单元测试”和“使用单元测试框架进行自动化测试”之间的区别。



对我来说,100%单元测试覆盖率是崇高的目标,但即使只达到50%,我们也知道那50%中的100%被覆盖。


单元测试是一个安全网。它们使您有信心重构代码,以减少技术负担或添加新行为,而不必担心破坏已实施的行为。

您不需要100%的代码覆盖率。

但是您需要100%的行为覆盖率。 (是的,代码覆盖率和行为覆盖率在某种程度上是相互关联的,但是它们并不完全相同。)

如果行为覆盖率不足100%,那么成功运行测试套件就没有任何意义因为您可以更改某些未经测试的行为。发布发布的第二天,您就会受到客户的关注。


结论

很少有测试比没有测试更好。毫无疑问!

但是没有太多的单元测试之类的东西。

这是因为每个单元测试都验证了对代码行为的单一期望。而且,您编写的单元测试不能超出对代码的期望。安全带上的孔可能会造成意外更改,从而损害生产系统。

#9 楼

绝对没错。我曾经是一家大型软件公司的SDET。我们的小型团队必须维护以前由大型团队处理的测试代码。最重要的是,我们的产品具有一些依赖关系,这些依赖关系不断带来重大变化,这意味着我们需要不断进行测试维护。我们没有选择增加团队规模的选择,因此当它们失败时,我们不得不扔掉数千个价值不高的测试。否则,我们将永远无法弥补这些缺陷。

在您将其视为纯粹的管理问题之前,请考虑一下现实世界中的许多项目,因为它们接近传统状态时会遭受人员减少的困扰。有时甚至在首次发布后就开始发生。

评论


“最重要的是,我们的产品具有某些依赖关系,这些依赖关系不断带来重大变化,这意味着我们需要不断进行测试维护。” -如果您的依赖关系不断中断,您说的那些测试要求维护听起来像是有价值的测试。

– CodeMonkey
17年5月4日在7:50

测试不是问题,而是组织。

– jwenting
17年5月4日在11:12

@CodeMonkey依赖关系没有中断。它们以需要更改我们产品的方式进行更新。是的,这些测试是有价值的,但远没有其他有价值。当等效的手动测试很困难时,自动测试最有价值。

– Mrog
17年5月5日15:39

@jwenting是的,这是组织问题,而不是代码问题。但这并不能改变测试太多的事实。无论原因如何,无法调查的失败测试都是没有用的。

– Mrog
17年5月5日15:41

什么是“ SDET”?

– Peter Mortensen
17年5月5日在16:34

#10 楼

假设您要重构测试代码以消除复制粘贴,那么拥有比产品代码多的测试代码行并不一定是问题。

问题在于,测试是实现的镜像,没有任何业务意义-例如,加载了模拟和存根并且仅断言某个方法调用其他方法的测试。

“为什么大多数单元测试是浪费”一文中有一个很好的引述,即单元测试应该具有“广泛,正式,独立的正确性和可确定的业务价值”。 >

#11 楼

我没有看到的一件事是您的测试需要快速且容易地进行,以便任何开发人员都能随时运行。

您不需要检入源代码控制并等待一个小时或更长时间(取决于代码库的大小),然后再进行测试,以查看更改是否破坏了某些内容-您希望能够在签入源代码管理之前(或至少在您进行更改之前)在自己的计算机上执行此操作。理想情况下,您应该能够通过单个脚本或按钮按下来运行测试。

当您在本地运行这些测试时,您希望它们能够快速运行-大约几秒钟。速度变慢了,您就会很想不充分地运行它们。问题。