单元测试能否成功添加到现有的生产项目中?如果是这样,如何以及是不是值得?

Posted

技术标签:

【中文标题】单元测试能否成功添加到现有的生产项目中?如果是这样,如何以及是不是值得?【英文标题】:Can unit testing be successfully added into an existing production project? If so, how and is it worth it?单元测试能否成功添加到现有的生产项目中?如果是这样,如何以及是否值得? 【发布时间】:2011-03-29 09:51:23 【问题描述】:

我正在强烈考虑将单元测试添加到正在生产的现有项目中。它是在 18 个月前开始的,当时我还没有真正看到 TDD 的任何好处(掌心),所以现在它是一个包含许多项目的相当大的解决方案,我还没有最模糊的想法是从哪里开始添加单元测试。让我想到这一点的原因是,有时一个旧错误似乎会重新出现,或者一个错误在没有真正被修复的情况下被签入为已修复。单元测试将减少或防止这些问题的发生。

通过阅读关于 SO 的 similar 问题,我看到了一些建议,例如从错误跟踪器开始并为每个错误编写测试用例以防止回归。但是,我担心我最终会错过大局,最终会错过如果我从一开始就使用 TDD 就会包含的基本测试。

是否应遵守任何流程/步骤,以确保现有解决方案经过正确单元测试,而不是简单地进行测试?我怎样才能确保测试具有良好的质量,而不仅仅是一个例子任何测试都比没有测试好

所以我想我也问的是;

值得为此付出努力吗? 正在生产的现有解决方案? 最好忽略测试 对于这个项目并将其添加到 未来可能重写吗? 什么会更有益;开支 几周添加测试或几个 几周添加功能?

(显然第三点的答案完全取决于您是在与管理层还是开发人员交谈)


赏金原因

增加赏金以尝试吸引更广泛的答案,这不仅证实了我现有的怀疑,即这是一件好事,而且还有一些很好的反对理由。

我的目标是稍后写出这个问题的优缺点,以尝试向管理层表明,花时间将产品的未来开发转移到 TDD 是值得的。我想在没有自己偏见的情况下应对这一挑战并发展我的推理。

【问题讨论】:

Michael Feathers 关于该主题的书籍的必填链接:amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/… @Mark - 谢谢,在我提供的链接中。我希望在不买另一本书的情况下得到一个体面的答案......虽然你永远不会有太多的书(除非你想完成工作)。 你真的需要阅读这本书 :) 它是我的最爱之一,确实有助于理解重构和自动化测试之间的紧张关系。 【参考方案1】:

我已经将单元测试引入了以前没有的代码库。我参与的最后一个大型项目,当我加入团队时,该产品已经投入生产,零单元测试。当我离开时 - 2 年后 - 我们进行了 4500 多个测试,在具有 230 000 + 生产 LOC(实时财务 Win-Forms 应用程序)的代码库中产生了大约 33% 的代码覆盖率。这听起来可能很低,但其结果是代码质量和缺陷率显着提高,而且士气和盈利能力也有所提高。

只有当您获得相关各方的准确理解和承诺时,才能做到这一点。

首先,重要的是要了解单元测试本身就是一种技能。按照“传统”标准,您可以成为一个非常高效的程序员,但仍然难以以可在更大项目中扩展的方式编写单元测试。

另外,特别是针对您的情况,将单元测试添加到没有测试的现有代码库本身也是一项专业技能。除非您或您团队中的某个人在将单元测试引入现有代码库方面有成功经验,否则我会说阅读 Feather's book 是必需的(不是可选的或强烈推荐)。

过渡到对代码进行单元测试是对人员和技能的投资,与对代码库质量的投资一样重要。了解这一点对于心态和管理期望非常重要。

现在,对于您的 cmets 和问题:

但是,我担心我最终会错过大局,最终会错过如果我从一开始就使用 TDD 就会包含的基本测试。

简短回答:是的,您会错过测试,是的,它们最初看起来可能不像在绿地情况下的样子。

更深层次的答案是:没关系。你从没有测试开始。开始添加测试,并随时重构。随着技能水平的提高,开始提高添加到项目中的所有新编写代码的标准。不断改进等等...

现在,在字里行间,我得到的印象是,这是来自“完美作为不采取行动的借口”的心态。更好的心态是专注于自我信任。因此,由于您可能还不知道该怎么做,所以您将在进行过程中弄清楚如何填写空白。因此,没有理由担心。

再次,它是一种技能。您不能以线性方式在一个“过程”或“逐步”烹饪书方法中从零测试到 TDD 完美。这将是一个过程。您的期望必须是渐进式和渐进式的进步和改进。没有灵丹妙药。

好消息是,随着数月(甚至数年)的过去,您的代码将逐渐开始成为“适当的”经过良好分解和经过良好测试的代码。

作为旁注。您会发现在旧代码库中引入单元测试的主要障碍是缺乏凝聚力和过度依赖。因此,您可能会发现最重要的技能将变成如何打破现有的依赖关系和解耦代码,而不是自己编写实际的单元测试。

是否应遵守任何流程/步骤,以确保现有解决方案经过适当的单元测试,而不是仅仅停留在其中?

除非您已经拥有它,否则请设置构建服务器并设置在每次签入时运行的持续集成构建,包括所有具有代码覆盖率的单元测试。

培训你的员工。

从某个地方开始并开始添加测试,同时从客户的角度取得进展(见下文)。

使用代码覆盖率作为您的生产代码库有多少正在测试中的指导参考。

构建时间应该总是很快。如果您的构建时间很慢,那么您的单元测试技能就会滞后。找到慢速测试并改进它们(分离生产代码和隔离测试)。写得好,你应该可以轻松地进行数千个单元测试,并且仍然在 10 分钟内完成构建(~1-几毫秒 / 测试是一个很好但非常粗略的指导方针,一些例外情况可能适用,例如使用反射的代码等)。

检查和调整。

我怎样才能确保测试是高质量的,而不是任何测试都比没有测试好。

你自己的判断必须是你现实的主要来源。没有任何指标可以代替技能。

如果您没有这种经验或判断力,请考虑与有这种经验或判断力的人签约。

两个粗略的次要指标是总代码覆盖率和构建速度。

是否值得为生产中的现有解决方案付出努力?

是的。花在定制系统或解决方案上的大部分资金是在投入生产后花费的。对质量、人员和技能的投资永远不会过时。

忽略此项目的测试并在未来可能的重写中添加它会更好吗?

您必须考虑的不仅是对人员和技能的投资,最重要的是总拥有成本和系统的预期使用寿命。

在大多数情况下,我个人的回答是“当然可以”,因为我知道这要好得多,但我知道可能会有例外。

什么会更有益;花几周时间添加测试还是花几周时间添加功能?

两者都不是。您的方法应该是在功能方面取得进展的同时向您的代码库添加测试。

同样,这是对人员、技能和代码库质量的投资,因此需要时间。团队成员需要学习如何打破依赖关系、编写单元测试、学习新习惯、提高纪律和质量意识、如何更好地设计软件等。重要的是要了解,当您开始添加测试时,您的团队成员可能不会拥有这些技能,但仍处于该方法成功所需的水平,因此停止进度以花费所有时间添加大量测试根本行不通。

此外,向任何规模庞大的项目的现有代码库添加单元测试是一项艰巨的任务,需要投入和坚持。你不能改变一些根本性的东西,期望在此过程中学到很多东西,并要求你的赞助商不要通过停止商业价值的流动来期望任何投资回报率。那不会飞,坦率地说它不应该。

第三,您希望在团队中灌输正确的业务重点价值观。质量永远不会以牺牲客户为代价,没有质量就无法快速发展。此外,客户生活在一个不断变化的世界中,您的工作是让他更容易适应。与客户保持一致需要质量和业务价值的流动。

您正在做的是偿还技术债务。而且您这样做的同时仍然为客户不断变化的需求提供服务。随着债务逐渐还清,情况有所改善,更容易更好地为客户服务并提供更多价值。等等。这种积极的势头是您应该追求的目标,因为它强调了可持续发展的原则,并将保持和提高道德——无论是对您的开发团队、您的客户还是您的利益相关者。

希望有帮助

【讨论】:

【参考方案2】:
是否值得为生产中的现有解决方案付出努力?

是的!

最好忽略此项目的测试并在未来可能的重写中添加它?

不!

什么会更有益;花几周时间添加测试还是花几周时间添加功能?

添加测试(尤其是自动化测试)可以大大更轻松地让项目在未来继续工作,并且大大降低你交付愚蠢的可能性给用户带来的问题。

放入的测试先验是检查您是否认为您的代码(以及其中的每个模块)的公共接口是否按照您的想法工作的测试。如果可以的话,试着诱导你的代码模块应该有的每个孤立的故障模式(注意这可能是不平凡的,你应该小心不要太仔细地检查事情是如何失败的,例如,你真的不想要做一些事情,比如计算失败时产生的日志消息的数量,因为验证它是否被记录就足够了)。

然后对您的错误数据库中的每个当前错误进行测试,该测试准确地引发错误并且在错误修复后将通过。然后修复这些错误! :-)

预先添加测试确实要花费时间,但您会在后端获得多次回报,因为您的代码最终质量更高。当您尝试发布新版本或进行维护时,这非常重要。

【讨论】:

【参考方案3】:

改造单元测试的问题是你会意识到你没有想到在这里注入依赖或在那里使用接口,不久你就会重写整个组件。如果您有时间这样做,您将为自己建立一个很好的安全网,但您可能会在此过程中引入一些细微的错误。

我参与了许多从第一天开始就确实需要单元测试的项目,并且没有简单的方法可以让它们进入那里,除非完全重写,当代码正在运行并且已经运行时通常无法证明这是合理的赚钱。最近,我求助于编写 powershell 脚本,这些脚本以一种在出现缺陷时立即重现缺陷的方式来运行代码,然后将这些脚本作为一套回归测试来进行进一步的更改。这样,您至少可以开始为应用程序构建一些测试,而无需对其进行太多更改,但是,这些更像是端到端回归测试,而不是适当的单元测试。

【讨论】:

如果代码从一开始就被很好地分割了,那么测试它就相当容易了。当您的代码杂乱地拼接在一起并且唯一暴露的测试点是用于完整集成测试时,问题就来了。 (我有这样的代码,可测试性几乎为零,因为它依赖于其他不容易模拟的组件,并且部署需要重新启动服务器。可测试......但很难做好。)【参考方案4】:

我同意大多数人所说的。向现有代码添加测试很有价值。我永远不会不同意这一点,但我想补充一点。

虽然向现有代码添加测试很有价值,但它确实是有代价的。这是以构建新功能为代价的。这两件事如何平衡完全取决于项目,并且存在许多变量。

测试所有代码需要多长时间?天?周?几个月?年? 您要为谁编写此代码?付费客户?一位教授?一个开源项目? 您的日程安排是怎样的?您是否有必须满足的严格期限?你们有最后期限吗?

再次强调,测试很有价值,您应该努力测试您的旧代码。这实际上更多是您如何处理它的问题。如果您有能力放弃所有内容并测试所有旧代码,那就去做吧。如果这不现实,那么至少

应该这样做 您编写的任何新代码都应该完全在单元测试中 您碰巧接触到的任何旧代码(错误修复、扩展等)都应进行单元测试

此外,这不是一个全有或全无的命题。如果您有一个由四人组成的团队,并且您可以通过安排一两个人负责旧版测试来满足您的最后期限,那么请务必这样做。

编辑:

我的目标是稍后写下这个问题的优缺点,以尝试向管理层表明,花时间将产品的未来开发转移到 TDD 是值得的。

这就像问“使用源代码管理有哪些优点和缺点?”或“在雇用人员之前对其进行面试的利弊是什么?”或“呼吸有什么好处和坏处?”

有时争论只有一方面。您需要对任何复杂的项目进行某种形式的自动化测试。不,测试不会自己编写,而且,是的,需要一点额外的时间才能完成。但是从长远来看,事后修复错误比预先编写测试需要更多的时间和成本。期间。仅此而已。

【讨论】:

【参考方案5】:

当我们开始添加测试时,它是针对一个已有十年历史、大约百万行的代码库,在 UI 和报告代码中逻辑太多。

我们做的第一件事(在设置连续构建服务器之后)是添加回归测试。这些是端到端测试。

每个测试套件首先将数据库初始化为已知状态。实际上,我们在 Subversion 中保存了数十个回归数据集(由于庞大的规模,我们将它们保存在与我们的代码不同的存储库中)。每个测试的 FixtureSetUp 将这些回归数据集之一复制到临时数据库中,然后从那里运行。 测试夹具设置然后运行一些我们感兴趣的结果。(此步骤是可选的 - 一些回归测试仅用于测试报告。) 然后每个测试运行一个报告,将报告输出到一个 .csv 文件,并将该 .csv 的内容与保存的快照进行比较。这些快照 .csv 存储在每个回归数据集旁边的 Subversion 中。如果报告输出与保存的快照不匹配,则测试失败。

回归测试的目的是告诉您是否有变化。这意味着如果您破坏了某些东西,它们会失败,但如果您故意更改某些东西,它们也会失败(在这种情况下,修复是更新快照文件)。您甚至不知道快照文件是否正确——系统中可能存在错误(然后当您修复这些错误时,回归测试将失败)。

不过,回归测试对我们来说是一个巨大的胜利。几乎我们系统中的所有内容都有一个报告,因此通过花费几周时间围绕报告获取测试工具,我们能够在我们的大部分代码库中获得一定程度的覆盖。编写等效的单元测试将花费数月或数年的时间。 (单元测试会给我们提供更好的覆盖率,并且不会那么脆弱;但我宁愿现在就拥有一些东西,而不是等待多年的完美。)

然后,当我们修复错误、添加增强功能或需要理解某些代码时,我们返回并开始添加单元测试。回归测试绝不会消除对单元测试的需要;它们只是一个一级安全网,因此您可以快速获得一些级别的测试覆盖率。然后你可以开始重构以打破依赖关系,这样你就可以添加单元测试了;回归测试让您确信重构不会破坏任何东西。

回归测试存在问题:它们很慢,而且有太多原因会导致它们崩溃。但至少对我们来说,他们是非常值得的。在过去的五年中,他们发现了无数的错误,他们在几个小时内就发现了它们,而不是等待一个 QA 周期。我们仍然有那些原始的回归测试,分布在七台不同的连续构建机器上(与运行快速单元测试的机器不同),我们甚至不时添加它们,因为我们仍然有很多代码,以至于我们的 6,000 + 单元测试不涵盖。

【讨论】:

【参考方案6】:

绝对值得。我们的应用程序具有复杂的交叉验证规则,我们最近不得不对业务规则进行重大更改。我们最终遇到了阻止用户保存的冲突。我意识到在应用程序中解决它需要很长时间(需要几分钟才能找到问题所在)。我想引入自动化单元测试并安装了框架,但除了几个虚拟测试之外我没有做任何事情来确保一切正常。有了新的业务规则,我开始编写测试。测试很快确定了导致冲突的条件,并且我们能够澄清规则。

如果您编写的测试涵盖了您正在添加或修改的功能,您将立即获得好处。如果您等待重写,您可能永远不会进行自动化测试。

您不应该花费大量时间为已经工作的现有事物编写测试。大多数时候,您没有现有代码的规范,因此您要测试的主要内容是您的逆向工程能力。另一方面,如果你要修改某些东西,你需要用测试覆盖那个功能,这样你就会知道你做了正确的改变。当然,对于新功能,编写失败的测试,然后实现缺少的功能。

【讨论】:

【参考方案7】:

我会添加我的声音并说是的,它总是有用的!

不过,您应该牢记一些区别:黑盒与白盒,单元与功能。由于定义各不相同,这就是我的意思:

黑盒 = 在没有专门的实现知识的情况下编写的测试,通常会在边缘情况下四处寻找,以确保事情发生在天真的用户所期望的情况下。 白盒 = 在实施知识的情况下编写的测试,这些测试经常尝试练习众所周知的故障点。 单元测试 = 单个单元(功能、可分离模块等)的测试。例如:确保您的数组类按预期工作,并且您的字符串比较函数返回各种输入的预期结果。 功能测试 = 一次性测试整个系统。这些测试将同时运行大部分系统。例如:初始化、打开连接、做一些真实的事情、关闭、终止。我喜欢区分这些和单元测试,因为它们的用途不同。

当我在游戏后期为发货产品添加测试时,我发现我从 白盒功能 测试中获得了最大的收益.如果您知道代码的任何部分特别脆弱,请编写白盒测试来覆盖问题案例,以帮助确保它不会以相同的方式破坏两次。同样,全系统功能测试是一种有用的健全性检查,可帮助您确保永远不会破坏 10 个最常见的用例。

小单元的黑盒和单元测试也很有用,但如果您的时间有限,最好尽早添加它们。到您发货时,您通常已经(艰难地)发现了这些测试会发现的大多数边缘情况和问题。

和其他人一样,我还要提醒您关于 TDD 的两个最重要的事情:

    创建测试是一项持续的工作。它永远不会停止。每次编写新代码或修改现有代码时,都应尝试添加新测试。 您的测试套件永远不会出错!不要让您拥有测试的事实使您产生虚假的安全感。仅仅因为它通过了测试套件并不意味着它工作正常,或者你没有引入微妙的性能回归等。

【讨论】:

【参考方案8】:

是否值得为生产中的应用添加单元测试取决于维护应用的成本。如果该应用程序的错误和增强请求很少,那么可能不值得付出努力。 OTOH,如果应用程序有问题或经常修改,那么单元测试将非常有益。

此时,请记住,我说的是有选择地添加单元测试,而不是尝试生成一套类似于如果您从一开始就练习 TDD 就会存在的测试套件。因此,在回答你第二个问题的后半部分:在你的下一个项目中使用 TDD,无论是新项目还是重写(抱歉,但这里有一个 another 的链接> 你真的应该读的书:Growing Object Oriented Software Guided by Tests)

我对您的第三个问题的回答与第一个相同:这取决于您的项目上下文。

在您的帖子中嵌入了一个关于确保所有改装测试正确完成的问题。重要的是要确保单元测试确实是unit测试,这(通常)意味着改造测试需要重构现有代码以允许您的层解耦/components(参见依赖注入;控制反转;存根;模拟)。如果您未能强制执行此操作,那么您的测试将成为集成测试,这很有用,但比真正的单元测试更有针对性且更脆弱。

【讨论】:

【参考方案9】:

你没有提到实现语言,但如果在 Java 中,那么你可以尝试这种方法:

    在单独的源代码树构建回归或“冒烟”测试中,使用工具生成它们,这可能使您接近 80% 的覆盖率。这些测试执行所有代码逻辑路径,并从那时起验证代码仍然完全按照它当前的方式执行(即使存在错误)。这为您提供了一个安全网,可以防止在进行必要的重构以使代码易于手动进行单元测试时无意中改变行为。

    产品建议 - 我曾经使用免费的基于网络的产品 Junit Factory,但遗憾的是它现在关闭了。然而,该产品在http://www.agitar.com/solutions/products/automated_junit_generation.html 商业许可的 AgitarOne JUnit 生成器中继续存在

    对于您修复的每个错误或从现在开始添加的功能,使用 TDD 方法确保新代码设计为可测试,并将这些测试放在正常的测试源代码树中。

    李>

    现有代码也可能需要更改或重构以使其可作为添加新功能的一部分进行测试;您的冒烟测试将为您提供安全网,以防止行为退化或无意中的细微变化。

    通过 TDD 进行更改(错误修复或功能)时,完成后可能伴随的冒烟测试失败。验证由于所做的更改而导致的故障是否符合预期,并删除可读性较差的冒烟测试,因为您的手写单元测试已完全覆盖该改进的组件。确保您的测试覆盖率不会下降,只会保持不变或增加。

    在修复错误时,编写一个失败的单元测试,首先暴露错误。

【讨论】:

【参考方案10】:

我想首先说单元测试非常重要,因为它可以帮助您在错误潜入生产之前将其捕获。

确定重新引入错误的项目/模块区域。从那些项目开始编写测试。为新功能和错误修复编写测试非常有意义。

是否值得为现有的 正在生产中的解决方案?

是的。您将看到 bug 下降和维护变得更容易的效果

忽略测试会更好吗 对于这个项目并将其添加到 未来可能重写吗?

我建议从现在开始。

什么会更有益;开支 几周添加测试或几个 几周添加功能?

你问错问题了。当然,功能比什么都重要。但是,您应该询问是否花费几周时间添加测试将使我的系统更加稳定。这会帮助我的最终用户吗?是否有助于团队中的新开发人员了解项目,并确保他/她不会因为对变更的整体影响缺乏了解而引入错误。

【讨论】:

【参考方案11】:

我非常喜欢 Refactor the Low-hanging Fruit 来回答从哪里开始重构的问题。这是一种轻松进入更好设计的方法,而无需咀嚼太多。

我认为同样的逻辑也适用于 TDD - 或者只是单元测试:根据需要编写所需的测试;为新代码编写测试;为出现的错误编写测试。您担心忽略代码库中较难到达的区域,这当然是一种风险,但作为一种开始方式:开始吧!您可以使用代码覆盖工具降低风险,而且风险(在我看来)并不那么大,无论如何:如果您要覆盖错误,覆盖新代码,覆盖您正在查看的代码,那么您将涵盖最需要测试的代码。

【讨论】:

【参考方案12】: 是的,是的。当您开始添加新功能时,它可能会导致一些旧代码修改,并因此成为潜在错误的来源。 (请参阅第一个)在开始添加新功能之前,所有(或几乎)代码(理想情况下)都应包含在单元测试中。 (见第一个和第二个):)。一个新的宏大功能可以“摧毁”旧的工作代码。

【讨论】:

【参考方案13】:

是的,它可以:只需尝试确保您从现在开始编写的所有代码都经过适当的测试。

如果已经存在的代码需要修改并且可以测试,那么就这样做,但最好不要太费力地尝试为稳定的代码进行测试。这类事情往往会产生连锁反应,并可能失控。

【讨论】:

【参考方案14】:

为生产中的现有解决方案付出努力是否值得?
是的。但是您不必编写所有单元测试即可开始。只需一一添加即可。

忽略这个项目的测试并在未来可能的重写中添加它会更好吗?
不会。第一次添加破坏功能的代码时,您会后悔的。

什么会更有利;花几周时间添加测试还是花几周时间添加功能?
对于新功能(代码),它很简单。您首先编写单元测试,然后编写功能。 对于旧代码,您可以自行决定。您不必将所有单元测试都准备好......添加那些伤害你最没有的......时间(和错误)会告诉你必须关注哪一个;)

【讨论】:

【参考方案15】:

更新

在原始答案 6 年后,我的看法略有不同。

我认为将单元测试添加到您编写的所有新代码中是有意义的 - 然后重构您进行更改的地方以使其可测试。

一次性为所有现有代码编写测试无济于事 - 但不为您编写的新代码(或您修改的区域)编写测试也没有任何意义。在重构/添加内容时添加测试可能是在没有测试的现有项目中添加测试并使代码更易于维护的最佳方式。

较早的答案

我要在这里引起一些注意:)

首先你的项目是什么——如果它是一个编译器、一种语言、一个框架或其他任何在很长一段时间内不会在功能上发生变化的东西,那么我认为添加单元测试绝对很棒。

但是,如果您正在开发的应用程序可能需要更改功能(因为需求不断变化),那么付出额外的努力是没有意义的。

为什么?

    单元测试仅涵盖代码测试——无论代码是否达到其设计目的——它不能替代无论如何都必须进行的手动测试(以发现功能性错误、可用性问题和所有其他类型的问题)

    单元测试需要时间!现在我来自哪里,这是一种珍贵的商品——企业通常会选择更好的功能而不是完整的测试套件。

    如果您的应用程序甚至对用户非常有用,他们会请求更改 - 因此您将拥有可以做得更好、更快并且可能会做新事情的版本 - 也可能会进行大量重构,因为你的代码增长。在动态环境中维护完整的单元测试套件是一件令人头疼的事情。

    单元测试不会影响产品的感知质量——用户看到的质量。当然,您的方法可能与第一天一样工作,表示层和业务层之间的接口可能是原始的 - 但猜猜怎么着?用户不在乎!找一些真正的测试人员来测试你的应用程序。而且通常情况下,这些方法和接口迟早必须改变。

什么会更有益;花几周时间添加测试还是花几周时间添加功能? - 有很多事情可以比编写测试做得更好 - 编写新功能、提高性能、提高可用性、编写更好的帮助手册、解决待处理的错误等。

现在不要误会我的意思 - 如果您绝对肯定未来 100 年事情不会改变,请继续,让自己崩溃并编写这些测试。自动化测试对于 API 来说也是一个好主意,您绝对不想破坏第三方代码。在其他任何地方,它只是让我稍后发货!

【讨论】:

我敦促你阅读这篇文章 - joelonsoftware.com/items/2009/01/31.html 您更愿意“修复待处理的错误”还是阻止它们出现?正确的单元测试确实可以通过最大限度地减少错误修复所花费的时间来节省时间。 这是一个神话。如果你告诉我自动化单元测试是手动测试的替代品,那么你就大错特错了。如果没有错误,手动测试人员会记录什么? 是的,不要误会我的意思——我并不是说单元测试是绝对的浪费——关键是要考虑编写它们所花费的时间以及当你改变你的产品,他们真的会还款吗.. 对我来说,双方都试过了,答案是否定的,他们还款的速度不够快。【参考方案16】:

您不太可能拥有大量的测试覆盖率,因此您必须谨慎地考虑添加测试的位置:

正如您所提到的,当您发现错误时,是编写测试(重现它)然后修复错误的好时机。如果您看到测试重现了该错误,则可以确定这是一个好的、有效的测试。鉴于大部分错误是回归(50%?),编写回归测试几乎总是值得的。 当您深入某个代码区域进行修改时,是围绕它编写测试的好时机。根据代码的性质,不同的测试是合适的。一组好的建议is found here。

OTOH,不值得只是坐在那里围绕人们满意的代码编写测试——尤其是在没有人打算修改它的情况下。它只是没有增加价值(除了可能理解系统的行为)。

祝你好运!

【讨论】:

【参考方案17】:

你说你不想再买一本书。所以请阅读 Michael Feather 在working effectively with legacy code 上的文章。那就买书吧:)

【讨论】:

【参考方案18】:

如果我处于你的位置,我可能会采用由外而内的方法,从运行整个系统的功能测试开始。我会尝试使用 RSpec 之类的 BDD 规范语言重新记录系统的需求,然后编写测试以通过自动化用户界面来验证这些需求。

然后我会针对新发现的错误进行缺陷驱动开发,编写单元测试来重现问题,并处理错误直到测试通过。

对于新功能,我会坚持由外而内的方法:从 RSpec 中记录的功能开始,并通过自动化用户界面进行验证(最初当然会失败),然后添加更细粒度的单元测试作为实现移动。

我不是这个过程的专家,但根据我的一点经验,我可以告诉你,通过自动化 UI 测试进行 BDD 并不容易,但我认为值得付出努力,并且可能会在你的案例。

【讨论】:

【参考方案19】:

无论如何,我都不是经验丰富的 TDD 专家,但我当然会说,尽可能多地进行单元测试非常重要。由于代码已经到位,我将首先获得某种单元测试自动化。我使用 TeamCity 来执行我的项目中的所有测试,它可以很好地总结组件的运行情况。

有了这些,我将继续讨论那些不会失败的真正关键的业务逻辑类组件。就我而言,有一些基本的三角学问题需要针对各种输入来解决,所以我测试了这些问题。我这样做的原因是,当我熬夜时,很容易浪费时间深入挖掘真正不需要触及的代码深度,因为您知道它们已经过测试 对于所有可能的输入(在我的例子中,输入的数量是有限的)。

好的,现在希望您对这些关键部分感觉更好。我不会坐下来敲打所有的测试,而是会在它们出现时攻击它们。如果您遇到了一个需要修复的真正 PITA 错误,请为其编写单元测试并将它们排除在外。

在某些情况下,您会发现测试很困难,因为您无法从测试中实例化特定类,因此您必须模拟它。哦,但也许你不能轻易地模拟它,因为你没有写入接口。我将这些“哎呀”场景作为实现所述接口的机会,因为,嗯,这是一件好事。

从那里,我会使用代码覆盖工具配置您的构建服务器或任何您已配置的自动化。它们会创建令人讨厌的条形图,其中包含大的红色区域,您的覆盖范围很差。现在 100% 的覆盖率不是你的目标,100% 的覆盖率也不一定意味着你的代码是防弹的,但是当我有空闲时间时,红条肯定会激励我。 :)

【讨论】:

【参考方案20】:

有很多好的答案,所以我不会重复他们的内容。我检查了您的个人资料,您似乎是 C# .NET 开发人员。因此,我添加了对 Microsoft PEX and Moles 项目的引用,该项目可以帮助您为遗留代码自动生成单元测试。我知道自动生成不是最好的方式,但至少它是开始的方式。查看 MSDN 杂志上关于使用 PEX for legacy code 的这篇非常有趣的文章。

【讨论】:

【参考方案21】:

我建议阅读 TopTal 工程师的精彩 article,其中解释了从哪里开始添加测试:它包含大量数学,但基本思想是:

1) 测量代码的传入耦合 (CA)(一个类被其他类使用多少,这意味着破坏它会造成广泛的损害)

2) 衡量您的代码的循环复杂度 (CC)(更高的复杂度 = 更高的中断变化)

您需要识别具有高 CA 和 CC 的类,即具有函数 f(CA,CC) 并且两个指标之间差异最小的类应该被赋予测试覆盖率的最高优先级。

为什么?因为高 CA 但非常低的 CC 类非常重要,但不太可能中断。另一方面,低CA但高CC可能会破裂,但会造成较小的伤害。所以你要平衡。

【讨论】:

【参考方案22】:

这取决于... 进行单元测试很棒,但您需要考虑您的用户是谁以及他们愿意容忍什么,以便获得更无错误的产品。不可避免地,通过重构目前没有单元测试的代码,您将引入错误,许多用户会发现很难理解您正在使产品暂时有更多缺陷,以便从长远来看减少缺陷。最终还是用户说了算……

【讨论】:

【参考方案23】:

是的。 不。 添加测试。

采用更多的 TDD 方法实际上会更好地通知您添加新功能并使回归测试更容易的工作。看看吧!

【讨论】:

以上是关于单元测试能否成功添加到现有的生产项目中?如果是这样,如何以及是不是值得?的主要内容,如果未能解决你的问题,请参考以下文章

KMM:如何将共享模块引用到现有的 iOS 项目中

如何将测试添加到现有的 Typescript 项目,并让它显示在测试资源管理器中?

如何将带有 Jest 的 Vue Test Utils 添加到现有的 Vue-CLI 3 项目中?

如何将 Artisan cli 添加到现有的 laravel 项目?

可以将另一个项添加到现有的枚举类型中吗?

将带有分页的 UIScrollView 添加到现有的 UIViewController