![]() |
1
160
我将单元测试引入到以前没有的代码库中。我参与的最后一个大项目是,当我到达团队时,这个产品已经在生产中进行了零单元测试。两年后我离开时,我们进行了4500多次测试,代码覆盖率约为33%,代码库中有230 000多个生产loc(实时财务赢单应用程序)。这听起来可能很低,但结果是代码质量和缺陷率得到了显著的提高,同时士气和盈利能力也得到了提高。 当你对相关各方都有一个准确的理解和承诺时,就可以做到这一点。 首先,理解单元测试本身就是一种技能是很重要的。按照“常规”标准,您可以成为一个非常高效的程序员,并且仍然难以以一种在更大的项目中可扩展的方式编写单元测试。 另外,特别是对于您的情况,将单元测试添加到没有测试的现有代码库本身也是一种专门技能。除非您或您团队中的某个人在将单元测试引入现有代码库方面有成功的经验,否则我会说阅读 Feather's book 是一项要求(不可选或强烈推荐)。 向单元测试代码过渡是对人员和技能的投资,与对代码库质量的投资一样多。理解这一点对于心态和管理期望非常重要。 现在,对于您的评论和问题:
简短的回答:是的,您将错过测试,是的,它们最初可能不像在绿野环境中那样。 更深层次的答案是:这不重要。你从没有测试开始。开始添加测试,并在进行时重构。随着技能水平的提高,开始提高添加到项目中的所有新编写代码的门槛。不断改进等… 现在,在这两行之间,我觉得这是来自“完美是不采取行动的借口”的心态。更好的心态是专注于自我信任。既然你可能还不知道如何去做,你就要在你去填补空白的时候,找出如何去做。因此,没有理由担心。 同样,这是一种技巧。你不能在一个“过程”或“一步一步”的烹饪书方法中以线性方式从零测试到TDD完美。这将是一个过程。你的期望必须是逐步的、渐进的进步和改进。没有魔法药丸。 好消息是,随着几个月(甚至几年)的过去,您的代码将逐渐开始成为“合适的”经过良好分解和测试的代码。 作为旁注。您将发现在旧的代码库中引入单元测试的主要障碍是缺乏内聚性和过度的依赖性。因此,您可能会发现,最重要的技能将是如何打破现有的依赖关系和分离代码,而不是编写实际的单元测试本身。
除非您已经拥有它,否则请设置一个构建服务器,并设置一个在每次签入(包括具有代码覆盖率的所有单元测试)上运行的持续集成构建。 训练你的人。 从某个地方开始,并在从客户的角度取得进展的同时开始添加测试(见下文)。 使用代码覆盖率作为指导性参考,以了解您的生产代码库有多少正在测试中。 构建时间应该总是很快。如果您的构建时间很慢,那么您的单元测试技能就会滞后。找到并改进缓慢的测试(分离生产代码并隔离测试)。写得好,您应该能够轻松地进行数千个单元测试,并且在10分钟内完成一个构建(大约1-几毫秒/测试是一个很好但非常粗略的指导原则,一些例外可能适用,如代码使用反射等)。 检查并调整。
你自己的判断必须是你现实的主要来源。没有可以取代技能的度量标准。 如果你没有这样的经验或判断力,可以考虑与有经验或判断力的人签约。 两个粗略的次要指标是总代码覆盖率和构建速度。
对。在一个定制的系统或解决方案上花费的绝大多数资金都是在它投入生产之后花费的。在质量、人员和技能上的投资永远不会过时。
您必须考虑到,不仅是对人员和技能的投资,而且最重要的是系统的总体拥有成本和预期寿命。 在大多数情况下,我个人的回答是“当然可以”,因为我知道这一点更好,但我认识到可能会有例外。
两者都不。您的方法应该是在功能方面取得进展的同时向代码库中添加测试。 同样,它是对人员、技能和代码库质量的投资,因此需要时间。团队成员需要学习如何打破依赖关系、编写单元测试、学习新的habbits、提高纪律和质量意识、如何更好地设计软件等。重要的是要了解,当您开始添加测试时,您的团队成员可能还不具备这些技能,他们需要达到成功方法的水平,因此,停止所有时间用于添加大量测试的进度是行不通的。 此外,将单元测试添加到任何大型项目规模的现有代码库中是一项需要投入和持久性的大型工作。你不能改变一些基本的东西,在路上期待大量的学习,并要求你的赞助人不要因为停止业务价值的流动而期待任何投资回报。那不会飞,坦率地说,它不应该飞。 第三,你想在你的团队中灌输良好的业务重点价值观。质量永远不会以牺牲客户为代价,没有质量你就不能走得快。而且,客户生活在一个不断变化的世界中,你的工作就是让他更容易适应。客户协调需要质量和业务价值流。 你所做的就是还清技术债务。你这样做的同时,仍然为客户提供不断变化的需求。随着债务的偿还,情况逐渐好转,更好地为客户服务和提供更多价值变得更容易。等等。这种积极的势头是你应该达到的目标,因为它强调了可持续发展的原则,并将维护和改善道德——无论是对于你的开发团队、你的客户还是你的利益相关者。 希望有所帮助 |
![]() |
2
23
对!
不!
添加测试(尤其是自动测试)使其 许多的 更容易让项目在未来继续工作,而且它使您将愚蠢的问题发送给用户的可能性大大降低。 测试输入 先验的 是检查您认为代码的公共接口(以及其中的每个模块)是否按照您的想法工作的接口。如果可以的话,还可以尝试诱导代码模块应该具有的每个独立的故障模式(注意,这可能是非常重要的,并且您应该小心不要过于仔细地检查事情是如何失败的,例如,您确实不想做像计算失败时生成的日志消息数这样的事情,因为验证它是否被记录根本不是呃) 然后,对bug数据库中的每个当前bug进行测试,以准确地引发bug,并在bug修复后通过测试。那就去修复那些虫子吧!-) 在前面添加测试确实需要花费时间,但是在后端您会得到很多次的回报,因为您的代码最终的质量要高得多。当您尝试发布新版本或进行维护时,这非常重要。 |
![]() |
3
15
改装单元测试的问题是,你会意识到你没有想到在这里注入依赖项或者在那里使用接口,不久你就会重写整个组件。如果您有时间这样做,您将为自己构建一个很好的安全网,但是您可以在这一过程中引入一些细微的bug。 从第一天开始,我就参与了许多项目,这些项目确实需要单元测试,而且没有简单的方法让它们进入其中,除了一个完整的重写,当代码工作并且已经赚钱时,这通常是不合理的。最近,我开始编写PowerShell脚本,这些脚本以一种方式运行代码,在代码出现缺陷时立即复制该缺陷,然后将这些脚本作为一组回归测试保留下来,以便进行进一步的更改。通过这种方式,您至少可以开始为应用程序构建一些测试,而不会对其进行太多的更改,但是,这些测试更像是端到端回归测试,而不是适当的单元测试。 |
![]() |
4
11
我同意大多数人所说的。向现有代码中添加测试是有价值的。我决不会反对这一点,但我想补充一点警告。 尽管向现有代码中添加测试是有价值的,但它确实是有代价的。它的代价是 不 开发新功能。这两件事如何平衡完全取决于项目,并且有许多变量。
再说一遍,让我强调一下,测试是有价值的,您应该努力让您的旧代码接受测试。这更像是你如何处理的问题。如果您可以放下所有东西并对所有旧代码进行测试,那么就这么做。如果这不现实,你应该这样做 至少
而且,这不是一个全无命题。如果你有一个团队,比如说,四个人,并且你可以通过让一到两个人承担遗留测试的责任来满足你的最后期限,那么一定要做到这一点。 编辑:
这就像在问“使用源代码管理的利弊是什么?”或者“在雇佣员工之前,面试员工的利弊是什么?”或者“呼吸的利弊是什么?” 有时候争论只有一个方面。 对于任何复杂的项目,您都需要有某种形式的自动化测试。不,测试不会自己写,而且,是的,需要一点额外的时间才能把事情弄出去。但是 从长远来看,与预先编写测试相比,事后修复bug要花费更多的时间和金钱。 时期。就这些了。 |
![]() |
5
7
这绝对值得。我们的应用程序有复杂的交叉验证规则,最近我们不得不对业务规则进行重大更改。我们最终遇到了阻止用户保存的冲突。我意识到在应用程序中解决这个问题需要花费很长时间(只需要几分钟就可以达到问题所在)。我本来想引入自动化单元测试并安装框架,但除了几个虚拟测试之外,我什么都没做,以确保一切正常。掌握了新的业务规则后,我开始编写测试。测试很快确定了导致冲突的条件,并且我们能够澄清规则。 如果您编写的测试涵盖了您要添加或修改的功能,那么您将立即受益。如果您等待重新写入,可能永远不会有自动测试。 你不应该花太多时间为已经有效的现有事物编写测试。大多数时候,您没有现有代码的规范,所以您测试的主要内容是您的逆向工程能力。另一方面,如果要修改某些内容,则需要通过测试来覆盖该功能,这样您就可以知道所做的更改是正确的。当然,对于新功能,编写失败的测试,然后实现缺少的功能。 |
![]() |
6
7
当我们开始添加测试时,它已经存在10年了,大约有一百万行代码库,在UI和报告代码中有太多的逻辑。 我们做的第一件事(在建立了一个连续的构建服务器之后)是添加回归测试。这些是端到端的测试。
回归测试的目的是告诉你是否有什么变化。这意味着,如果您破坏了某个东西,它们就会失败,但是如果您故意更改了某个东西,它们也会失败(在这种情况下,修复方法是更新快照文件)。您不知道快照文件甚至是正确的——系统中可能存在错误(然后当您修复这些错误时,回归测试将失败)。 然而,回归测试对我们来说是一个巨大的胜利。我们的系统中几乎所有的东西都有一个报告,因此,通过花几个星期的时间围绕这些报告进行测试,我们能够在很大一部分代码库上获得一定程度的覆盖率。编写等效的单元测试需要几个月或几年的时间。(单元测试会给我们提供更好的覆盖范围,并且不会那么脆弱;但是我宁愿现在就有东西,而不是等待数年的完善。) 然后我们回到过去,开始添加单元测试,当我们修复了错误、增加了增强或者需要理解一些代码时。回归测试绝不能消除对单元测试的需求;它们只是一个一级安全网,因此您可以 一些 测试覆盖级别很快。然后您可以开始重构以打破依赖关系,这样您就可以添加单元测试;回归测试让您对重构没有破坏任何东西充满信心。 回归测试有问题:它们速度慢,而且有太多的原因可以打破。但至少对我们来说 所以 值得的。在过去的五年中,他们捕获了无数的错误,并且在几个小时内捕获它们,而不是等待一个QA周期。我们仍然有那些原始的回归测试,分布在七个不同的连续构建机器上(与运行快速单元测试的机器分开),我们甚至不时地添加它们,因为我们仍然有太多的代码,以至于我们的6000+单元测试无法覆盖。 |
![]() |
7
5
我会添加我的声音,然后说“是的,它总是有用的!” 不过,您应该记住一些区别:黑盒与白盒,以及单元与功能。由于定义不同,我的意思是:
当我在游戏后期为一个运输产品添加测试时,我发现我从 白盒 和 功能性 测验。如果您知道代码的任何部分特别脆弱,那么就编写白盒测试来覆盖问题案例,以确保它不会以相同的方式破坏两次。同样,整个系统功能测试也是一个有用的健全性检查,它可以帮助您确保永远不会破坏10个最常见的用例。 黑盒和小单元的单元测试也很有用,但是如果您的时间有限,最好尽早添加它们。到您发货时,您通常已经发现(困难的方式)大多数边缘案例和这些测试会发现的问题。 像其他人一样,我也会提醒你关于TDD最重要的两件事:
|
![]() |
8
4
是否值得将单元测试添加到正在生产的应用程序中取决于维护应用程序的成本。如果这个应用程序有一些缺陷和增强请求,那么它可能不值得这样做。Otoh,如果应用程序有缺陷或者经常修改,那么单元测试将是非常有益的。 在这一点上,请记住,我所说的是有选择地添加单元测试,而不是尝试生成一组类似于从一开始就练习TDD的测试。因此,在回答第二个问题的后半部分时:在下一个项目中使用TDD,不管它是新项目还是重写(抱歉,这里有一个链接指向 另一个 你真正应该读的书: Growing Object Oriented Software Guided by Tests ) 我对你第三个问题的回答和第一个相同:这取决于你的项目的背景。 在您的帖子中嵌入了一个关于确保完成任何改装测试的进一步问题。 适当地 . 重要的是要确保单元测试 单元 测试,这(通常)意味着改装测试需要重构现有代码,以允许层/组件的去耦(参见依赖注入;控制反转;存根;模拟)。如果您未能实现这一点,那么您的测试就变成了集成测试,这是有用的,但比真正的单元测试目标更小,更脆弱。 |
![]() |
9
4
你没有提到实现语言,但是如果在Java中你可以尝试这种方法:
|
![]() |
10
3
我想从这个答案开始,说单元测试非常重要,因为它将帮助您在bug进入生产之前逮捕它们。 确定重新引入错误的项目/模块区域。从那些项目开始编写测试。 为新功能和错误修复编写测试是完全有意义的。
对。您将看到bug的减少和维护变得更容易
我建议从现在开始。
你问错了问题。当然,功能比其他任何东西都重要。但是,您应该问,花几个星期增加测试是否会使我的系统更稳定。这对我的最终用户有帮助吗?它是否有助于团队中的新开发人员理解项目,并确保他/她不会由于不了解变更的整体影响而引入错误? |
![]() |
11
3
我很喜欢 Refactor the Low-hanging Fruit 作为对重构从何处开始的问题的回答。这是一种轻松进入更好的设计而不咬掉超过你能咀嚼的东西的方法。 我认为同样的逻辑也适用于TDD——或者只是单元测试:根据需要编写所需的测试;为新代码编写测试;根据出现的错误编写测试。您担心忽略代码库中更难到达的区域,这当然是一个风险,但作为一种开始的方法:开始!你可以通过代码覆盖工具来降低风险,而且风险(在我看来)也没有那么大:如果你在覆盖bug,覆盖新代码,覆盖你正在查看的代码,那么你就覆盖了最需要测试的代码。 |
![]() |
12
2
|
![]() |
13
2
是的,它可以:只需确保从现在开始编写的所有代码都有一个测试。 如果已经到位的代码需要修改,并且可以测试,那么就这样做,但是最好不要过于积极地尝试为稳定的代码准备好测试。这类事情往往会产生连锁反应,并可能螺旋式失控。 |
![]() |
14
2
对于正在生产的现有解决方案来说,这值得付出努力吗?对。但您不必编写所有单元测试就可以开始。把它们一个一个地加起来。
是否最好忽略此项目的测试,并在将来可能的重写中添加它?不,第一次添加破坏功能的代码时,您会后悔的。
还有什么更有益的呢?花几个星期增加测试或者几个星期增加功能?对于新功能(代码),这很简单。首先编写单元测试,然后编写功能。 对于旧的代码,您可以在路上决定。你不需要所有的单元测试都到位…加上那些伤害你最深的…时间(和错误)会告诉你该关注哪一个;) |
![]() |
15
2
更新 6年后的原始答案,我有一个略有不同的看法。 我认为将单元测试添加到您编写的所有新代码中是有意义的,然后重构您所做更改的地方,使其可测试。 为所有现有代码一次性编写测试不会有帮助,但为编写的新代码(或修改的区域)编写测试也没有意义。在重构/添加东西时添加测试可能是添加测试并使代码在没有测试的现有项目中更易于维护的最佳方法。 更早的回答 我要扬起眉毛:) 首先,你的项目是什么——如果它是一个编译器、一种语言、一个框架或者其他任何在很长一段时间内不会改变功能的东西,那么我认为添加单元测试是非常棒的。 但是,如果您正在开发一个可能需要更改功能(由于需求的变化)的应用程序,那么没有必要进行额外的工作。 为什么?
还有什么更有益的呢?花几周时间增加测试或者几周时间增加功能?-有很多事情比编写测试做得更好——编写新功能、提高性能、提高可用性、编写更好的帮助手册、解决待定的错误等。 现在别误会我——如果你对未来100年事情不会发生变化持绝对肯定的态度,那就去做吧,把你自己打倒,写下那些测试。对于API来说,自动化测试也是个好主意,因为您绝对不想破坏第三方代码。在其他地方,它只是让我晚点上船的东西! |
![]() |
16
1
您不太可能有大量的测试覆盖率,因此您必须在添加测试的位置采取策略:
Othh,不值得只是坐着围绕人们满意的代码编写测试——特别是如果没有人愿意修改它。它只是不增加价值(可能理解系统的行为除外)。 祝你好运! |
![]() |
17
1
你说你不想再买一本书。所以只要读一下迈克尔·费瑟的文章 working effectively with legacy code . 那就买这本书吧:) |
![]() |
18
1
如果我在你的位置上,我可能会采用一种外部的方法,从运行整个系统的功能测试开始。我将尝试使用BDD规范语言(如rspec)重新记录系统的需求,然后编写测试,通过自动化用户界面来验证这些需求。 然后,我将为新发现的bug进行缺陷驱动的开发,编写单元测试来重现问题,并处理bug,直到测试通过。 对于新特性,我将坚持使用外部的方法:从RSPEC中记录的特性开始,通过自动化用户界面(当然,最初会失败)进行验证,然后随着实现的发展添加更细粒度的单元测试。 我不是这个过程的专家,但是从我的经验来看,我可以告诉你,通过自动化的用户界面测试进行BDD并不容易,但是我认为这是值得的,而且在你的案例中可能会产生最大的好处。 |
![]() |
19
1
我不是一个经验丰富的TDD专家,但我当然会说,它是非常重要的单元测试尽可能多。因为代码已经就位,所以我首先要做的是让单元测试自动化就位。我使用TeamCity来练习我的项目中的所有测试,它为您提供了关于组件如何工作的一个很好的摘要。 有了它,我将转向那些真正关键的业务逻辑,比如不能失败的组件。在我的例子中,对于各种输入,有一些基本的三角学问题需要解决,所以我测试这些问题。我这样做的原因是,当我在夜深人静的时候,很容易浪费时间去挖掘真正不需要被触碰的代码深度,因为 知道他们已经被测试过了 对于所有可能的输入(在我的例子中,输入的数量是有限的)。 好吧,现在你希望对那些关键的部分感觉更好。我不会坐下来把所有的测试都敲出来,而是在测试出来的时候攻击它们。如果你碰到了一个真正需要修复的PITA错误,那么就为它编写单元测试,并让它们远离这个障碍。 在某些情况下,您会发现测试很困难,因为您无法从测试中实例化特定的类,所以必须模拟它。哦,但也许你不能轻松地模仿它,因为你没有写接口。我把这些“whoops”场景作为实现上述接口的机会,因为,好吧,这是一件好事。 从那里,我将得到您的构建服务器或者您已经用代码覆盖工具配置的任何自动化。它们创建了大红色区域的讨厌的条形图,在那里你的覆盖率很低。现在100%的覆盖率不是你的目标,100%的覆盖率也不一定意味着你的代码是防弹的,但是当我有空闲时间的时候,红色条肯定会激励我。:) |
![]() |
20
1
有这么多好的答案,所以我不会重复它们的内容。我检查了你的个人资料,你似乎是C.NET开发人员。因此,我要添加对Microsoft的引用 PEX and Moles 可以帮助您为遗留代码自动生成单元测试的项目。我知道自动生成不是最好的方法,但至少它是开始的方法。查看这篇来自《msdn杂志》关于使用 PEX for legacy code . |
![]() |
21
1
我建议你读一本精彩的书 article Toptal的工程师解释说 在哪里? 开始添加测试:它包含很多数学知识,但基本思想是: 1)测量代码 传入耦合(CA) (一个等级有多少被其他等级使用,这意味着打破它会造成广泛的损害) 2)测量代码 圈复杂度 (更高的复杂性=更高的断裂变化)
为什么?因为高CA但是非常低CC类是非常重要的,但不太可能中断。另一方面,低钙但高cc很可能会破裂,但造成的损害较小。所以你想要平衡。 |
![]() |
22
0
这取决于…
|
![]() |
23
-2
对。 不。 添加测试。 采用更多的TDD方法实际上会更好地通知您增加新功能的努力,并使回归测试更加容易。过来看! |
![]() |
S4beR · jest-手表模式错误 7 年前 |
|
M.C · 如何从Ruby TDD哈希数组中返回名称 7 年前 |
![]() |
Maverick94 · Python unitest不工作 7 年前 |
![]() |
erexo · Moq模拟。Of<Obj>vs new Obj(); 7 年前 |
![]() |
jpuriol · JUnit测试中的“单例” 7 年前 |
![]() |
Py.Jordan · 如何在Python中模拟用户输入 7 年前 |