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

看来,有两种完全不同的测试方法,我想引用两种方法。

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

布兰登守护者:

从理论上讲,测试应该是不可知的 实施。 这导致较少的脆性测试,而实际测试 结果(或行为)。

使用RSpec,我觉得完全嘲笑您的通用方法 测试您的控制器的模型最终迫使您看起来太多 进入您的控制器的实现。

这本身还不错,但问题是它也可以凝视 控制器中有很多指令来决定如何使用模型。 为什么呢 如果我的控制器叫Thing.new怎么办? 如果我的控制器决定怎么办 拿来Thing.create! 和救援路线? 如果我的模型有一个 特殊的初始化方法,例如Thing.build_with_foo? 我的规格 如果我更改实现,行为就不会失败。

当您嵌套资源并在 每个控制器创建多个模型。 我的一些设置方法结束了 长达15行或更多行且非常脆弱。

RSpec的目的是将您的控制器逻辑与 您的模型,理论上听起来不错,但几乎与 用于集成堆栈(如Rails)的颗粒。 特别是如果你练习 瘦控制器/脂肪模型学科, 控制器变得非常小,设置也变得巨大。

那么想做BDD是什么呢? 退后一步,我的行为 真正要测试的不是我的控制器调用Thing.new,而是 给定参数X,它会创建一个新事物并重定向到它。

大卫·切利姆斯基(David Chelimsky):

这都是权衡取舍。

AR选择继承而不是委托的事实使我们陷入困境 测试绑定–我们必须耦合到数据库,否则我们必须 与实施更亲密。 我们接受这种设计选择 因为我们可以从表现力和干燥感中受益。

为了解决这个难题,我选择了更快的测试,但付出了代价 稍脆。 您选择了较少的脆性测试,但要付出代价 它们中的运行速度稍慢。 无论哪种方式都需要权衡。

在实践中,我进行了数百次(即使不是数千次)测试 一天(我使用自动测试并采取非常细致的步骤),然后更改是否 我几乎从不使用“新”或“创建”。 同样由于细粒度的步骤,新 起初出现的模型非常不稳定。 valid_thing_attrs 方法可以最大程度地减少这种痛苦,但这仍然意味着 每个新的必填字段都意味着我必须进行更改 valid_thing_attrs。

但是,如果您的方法在实践中对您有用,那么它很好! 在 事实上,我强烈建议您发布带有生成器的插件 以您喜欢的方式生成示例。 我肯定很多 的人将从中受益。

瑞安·贝茨(Ryan Bates):

出于好奇,您在测试/规格中多久使用一次模拟? 也许我做错了,但是我发现它很严重 限制。自一个多月前改用rSpec以来,我一直在 他们在文档中推荐的控制器和视图层的内容 完全不打数据库,模型完全被嘲笑 出来。这可以使您获得不错的速度提升,并使某些事情变得容易, 但是我发现这样做的弊端远远超过了优点。以来 使用模拟程序,我的规格变成了维护噩梦。眼镜 用于测试行为,而不是执行。我不在乎 如果一个方法被调用,我只想确保结果输出 是正确的。因为嘲笑使规范挑剔 实施,它可以进行简单的重构(不会改变 行为),而不必经常回去 “修复”规格。我对规格/测试应有的看法很坚决 盖。测试仅在应用程序中断时才会中断。这是一 我之所以很难测试视图层的原因是因为我发现它太僵硬了。 通常会导致测试中断而应用程序在以下情况下不会中断 改变视图中的小事情。我在发现同样的问题 模拟。最重要的是,我今天才意识到,模拟/存根 一个类方法(有时)在规范之间徘徊。规格应 自我约束,不受其他规格的影响。这打破了 规则并导致棘手的错误。我从这一切中学到了什么?是 小心在哪里使用嘲笑。存根还不算坏,但仍然有 一些相同的问题。

我花了过去几个小时,从规格中删除了几乎所有模拟。 我还合并了控制器,并使用 控制器规格中的“ integrate_views”。 我也正在加载所有 每个控制器规格的固定装置,因此需要填充一些测试数据 观点。 最终结果? 我的规格更短,更简单,更多 一致,不那么僵化,并且它们一起测试整个堆栈 (模型,视图,控制器),因此不会有任何漏洞通过裂缝。 我是 并不是说这是每个人的“正确”方法。 如果你的项目 需要非常严格的规范情况,那么可能不适合您,但是在我 情况下,这比我之前使用模拟游戏要好得多。 我仍然 认为存根是一个很好的解决方案,所以我仍然在做 那。

krn asked 2020-07-31T21:16:09Z
3个解决方案
16 votes

我认为这三种意见仍然完全有效。 Ryan和我在模拟的可维护性方面挣扎,而David觉得维护的权衡对于提高速度是值得的。

但是这些权衡是一个更深层次问题的征兆,David在2007年提到了ActiveRecord。 ActiveRecord的设计鼓励您创建过多的对象,对系统的其余部分了解太多,并且表面积太大。 这导致测试的测试太多,对系统的其余部分了解太多,或者太慢或太脆弱。

那么解决方案是什么? 尽可能将您的应用程序与框架分开。 编写许多小型类来为您的域建模,而不继承任何内容。 每个对象应具有有限的表面积(最多几个方法),并通过构造函数传递显式依赖关系。

使用这种方法,我只编写了两种类型的测试:隔离单元测试和全栈系统测试。 在隔离测试中,我模拟或存根不是测试对象的所有内容。 这些测试非常快,而且甚至不需要加载整个Rails环境。 全栈测试将对整个系统进行测试。 他们非常缓慢,失败时会给出无用的反馈。 我写的尽可能少,但足以使我确信我所有经过良好测试的对象都可以很好地集成。

不幸的是,我无法为您提供一个很好的示例项目(至今)。 我在关于“为什么我们的代码会闻到”的演示中谈到了一些问题,观看了Corey Haines关于Fast Rails测试的演示,我强烈建议阅读由测试指导的“增长的面向对象的软件”。

bkeepers answered 2020-07-31T21:28:54Z
9 votes

感谢您整理2007年的报价。回顾过去很有趣。

我对此感到非常满意的RailsCasts集涵盖了我当前的测试方法。 总而言之,我有两个级别的测试。

  • 高级:我在RSpec,Capybara和VCR中使用请求规范。 可以根据需要将测试标记为执行JavaScript。 这里避免了模拟,因为目标是测试整个堆栈。 每个控制器动作至少要测试一次,也许要测试几次。

  • 底层:在这里测试所有复杂的逻辑-主要是模型和助手。 我也避免在这里嘲笑。 必要时,测试会命中数据库或周围的对象。

请注意,没有控制器或视图规格。 我认为这些在请求规范中已充分涵盖。

由于几乎没有嘲笑,我如何保持测试快速? 这里有一些技巧。

  • 避免在高级测试中使用过多的分支逻辑。 任何复杂的逻辑都应移至较低级别。

  • 生成记录时(例如与Factory Girl一起使用),请首先使用build,并且仅在必要时切换到:focus

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

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

我发现模拟增加了测试的脆弱性,这就是为什么我避免使用它。 的确,它可以很好地辅助OO设计,但是在Rails应用程序的结构中,效果并不理想。 相反,我在很大程度上依靠重构,让代码本身告诉我设计应该如何进行。

这种方法在没有广泛而复杂的域逻辑的中小型Rails应用程序上效果最佳。

ryanb answered 2020-07-31T21:30:05Z
8 votes

好问题,好讨论。 @ryanb和@bkeepers提到他们只编写两种类型的测试。 我采用类似的方法,但是有第三种类型的测试:

  • 单元测试:通常(但并非总是)针对纯红宝石对象的隔离测试。 我的单元测试不涉及数据库,第三方API调用或任何其他外部内容。
  • 集成测试:这些测试仍专注于测试一门课程; 不同之处在于它们将类与我在单元测试中避免的外部东西集成在一起。 我的模型通常同时具有单元测试和集成测试,其中单元测试的重点是可以在不涉及DB的情况下进行测试的纯逻辑,而集成测试将涉及DB。 另外,我倾向于使用集成测试来测试第三方API包装器,使用VCR来保持测试的快速性和确定性,但是让我的CI构建使HTTP请求成为真实请求(以捕获任何API更改)。
  • 验收测试:整个功能的端到端测试。 这不仅仅是关于通过水豚进行UI测试; 我在gem中也做同样的事情,gem可能根本没有HTML UI。 在这些情况下,这将执行宝石端到端的操作。 我也倾向于在这些测试中使用VCR(如果它们发出外部HTTP请求),并且像在集成测试中一样,我的CI构建被设置为发出真实的HTTP请求。

就嘲笑而言,我没有“一刀切”的方法。 过去我肯定过分模仿了,但是我仍然发现它是一种非常有用的技术,尤其是在使用rspec-fire之类的工具时。 通常,我嘲笑协作者自由地扮演角色(特别是如果我拥有这些角色,并且它们是服务对象),并在大多数其他情况下尽量避免这样做。

过去一年左右的时间里,我的测试可能发生的最大变化是受到DAS的启发:以前我有一个spec_helper.rb可以加载整个环境,而现在我明确地仅加载了被测类(以及所有依赖项)。 除了提高了测试速度(确实有很大的不同!)之外,它还帮助我确定我的被测类何时引入了过多的依赖关系。

Myron Marston answered 2020-07-31T21:30:50Z
translate from https://stackoverflow.com:/questions/11006888/testing-how-to-focus-on-behavior-instead-of-implementation-without-losing-speed