我们已经尝试将单元测试引入到我们当前的项目中,但它似乎没有起作用。额外的代码似乎已成为一个维护问题,因为当我们的内部框架发生变化时,我们必须绕过并修复任何挂起它的单元测试。
我们有一个抽象基类,用于对我们的控制器进行单元测试,这些控制器充当调用子类抽象方法实现的模板,即Framework调用Initialize,因此我们的控制器类都有自己的Initialize方法。
我曾经是单位测试的倡导者,但它似乎并不适用于我们当前的项目。
任何人都可以帮助确定问题以及我们如何让单元测试对我们有用而不是对我们有用吗?
答案 0 :(得分:107)
如果测试是针对程序风格的代码编写的,那么测试就可以成为一种保证,这种代码很大程度上依赖于全局状态,或者位于丑陋方法的深层。 如果您使用OO语言编写代码,use OO constructs有效地减少了这一点。
代码中的阻塞点比其他部分更频繁地改变。在您的代码库中执行此操作,您的测试将变得更加健康。
尽你所能缩小测试及其执行的周围环境。
您可能无法删除所有重复项,但仍会尝试将其删除,因为它会导致痛苦。确保你没有删除那么多的重复,以致有人无法进入,并一目了然地告诉测试的内容。 (有关相同概念的替代解释,请参阅Paul Wheaton的"Evil Unit Tests"文章。)
考虑一下记录和回放Selenium测试所涉及的复杂性,以及在测试单个方法时可能发生的变化。
真正的单元测试需要真正的隔离。单元测试不会打到数据库或打开套接字。停止嘲笑这些互动。确认您正确地与协作者交谈,而不是此方法调用的正确结果是“42”。
对于某个特定团队是否会采用测试驱动所有代码,或者为每行代码编写“测试优先”而进行辩论是有争议的。但他们应该先写一些至少一些测试吗?绝对。在某些情况下,测试优先无疑是解决问题的最佳方式。
答案 1 :(得分:19)
您是否在测试足够小的代码单元?除非您从根本上改变核心代码中的所有内容,否则您不应该看到太多更改。
一旦情况稳定,您会更加欣赏单元测试,但即使是现在您的测试也会突出显示您的框架变更的传播程度。
值得,尽可能坚持下去。
答案 2 :(得分:12)
如果没有更多的信息,很难对你为什么遇到这些问题做出体面的准备。有时,改变界面等不可避免地会破坏很多东西,有时则会出现设计问题。
尝试对您所看到的失败进行分类是一个好主意。你有什么问题?例如。它是由于API的变化而进行的测试维护(如在重构后编译它们!),还是由于API的行为变化?如果您可以看到模式,那么您可以尝试更改生产代码的设计,或者更好地隔离测试以防止更改。
如果在很多地方改变一些东西会给你的测试套件造成无法估量的破坏,你可以做一些事情(大多数只是常见的单元测试技巧):
开发小型代码单元并进行测试 小单位代码。提取 它所在的接口或基类 有意义的是代码单元 其中有'接缝'。更多 你必须引入的依赖(或 更糟糕的是,在课堂内实例化 使用'新'),越暴露 改变你的代码。如果每个 代码单元有一把 依赖(有时是一对或 根本没有)那么它会更好 绝不改变。
只有在测试中断言 需要。不要断言中间, 偶然或无关的状态。由...设计 合同和合同测试(例如 如果你正在测试一个堆栈弹出方法, 之后不要测试计数属性 推 - 这应该是一个 单独测试)。
我看到了这个问题 相当多,特别是如果每次测试 是一个变种。如果有的话 偶然的状态变化,它打破了 断言的一切 (是否需要断言或 不是)。
与普通代码一样,使用工厂和构建器 在你的单元测试中。我在大约40次测试时学到了这一点 需要在API更改后更新构造函数调用...
同样重要的是,使用正面 门先。你的测试应该永远 如果可用,请使用正常状态。只在必要时才使用基于交互的测试(即没有要验证的状态)。
无论如何,我的要点是,我试图找出测试破坏的原因/地点并从那里开始。尽力使自己免受变化。
答案 3 :(得分:8)
单元测试的一个好处是,当您进行这样的更改时,您可以证明您没有破坏您的代码。你必须让你的测试与你的框架保持同步,但是这个相当平凡的工作比试图弄清楚你重构时发生的事情要容易得多。
答案 4 :(得分:4)
我坚持要坚持使用TDD。尝试检查您的单元测试框架,与您的团队一起做一个RCA(根本原因分析)并确定该区域。
在套件级别修复单元测试代码,不要经常更改代码,特别是函数名称或其他模块。
如果您能够很好地分享您的案例研究,那么我们会很感激,那么我们可以在问题区域挖掘更多信息吗?
答案 5 :(得分:4)
好问题!
设计好的单元测试很难设计软件本身。开发人员很少承认这一点,因此结果通常是仓促编写的单元测试,只要被测系统发生变化,就需要进行维护。因此,解决问题的部分原因可能是花费更多时间来改进单元测试的设计。
我可以推荐一本值得称之为The Design Patterns of Unit-Testing
的好书HTH
答案 6 :(得分:4)
如果问题是您的测试与实际代码过时,您可以执行以下一项或两项:
答案 7 :(得分:3)
您的单元测试正在做他们应该做的事情。由于框架,即时代码或其他外部资源的变化,导致行为中的任何中断。这应该做的是帮助您确定行为是否确实发生了变化,并且需要相应地修改单元测试,或者是否引入了错误,从而导致单元测试失败并需要更正。
不要放弃,而现在令人沮丧的是,这种好处将会实现。
答案 8 :(得分:3)
如果代码中的逻辑已经改变,并且您已经为这些代码片段编写了测试,我会假设需要更改测试以检查新逻辑。单元测试应该是相当简单的代码,用于测试代码的逻辑。
答案 9 :(得分:2)
为什么每次更改框架时都必须更改单元测试? 这不应该是另一种方式吗?
如果您正在使用TDD,那么您应该首先确定您的测试正在测试错误的行为,并且他们应该确认存在所需的行为。现在你已经修复了测试,你的测试失败了,你必须去除框架中的错误,直到你的测试再次通过。
答案 10 :(得分:2)
我不确定难以维护代码测试的具体问题,但是当我遇到类似的测试问题时,我可以分享一些自己的经验。我最终了解到缺乏可测试性主要是由于测试类的一些设计问题:
正因为如此,我发现通常我的测试都在破坏 - 不是因为测试中的类发生了变化 - 而是由于被测试的类正在调用的其他类的变化。通常,重构类要求它们的数据依赖性并使用模拟对象进行测试(EasyMock等用于Java)使得测试更加集中和可维护。我真的很喜欢这个主题的一些网站:
答案 11 :(得分:1)
我建议投资测试自动化工具。如果您使用持续集成,则可以使其协同工作。那里有工具可以扫描你的代码库并为你生成测试。然后将运行它们。这种方法的缺点是它过于通用。因为在很多情况下单元测试的目的是打破系统。 我写了很多测试,是的,如果代码库发生变化,我必须更改它们。
自动化工具有一个很好的界限,你肯定会有更好的代码覆盖率。
然而,通过基于wrttien develper的测试,您还将测试系统完整性。
希望这有帮助。
答案 12 :(得分:1)
我发现,除非你使用鼓励编写非常小的类的IoC / DI方法,并且虔诚地遵循单一责任原则,否则单元测试最终会测试多个类的交互,这使得它们非常复杂,因此很脆弱。
我的观点是,许多新颖的软件开发技术仅在一起使用时才有效。特别是MVC,ORM,IoC,单元测试和模拟。 DDD(在现代原始意义上)和TDD / BDD更加独立,因此您可以使用它们。
答案 13 :(得分:1)
额外的代码似乎已成为一种维护问题,因为当我们的内部框架发生变化时,我们必须绕过并修复任何挂起它的单元测试。
另一种方法是,当您的Framework发生更改时,您不会测试更改。或者你根本不测试框架。那是你要的吗?
您可以尝试重构您的框架,使其由可以独立测试的较小部分组成。然后,当您的框架发生变化时,您希望(a)更少的部分发生变化,或者(b)变化主要在于作品的组合方式。无论哪种方式都可以让您更好地重用代码和测试。但真正的智力工作涉及到;不要指望它很容易。
答案 14 :(得分:1)
如果您的代码很难测试并且测试代码中断或需要花费很多精力来保持同步,那么您就会遇到更大的问题。
考虑使用提取方法重构来抽出一小块代码来完成一件事而且只做一件事;没有依赖关系,并将测试写入那些小方法。
答案 15 :(得分:1)
你的单元测试不是太黑盒了吗?我的意思是......让我举一个例子:假设您正在对某种容器进行单元测试,您是否使用容器的get()方法来验证新项目是否实际存储,或者您是否设法获取句柄实际存储直接检索项目存储的位置?后者进行了脆弱的测试:当你改变实现时,你就会破坏测试。
您应该测试接口,而不是内部实现。
当您更改框架时,最好先尝试更改测试,然后再更改框架。
答案 16 :(得分:1)
当然,一切都带有价格。在开发的早期阶段,很多单元测试都必须改变。
您可能希望查看代码的某些部分以进行更多封装,创建更少的依赖性等。
当你接近生产日期时,你会很高兴你有这些测试,相信我:)
答案 17 :(得分:0)
我自己一直在考虑这个话题。我非常看重单位测试的价值,但不是严格的TDD。在我看来,在某种程度上,你可能正在进行探索式编程,在这种情况下你将事物划分为类/接口的方式需要改变。如果你在旧的类结构的单元测试中投入了大量的时间,那就是增加了对重构的惯性,放弃额外的代码会很痛苦等等。
答案 18 :(得分:0)
有时设计TDD测试会对应用程序本身的设计提出质疑。检查你的类是否设计得很好,你的方法只执行一件事......通过良好的设计,编写代码来测试简单的方法和类应该很简单。