似乎有两种完全不同的测试方法,我想列举这两种方法。
问题是,这些意见是在5年前(2007年)提出的,我感兴趣的是,自那时以来发生了什么变化,我应该走哪条路。
布兰登·基佩尔斯
理论上说,测试应该是与实现无关的。这将导致不太脆弱的测试,并实际测试结果(或行为)。 使用RSpec,我觉得完全模拟模型以测试控制器的常见方法最终会迫使您过多地研究控制器的实现。 这本身并不算太糟糕,但问题是,它对控制器的关联太多,无法决定如何使用该模型。为什么我的控制器调用Thing.new很重要?如果我的控制器决定拿Thing.create怎么办!还有营救路线?如果我的模型有一个特殊的初始化方法,比如Thing.build_with_foo,该怎么办?如果我更改了实现,我的行为规范就不会失败。 当您有嵌套资源并且每个控制器创建多个模型时,这个问题会变得更糟。我的一些安装方法最终会有15行或更多行长,并且非常脆弱。 RSpec的意图是将您的控制器逻辑与您的模型完全隔离开来,这在理论上听起来不错,但是对于一个集成的栈(如Rails )来说,它几乎是与谷粒相反的。特别是当您实践瘦控制器/fat模型规则时,控制器中的逻辑量会变得非常小,并且设置会变得非常庞大。 那么BDD想要做什么呢?退一步,我真正想测试的行为不是我的控制器调用Thing.new,而是给定参数X,它创建了一个新的东西并重定向到它。
大卫·克里姆斯基:
这都是关于权衡的。 AR选择继承而不是委托这一事实使我们处于一个测试绑定中--我们必须耦合到数据库,或者我们必须更密切地了解实现。我们接受这种设计选择是因为我们在表现力和干性方面获得了好处。 在艰难应对困境的过程中,我选择了更快的测试,代价是稍微脆弱一些。您选择的是不太脆弱的测试,而代价是它们运行得稍微慢一些。不管是哪种方式都是一种交换。 实际上,我每天运行测试数百次(如果不是数千次的话)(我使用自动测试并采取非常细粒度的步骤),并且我更改使用“new”还是“create”几乎从不。此外,由于粒状的步骤,新的模型出现是非常不稳定的。valid_thing_attrs方法减少了这一点带来的痛苦,但它仍然意味着每一个新的必需字段都意味着我必须更改valid_thing_attrs。 但是如果你的方法在实践中对你有用,那就好了!事实上,我强烈建议您发布一个带有生成器的插件,以您喜欢的方式生成示例。我相信很多人都会从中受益。
赖安·贝茨
出于好奇,您在测试/规范中多久使用一次模拟?也许我做错了什么,但我发现它是严重的限制。自从一个多月前切换到rSpec以来,我一直在执行他们在文档中推荐的操作,在这些文档中,控制器和视图层根本不访问数据库,并且模型完全模拟出来。这给了你一个不错的速度提高,并使一些事情更容易,但我发现的缺点,这远远超过了优点。自从使用了模拟之后,我的规范就变成了维护的噩梦。规范旨在测试行为,而不是实现。我不在乎是否调用了一个方法,我只是想确保结果的输出是正确的。因为嘲弄使规范对实现挑剔,所以如果不不断地返回并“修复”规范,就不可能进行简单的重构(不改变行为)。对于规范/测试应该包括什么,我非常固执己见。只有当应用程序中断时,测试才会中断。这就是为什么我很难测试视图层的原因之一,因为我发现它太僵化了。当在视图中改变一些小东西时,它通常会导致测试中断而不破坏应用程序。我也发现了同样的问题。最重要的是,我今天才意识到,嘲弄/固执类方法(有时)停留在规范之间。规格应该是独立的,不受其他规格的影响。这违反了规则,并导致棘手的错误。我从这一切中学到了什么?在使用嘲弄的地方要小心。顽固不化并不是那么糟糕,但仍然存在一些同样的问题。 在过去的几个小时里,我从我的规格中删除了几乎所有的模拟。我还使用控制器规范中的"integrate_views“将控制器和视图规范合并为一个。我也是加载每个控制器规范的所有夹具,所以有一些测试数据来填充视图。最终结果是什么?我的规范更短、更简单、更一致、更不严格,并且它们一起测试整个堆栈(模型、视图、控制器),这样就不会有错误从裂缝中溜走。我不是说这是每个人的“正确”方式。如果您的项目需要非常严格的规范情况,那么它可能不适合您,但在我的例子中,这比我使用模拟之前的情况要好得多。我仍然认为固执是一个很好的解决办法,在一些地方,所以我仍在这样做。
发布于 2012-06-13 12:47:53
我认为这三种观点都是完全正确的。Ryan和我一直在为嘲弄的可维护性而挣扎,而David认为维护的代价是值得的,因为速度的提高是值得的。
但这些权衡是一个更深层次问题的症状,大卫曾在2007年提到过这个问题: ActiveRecord。ActiveRecord的设计鼓励您创建上帝对象,这些对象做得太多,对系统的其他部分了解太多,并且有太多的表面积。这导致测试需要测试的太多,对系统其他部分了解太多,或者太慢,或者太脆弱。
那么解决办法是什么呢?尽可能多地将应用程序与框架分开。编写大量的小类来建模您的域,并且不继承任何东西。每个对象都应该具有有限的表面积(不超过几个方法)和通过构造函数传递的显式依赖关系。
使用这种方法,我只编写了两种类型的测试:独立单元测试和全堆栈系统测试。在隔离测试中,我模拟或存根所有不是被测试对象的东西。这些测试速度非常快,甚至不需要加载整个Rails环境。完整的堆栈测试对整个系统进行了测试。他们速度慢得令人痛苦,失败时会给出无用的反馈。我只写一些必要的东西,但足够让我相信我所有经过良好测试的对象都能很好地集成。
不幸的是,我不能向您指出一个做得很好的示例项目(还)。我在为什么我们的代码闻起来上的演讲中谈到了这个问题,看了Corey在快速钢轨试验上的演讲,我强烈推荐阅读基于测试的面向对象软件的成长。
发布于 2012-06-13 18:37:53
感谢你汇编了2007年的引文。回顾过去是很有趣的。
我目前的测试方法包含在这一集RailsCasts中,我对此非常满意。总之,我有两个级别的测试。
注意,没有控制器或视图规范。我觉得这些都在请求规范中得到了充分的涵盖。
既然很少有人嘲笑,我该如何保持测试的快速性?这里有一些提示。
build
,必要时只切换到create
。:focus
标记在RSpec中限制在特定区域工作时运行的测试。如果它是一个大型测试套件,那么将Guardfile中的all_after_pass: false, all_on_start: false
设置为只在需要时运行它们。我发现嘲笑增加了测试的脆性,这就是为什么我要避免的原因。的确,它可以作为OO设计的辅助工具,但在Rails应用程序的结构中,这感觉并不那么有效。相反,我非常依赖重构,让代码本身告诉我设计应该如何进行。
这种方法在中小型Rails应用程序上工作得最好,没有广泛的、复杂的域逻辑。
发布于 2012-06-13 21:35:04
伟大的问题和伟大的讨论。@ryanb和@b收藏者提到他们只编写了两种类型的测试。我采取了类似的方法,但有第三种测试:
至于嘲笑,我没有“一刀切”的方法。在过去,我肯定会过度模仿,但我仍然认为它是一种非常有用的技术,特别是在使用rspec-开火之类的东西时。通常,我会随意地嘲笑协作者扮演角色(特别是如果我拥有它们,并且它们是服务对象),并在大多数其他情况下尽量避免它。
在过去的一年中,我的测试中最大的变化可能是受到DAS的启发:以前我有一个加载整个环境的spec_helper.rb
,现在我只显式地加载测试中的类(以及任何依赖项)。除了改进的测试速度(这确实产生了巨大的变化!)它帮助我识别测试中的类在什么时候引入了太多的依赖关系。
https://stackoverflow.com/questions/11006888
复制相似问题