testing:如何把注意力放在行为上而不是在不失速的情况下实现?

看来,有两种完全不同的testing方法,我想引用他们两个。

问题是,这些意见是在5年前(2007年)提出的,我对此感兴趣,自那以后发生了什么变化,我该走哪条路。

布兰登老板 :

理论是testing应该是不可知的实现。 这导致testing不太脆,实际testing结果(或行为)。

对于RSpec,我觉得完全模拟你的模型来testing你的控制器最终会迫使你过多地考虑你的控制器的实现。

这本身并不是太糟糕,但问题在于,它太多地进入控制器,以决定如何使用模型。 为什么我的控制器会调用Thing.new? 如果我的控制器决定采取Thing.create怎么办! 和救援路线? 如果我的模型有一个特殊的初始化方法,如Thing.build_with_foo? 我的行为规范不应该失败,如果我改变实施。

当你有嵌套的资源并且为每个控制器创build多个模型时,这个问题变得更糟。 我的一些设置方法最终是15行或更长的行,非常脆弱。

RSpec的目的是将你的控制器逻辑从你的模型中完全隔离出来,这在理论上听起来不错,但是几乎和Rails这样的集成堆栈运行在一起。 特别是如果你练习瘦身控制器/胖模型的规则,控制器中的逻辑数量变得非常小,并且设置变得巨大。

那么什么是BDD想要做的? 退一步说,我真正想testing的行为不是我的控制器调用Thing.new,而是给定参数X,它创build一个新的东西并redirect到它。

大卫Chelimsky:

这全是关于权衡。

ARselectinheritance而不是授权的事实使我们处于testing的束缚之中 – 我们必须将其与数据库耦合起来,否则我们必须更加贴近实施。 我们接受这种deviseselect,因为我们从performance力和干爽中获得好处。

为了应对困境,我select了更快速的testing,代价是稍微更脆弱。 你select较脆弱的testing,代价是稍微慢一些。 这是一个折中的方法。

在实践中,我每天运行数百次,甚至数千次(我使用自动testing并采取非常细化的步骤),并且改变我是否几乎从不使用“新build”或“创build”。 同样由于细化的步骤,出现的新模型起初非常不稳定。 valid_thing_attrs方法最大限度地减less了这一点的痛苦,但它仍然意味着每个新的必需字段意味着我必须更改valid_thing_attrs。

但如果你的方法在实践中为你工作,那么它的好! 事实上,我强烈build议你发布一个插件,用你喜欢它们的方式生成例子。 我相信很多人会从中受益。

瑞安·贝茨 :

出于好奇,你在testing/规格中多久使用mock? 也许我做错了什么,但我觉得这是严重的限制。 从一个月前转换到rSpec,我一直在做他们推荐的文档,控制器和视图层根本没有碰到数据库,模型被完全嘲弄了。 这给你一个很好的速度提升,使一些事情更容易,但我发现这样做的弊端远远超过专业人士。 自从使用mock之后,我的规格变成了维护噩梦。 规范是为了testing行为,而不是实现。 我不在乎一个方法是否被调用,我只是想确保输出结果是正确的。 因为嘲讽使得规范对实现有挑剔,所以不必经常返回并“修复”规范就可以做简单的重构(不会改变行为)。 我非常想知道一个规范/testing应该包括什么。 只有当应用程序中断时,testing才会中断。 这是为什么我很难testing视图层的一个原因,因为我觉得它太僵硬了。 在视图中更改小的东西时,通常会导致testing中断,而不会中断应用程序。 我发现与嘲笑相同的问题。 最重要的是,我今天才意识到,嘲笑/扼杀类方法(有时)在规范之间。 规格应该是自包含的,不受其他规格的影响。 这打破了这个规则,并导致了棘手的错误。 我从这些中学到了什么? 小心你使用嘲笑。 存根并不坏,但仍然有一些相同的问题。

我花了几个小时,几乎所有的嘲笑从我的规格。 我还将控制器和查看规格合并为一个,在控制器规格中使用“integrate_views”。 我也加载了每个控制器规格的所有灯具,所以有一些testing数据来填补意见。 最终结果? 我的规格更短,更简单,更一致,不太僵化,他们一起testing整个堆栈(模型,视图,控制器),所以没有错误可以通过裂缝。 我不是说这是对每个人都“正确”的方式。 如果你的项目需要一个非常严格的规范案例,那么它可能不适合你,但在我的情况下,这是世界上比我以前使用模拟更好。 我仍然认为在几个地方存根是一个很好的解决scheme,所以我仍然这样做。

我认为这三个意见都是完全有效的。 Ryan和我一直在为嘲笑的可维护性而苦恼,而David认为维护权衡对于速度的提高是值得的。

但是这些权衡是大卫在2007年提到的一个更深层次的问题的症状:ActiveRecord。 ActiveRecord的devise鼓励你创造太多的神物,对系统的其他部分了解太多,并且有太多的表面积。 这导致testing有太多的testing,对系统的其他部分了解太多,而且太慢或太脆弱。

那么解决scheme是什么? 尽可能多的将您的应用程序从框架中分离出来。 写很多模拟你的域的小类,不要inheritance任何东西。 每个对象应该具有有限的表面积(不超过几个方法),并通过构造函数传入显式依赖关系。

采用这种方法,我只写了两种types的testing:隔离unit testing和全堆栈系统testing。 在隔离testing中,我嘲笑或残缺所有不是被测对象的东西。 这些testing非常快速,甚至不需要加载整个Rails环境。 整个堆栈testing运行整个系统。 当他们失败时,他们痛苦地慢,并且给予无用的反馈。 我尽可能less写,但足以让我相信,我所有的testing对象都很好地融合在一起。

不幸的是,我不能指出你做了一个很好的例子。 我在关于为什么我们的代码闻起来的演讲中谈了一些,看Corey Haines关于快速轨道testing的演讲,我强烈推荐阅读“以testing为导向的面向对象的软件” 。

感谢您汇编2007年的报价。回头看看很有意思。

我目前的testing方法已经涵盖在这个我很满意的RailsCasts情节中。 总之,我有两个级别的testing。

  • 高级别:我使用RSpec,水豚和录像机的请求规格。 testing可以被标记为根据需要执行JavaScript。 这里避免嘲笑,因为目标是testing整个堆栈。 每个控制器动作至lesstesting一次,也许是几次。

  • 低级别:这是testing所有复杂的逻辑的地方 – 主要是模型和助手。 我也避免在这里嘲笑。 testing会在必要时触击数据库或周围的物体。

注意,没有控制器或查看规格。 我觉得这些在请求规范中有充分的介绍。

既然有点嘲讽,我该如何保持快速的testing呢? 这里有一些提示。

  • 避免高级testing中的过度分支逻辑。 任何复杂的逻辑都应该移到较低的层面。

  • 当生成logging时(例如Factory Girl),使用build第一个,必要时只用switch来create

  • 使用Spork的 Guard来跳过Rails的启动时间。 相关的testing通常在保存文件后的几秒钟内完成。 在RSpec中使用:focus标签来限制在特定区域工作时运行的testing。 如果是大型testing套件,则在all_after_pass: false, all_on_start: false设置为仅在需要时运行它们。

  • 每个testing使用多个断言。 为每个断言执行相同的设置代码将大大增加testing时间。 RSpec将打印出失败的行,因此很容易find它。

我发现嘲笑增加了testing的脆弱性,这就是为什么我要避免它。 诚然,它可以作为面向对象devise的一个辅助工具,但是在Rails应用程序的结构中,这并没有那么有效。 相反,我很大程度上依赖于重构,并让代码本身告诉我该如何去devise。

这种方法最适合于中小型Rails应用程序,而不需要大量复杂的领域逻辑。

伟大的问题和伟大的讨论。 @ryanb和@bkeepers提到他们只写两种types的testing。 我采取了类似的方法,但有第三种types的testing:

  • unit testing:单独的testing,通常,但不总是,平原ruby对象。 我的unit testing不涉及数据库,第三方API调用,或任何其他外部的东西。
  • 集成testing:这些testing仍然集中在testing一个类; 不同之处在于他们将这个类与我在unit testing中避免的外部因素整合在一起。 我的模型往往有unit testing和集成testing,unit testing的重点是纯逻辑,可以在不涉及数据库的情况下进行testing,集成testing将涉及数据库。 另外,我倾向于使用集成testing来testing第三方API封装,使用VCR来保持testing的快速和确定性,但是让CI构buildHTTP请求以实现(捕获任何API更改)。
  • 验收testing:整个function的端到端testing。 这不仅仅是通过水豚的UItesting; 我在我的gem中也是这样,它可能根本没有HTML UI。 在这些情况下,这可以执行任何gem端到端的操作。 我也倾向于在这些testing中使用VCR(如果他们发出外部HTTP请求),并且像在我的集成testing中那样,我的CI构build被设置为使得HTTP请求成为真实的。

就嘲笑而言,我没有“一刀切”的方法。 我过去肯定是过度的,但我仍然觉得这是一个非常有用的技术,特别是在使用类似rspec-fire的东西的时候。 一般来说,我模仿合作者自由地扮演angular色(特别是如果我拥有他们,他们是服务对象),并试图避免在大多数情况下。

在过去一年左右,我testing的最大变化可能是DAS的启发:而我曾经有一个加载整个环境的spec_helper.rb ,现在我明确地加载了testing下的类(以及任何依赖关系)。 除了改进的testing速度(这确实造成了巨大的差异!)它帮助我确定何时我的testing类正在拉太多的依赖关系。