unit testing通过
我们试图将unit testing引入到我们当前的项目中,但似乎没有工作。 额外的代码似乎已经成为一个维护头痛,因为当我们的内部框架变化,我们必须周旋,并修复任何挂起它的unit testing。
我们有一个抽象基类,用于unit testing我们的控制器,作为调用子类的抽象方法实现的模板,即框架调用Initialize,所以我们的控制器类都有自己的Initialize方法。
我曾经是一个unit testing的倡导者,但似乎并没有在我们目前的项目上工作。
任何人都可以帮助确定问题,以及我们如何使unit testing为我们工作,而不是反对我们?
提示:
避免写程序代码
如果程序风格的代码严重依赖于全局状态,或者深陷在丑陋的方法之中,那么testing可能会成为一个难题。 如果您使用OO语言编写代码,请使用OO构造来有效地减less这种情况。
- 尽可能避免全局状态。
- 避免静态,因为它们往往会影响你的代码库,并最终导致事情变得不可能是静态的。 他们也膨胀你的testing环境(见下文)。
- 有效地利用多态来防止过多的if和flags
find哪些变化,封装它,并将其与保持相同的分开。
代码中的阻塞点比其他部分更频繁地变化。 在您的代码库中执行此操作,您的testing将变得更加健康。
- 良好的封装导致良好的,松散耦合的devise。
- 重构和模块化。
- 保持testing小而专注。
围绕testing的上下文越大,维护越困难。
尽你所能地缩小testing和执行它们的周围环境。
- 使用组合方法重构来testing更小的代码块。
- 你是否正在使用像TestNG或JUnit4这样的更新的testing框架? 它们允许您通过在testing生命周期中提供更多细粒度的钩子来消除testing中的重复。
- 调查使用testing双打(嘲笑,假货,存根),以减lesstesting环境的大小。
- 调查testing数据生成器模式。
从testing中删除重复,但要确保它们保持焦点。
您可能无法删除所有重复项,但仍会尝试将其删除。 确保你不要删除那么多的重复,以至于不能进来,并且一目了然地告诉你testing的结果。 (请参阅Paul Wheaton的“邪恶unit testing”文章,了解相同概念的另一种解释。)
- 没有人会想要解决一个testing,如果他们不知道它在做什么。
- 按照安排,行动,断言模式。
- 每个testing只使用一个断言。
在正确的级别testing您正在validation的内容。
考虑一下logging和回放Seleniumtesting中涉及到的复杂性,以及与您testing单一方法相比您可能会改变什么。
- 相互依赖。
- 使用dependency injection/控制反转。
- 使用testing双打来初始化一个对象进行testing,并确保你单独testing单个代码单元。
- 确保你正在写相关的testing
- “spring的陷阱”通过引入一个错误的目的,并确保它被抓住了testing。
- 另请参阅: 集成testing是一个骗局
知道何时使用基于状态和基于交互的testing
真正的unit testing需要真正的隔离 unit testing不会打到数据库或打开套接字。 停下来嘲笑这些互动。 确认您正确地与您的协作者交谈,而不是来自此方法调用的正确结果是“42”。
演示testing驾驶代码
对于一个给定的团队是否会采取testing驱动所有代码或者为每一行代码编写“testing”的问题,都有争议。 但是他们应该先写一些testing吗? 绝对。 有些情况下,testing优先无疑是解决问题的最佳方法。
- 尝试这个练习: TDD就像你的意思 (另一个描述)
- 另见: testing驱动的发展和科学的方法
资源:
- testing由Lasse Koskela 驱动
- 越来越多的OO软件,由史蒂夫·弗里曼和纳特·普赖斯的testing指导
- 通过Michael Feathers的遗留代码有效地工作
- 以Gojko Adzic为例
- 博客检查: 杰伊·菲尔德 , 安迪·格洛弗 , 纳特·普赖斯
- 正如其他答案中已经提到的:
- XUnit模式
- testing嗅觉
- Googletesting博客
- Miskov Hevery的“ OO Designability for Testability ”
- Paul Wheaton的“ 邪恶unit testing ”
- JB Rainsberger的“ 集成testing是一个骗局 ”
- JB Rainsberger的“ 软件devise经济学 ”
- Rick Mugridge的“ 试驾发展与科学方法 ”
- “ TDD就好像你想的那样 ”由Keith Braithwaite原本的演习,也由Gojko Adzic
你testing足够小的代码单元? 你不应该看到太多的变化,除非你从根本上改变核心代码中的一切。
一旦事情稳定下来,你会更加欣赏unit testing,但即使是现在,你的testing也突出了你的框架的变化在何种程度上被传播。
这是值得的,尽可能地坚持下去。
没有更多的信息,很难做出体面的刺你为什么遭受这些问题。 有时,不可避免的是,改变接口等将会破坏很多事情,有时甚至是devise问题。
尝试对您所看到的失败进行分类是个好主意。 你有什么问题? 例如,是否由于API更改而进行testing维护(如在重构后进行编译!),还是由于API的行为改变? 如果您可以看到一个模式,那么您可以尝试更改生产代码的devise,或者更好地将testing隔离开来。
如果在很多地方改变一些东西会给你的testing套件造成不可估量的破坏,那么你可以做一些事情(其中大部分只是常见的unit testing技巧):
-
开发小部分代码并testing小部分代码。 在有意义的地方抽取接口或基类,以使代码单元具有“接缝”。 你需要更多的依赖关系(或者更糟糕的是,在类中使用'new'来实例化),你的代码会更容易被改变。 如果每个代码单元都有一些依赖关系(有时候是一对或者一个都没有),那么它就更好地与变化隔离了。
-
只有断言什么testing需要。 不要断言中间,偶然或无关的状态。 按合同进行devise和按合同进行testing(例如,如果您正在testing堆栈popup方法,则不要在推送后testing计数属性 – 这应该在单独的testing中)。
我看到这个问题相当多,特别是如果每个testing是一个变种。 如果任何偶然的状态发生变化,它就会破坏所有在其上的声明(无论声明是否需要)。
-
就像正常的代码一样,在unit testing中使用工厂和构build器。 我了解到,当大约40个testing需要API更改后更新的构造函数调用…
-
同样重要的是,首先使用前门。 如果可用,您的testing应始终使用正常状态。 只有在必须使用基于交互的testing时(即没有状态来validation)。
无论如何,这个要点是我会试图找出为什么/在哪里testing打破,并从那里去。 尽最大努力避免变化。
unit testing的好处之一是,当你做这样的改变时,你可以certificate你不会破坏你的代码。 你必须保持你的testing与你的框架保持同步,但是这个相当平凡的工作比试图弄清楚你重构时破坏了什么要容易得多。
我会坚持你坚持TDD。 尝试检查您的unit testing框架与您的团队做一个RCA(根本原因分析),并确定该区域。
修复套件级别的unit testing代码,不要经常更改您的代码,特别是函数名称或其他模块。
如果你可以分享你的案例研究,那么将不胜感激,那么我们可以在问题领域挖掘更多?
好问题!
devise良好的unit testing与devise软件本身一样困难。 这很less被开发人员所认可,所以结果往往是急速编写的unit testing,每当被测系统发生变化时都需要维护。 所以,解决问题的一部分可能会花费更多的时间来改进unit testing的devise。
我可以推荐一本值得作为“unit testingdevise模式”的书
HTH
如果问题是您的testing与实际代码已经过时,您可以执行以下一项或两项操作:
- 训练所有开发人员不要通过不更新unit testing的代码评论。
- 设置一个自动testing框,在每次签入后运行整套unit testing,并通过电子邮件发送中断构build。 (我们以前认为这只是“大男孩”,但我们在专用的盒子上使用了开源软件包。)
那么,如果代码中的逻辑发生了变化,并且您已经为这些代码段编写了testing,那么我会假设testing将需要更改以检查新的逻辑。 unit testing应该是testing代码逻辑的相当简单的代码。
你的unit testing正在做他们应该做的事情。 由于框架,即时代码或其他外部来源的变化,导致行为发生任何中断。 这个应该做的是帮助你确定行为是否改变,unit testing是否需要相应修改,或者是否引入了错误,从而导致unit testing失败,需要修正。
不要放弃,而现在令人沮丧的是,好处将会实现。
我不确定哪些具体问题会使代码难以维护,但是当我遇到类似的问题时,我可以分享一些自己的经验。 我最终了解到,缺乏可testing性主要是由于被testing的类的一些devise问题:
- 使用具体类而不是接口
- 使用单身人士
- 调用大量用于业务逻辑和数据访问的静态方法而不是接口方法
正因为如此,我发现通常我的testing是打破的 – 不是因为被testing的类别发生了变化 – 而是由于被testing类正在调用的其他类的变化。 一般来说,重构类来请求它们的数据依赖性和使用模拟对象进行testing(EasyMock等人用于Java)使得testing更加集中和可维护。 我真的很喜欢这个主题的一些网站:
- Googletesting博客
- 编写可testing代码的指南
为什么每次更改框架时都必须更改unit testing? 难道这不是相反吗?
如果您使用的是TDD,那么您应该首先确定您的testing正在testing错误的行为,并且应该确认所需的行为是否存在。 现在你已经修复了你的testing,你的testing失败了,你必须在你的框架中填充bug,直到你的testing再次通过。
一切都与价格当然。 在这个早期的开发阶段,很多unit testing都是必须要改变的。
您可能想要查看一些代码来执行更多的封装,创build更less的依赖关系等。
当你接近生产date,你会很高兴你有这些testing,相信我:)
你的unit testing是不是太黑盒? 我的意思是…让我举个例子:假设你正在testing某种容器的unit testing,你使用容器的get()方法来validation实际存储的新项目,还是设法得到一个句柄实际的存储直接检索存储的项目? 后者做出脆弱的testing:当你改变实现时,你打破了testing。
您应该根据接口进行testing,而不是内部实现。
而当你改变框架时,最好先试着改变testing,然后再改变框架。
我会build议投资一个testing自动化工具。 如果您正在使用持续集成,则可以使其一起工作。 有一些工具可以扫描您的代码库,并为您生成testing。 然后将运行它们。 这种方法的缺点是它太泛化了。 因为在许多情况下unit testing的目的是打破系统。 我写了大量的testing,是的,如果代码库发生变化,我必须改变它们。
自动化工具有一个很好的线,你会明确有更好的代码覆盖率。
但是,基于testing的开发人员也会testing系统的完整性。
希望这可以帮助。
如果你的代码真的很难testing,并且testing代码崩溃或者需要很多努力来保持同步,那么你就有更大的问题。
考虑使用抽取方法重构来抽出一小块代码来做一件事,而且只做一件事。 没有依赖关系,并把你的testing写入那些小的方法。
额外的代码似乎已经成为一个维护头痛,因为当我们的内部框架变化,我们必须周旋,并修复任何挂起它的unit testing。
另一种方法是当您的框架更改时,您不testing更改。 或者你根本不testing框架。 那是你要的吗?
您可以尝试重构您的框架,以便它可以由可以独立testing的小块组成。 那么当你的框架发生变化的时候,你希望(a)更less的片断发生变化,或者(b)这些变化主要是以片断的组合方式。 无论哪种方式将使您更好地重用代码和testing。 但真正的智力努力是涉及的; 不要指望它很容易。
我发现,除非你使用鼓励编写非常小的类的IoC / DI方法,并且遵循单一责任原则,unit testing最终会testing多个类的相互作用,这使得它们非常复杂,因此是脆弱的。
我的观点是,许多新颖的软件开发技术只有在一起使用时才起作用。 尤其是MVC,ORM,IoC,unit testing和嘲弄。 DDD(现代原始意义上的)和TDD / BDD更独立,所以你可以使用它们。
有时在deviseTDDtesting时会对应用程序本身的devise提出质疑。 检查你的类是否devise得很好,你的方法一次只能执行一件事……用好的devise,编写代码来testing简单的方法和类应该很简单。
我一直在想这个话题。 我非常喜欢unit testing的价值,但不是严格的TDD。 在我看来,在某种程度上,您可能正在进行探索性编程,而将事物划分为类/接口的方式将需要改变。 如果你为旧的类结构进行了大量的unit testing,那么这会增加对重构的惰性,并且很难放弃额外的代码等等。