首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >测试:如何在不失去速度的情况下专注于行为而不是实现?

测试:如何在不失去速度的情况下专注于行为而不是实现?
EN

Stack Overflow用户
提问于 2012-06-13 00:56:36
回答 3查看 4K关注 0票数 43

似乎有两种完全不同的测试方法,我想列举这两种方法。

问题是,这些意见是在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“将控制器和视图规范合并为一个。我也是加载每个控制器规范的所有夹具,所以有一些测试数据来填充视图。最终结果是什么?我的规范更短、更简单、更一致、更不严格,并且它们一起测试整个堆栈(模型、视图、控制器),这样就不会有错误从裂缝中溜走。我不是说这是每个人的“正确”方式。如果您的项目需要非常严格的规范情况,那么它可能不适合您,但在我的例子中,这比我使用模拟之前的情况要好得多。我仍然认为固执是一个很好的解决办法,在一些地方,所以我仍在这样做。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2012-06-13 12:47:53

我认为这三种观点都是完全正确的。Ryan和我一直在为嘲弄的可维护性而挣扎,而David认为维护的代价是值得的,因为速度的提高是值得的。

但这些权衡是一个更深层次问题的症状,大卫曾在2007年提到过这个问题: ActiveRecord。ActiveRecord的设计鼓励您创建上帝对象,这些对象做得太多,对系统的其他部分了解太多,并且有太多的表面积。这导致测试需要测试的太多,对系统其他部分了解太多,或者太慢,或者太脆弱。

那么解决办法是什么呢?尽可能多地将应用程序与框架分开。编写大量的小类来建模您的域,并且不继承任何东西。每个对象都应该具有有限的表面积(不超过几个方法)和通过构造函数传递的显式依赖关系。

使用这种方法,我只编写了两种类型的测试:独立单元测试和全堆栈系统测试。在隔离测试中,我模拟或存根所有不是被测试对象的东西。这些测试速度非常快,甚至不需要加载整个Rails环境。完整的堆栈测试对整个系统进行了测试。他们速度慢得令人痛苦,失败时会给出无用的反馈。我只写一些必要的东西,但足够让我相信我所有经过良好测试的对象都能很好地集成。

不幸的是,我不能向您指出一个做得很好的示例项目(还)。我在为什么我们的代码闻起来上的演讲中谈到了这个问题,看了Corey在快速钢轨试验上的演讲,我强烈推荐阅读基于测试的面向对象软件的成长

票数 16
EN

Stack Overflow用户

发布于 2012-06-13 18:37:53

感谢你汇编了2007年的引文。回顾过去是很有趣的。

我目前的测试方法包含在这一集RailsCasts中,我对此非常满意。总之,我有两个级别的测试。

  • 高级:I在RSpec、Capybara和VCR中使用请求规范。可以根据需要标记测试以执行JavaScript。这里避免了模拟,因为目标是测试整个堆栈。每个控制器动作至少测试一次,也许几次。
  • 低级:--这是测试所有复杂逻辑的地方--主要是模型和助手。我也避免在这里嘲弄。必要时,测试会击中数据库或周围的对象。

注意,没有控制器或视图规范。我觉得这些都在请求规范中得到了充分的涵盖。

既然很少有人嘲笑,我该如何保持测试的快速性?这里有一些提示。

  • 在高级测试中避免过多的分支逻辑。任何复杂的逻辑都应该移到较低的级别。
  • 当生成记录(例如与工厂女孩)时,首先使用build,必要时只切换到create
  • 使用守卫猪肉跳过Rails启动时间。相关的测试通常在保存文件后的几秒钟内完成。使用:focus标记在RSpec中限制在特定区域工作时运行的测试。如果它是一个大型测试套件,那么将Guardfile中的all_after_pass: false, all_on_start: false设置为只在需要时运行它们。
  • 我在每个测试中使用多个断言。对每个断言执行相同的设置代码将大大增加测试时间。RSpec将打印出失败的行,因此很容易找到它。

我发现嘲笑增加了测试的脆性,这就是为什么我要避免的原因。的确,它可以作为OO设计的辅助工具,但在Rails应用程序的结构中,这感觉并不那么有效。相反,我非常依赖重构,让代码本身告诉我设计应该如何进行。

这种方法在中小型Rails应用程序上工作得最好,没有广泛的、复杂的域逻辑。

票数 10
EN

Stack Overflow用户

发布于 2012-06-13 21:35:04

伟大的问题和伟大的讨论。@ryanb和@b收藏者提到他们只编写了两种类型的测试。我采取了类似的方法,但有第三种测试:

  • 单元测试:针对普通的ruby对象的隔离测试,通常是,但不是总是这样。我的单元测试不涉及DB、第三方API调用或任何其他外部内容。
  • 集成测试:这些测试仍然侧重于测试一个类;不同之处在于,它们将该类与我在单元测试中避免的外部内容集成在一起。我的模型通常有单元测试和集成测试,其中单元测试集中在可以测试涉及DB的w/o的纯逻辑中,而集成测试将涉及DB。此外,我倾向于使用集成测试测试第三方API包装器,使用VCR来保持测试的快速性和确定性,但让我的CI构建使HTTP请求成为现实(捕捉任何API更改)。
  • 验收测试:针对整个功能的端到端测试.这不仅仅是通过capybara进行UI测试的问题;我在我的宝石中也是这样做的,它可能根本没有HTML。在这种情况下,无论宝石做什么,这都是端到端的练习。我也倾向于在这些测试中使用VCR (如果它们发出外部HTTP请求),并且与我的集成测试一样,我的CI构建是为了使HTTP请求变为真实的。

至于嘲笑,我没有“一刀切”的方法。在过去,我肯定会过度模仿,但我仍然认为它是一种非常有用的技术,特别是在使用rspec-开火之类的东西时。通常,我会随意地嘲笑协作者扮演角色(特别是如果我拥有它们,并且它们是服务对象),并在大多数其他情况下尽量避免它。

在过去的一年中,我的测试中最大的变化可能是受到DAS的启发:以前我有一个加载整个环境的spec_helper.rb,现在我只显式地加载测试中的类(以及任何依赖项)。除了改进的测试速度(这确实产生了巨大的变化!)它帮助我识别测试中的类在什么时候引入了太多的依赖关系。

票数 8
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/11006888

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档