在一个大型Rails应用中加速RSpec测试

96

我有一个Rails应用程序,其中包含超过2,000个RSpec测试示例。不用说,这是一个庞大的应用程序,需要进行大量的测试。目前运行这些测试非常低效,因为它需要很长时间,以至于我们在推出新版本前几乎被打击了信心。我在spec.opts中添加了--profile参数来查找运行最长的测试示例,并且有至少10个测试示例平均需要运行10秒钟。这种情况在RSpec专家中正常吗?一个测试例子10秒钟太长了吗?我知道,对于2,000个测试用例,要全面测试需要花费相当多的时间,但目前4个小时显然太离谱了。

你们看到的最长运行时间是多少?我该如何排除现有的测试用例中的瓶颈并帮助加快速度?此时每一分钟都非常重要。


1
这些慢测试是集成测试吗?它们是否会访问数据库?如果是,数据库被重新加载的频率是多少,你能模拟数据库吗? - Kaleb Pederson
1
你能否只运行与你正在工作的部分相关的规范,类似于SeattleRB的autotest?你是否有一个可以运行所有测试的持续集成服务器? - Andrew Grimm
请记住,所有事物都是相对的。我曾听到过“我们的测试套件需要很长时间”这样的抱怨,但20分钟和16-20小时都有出现过。这完全取决于观察者的角度。对于某些测试来说,10秒钟可能意味着一个单元测试已经变成了下面提到的集成测试。 - Michael Durrant
针对这种问题的建议是:使用 perftools.rb 与您的测试框架一起,了解哪些部分占用了大部分时间。取出前10个调用并尝试消除/简化它们。然后重复此过程,直到满意为止。 - aledalgrande
7个回答

123

单个测试运行10秒钟非常长。我的直觉是你的规格目标在同时运行单元测试和整合测试。这是项目通常会遇到的问题,如果想要更多更快地生产,你需要克服这种技术债务。有许多策略可以帮助你做到这一点...我将推荐一些我过去使用过的方法。

1. 将单元测试和整合测试分离

首先,我会将单元测试和整合测试分开。你可以通过以下两种方式之一来完成:

  1. 移动它们(到spec目录下的不同文件夹中)- 并修改rake目标
  2. 给它们打标签(rspec允许你为测试打标签)

这种哲学认为,你希望正常的构建速度很快 - 否则人们就不会很高兴经常运行它们。因此回到那个领域。让你的常规测试运行快速,并使用持续集成服务器来运行更完整的构建。

整合测试是涉及外部依赖关系的测试(例如数据库、Web服务、队列,某些人可能会争论FileSystem)。单元测试只测试你想要检查的特定代码项。它应该快速运行(45秒内的9000次测试是可能的),即大部分都应该在内存中运行。

2. 将整合测试转换为单元测试

如果你的单元测试数量比整合测试套件还少,那么你就有问题了。这意味着不一致性将更容易出现。因此,从这里开始,创建更多的单元测试来替换整合测试。以下是有助于这个过程的一些事情:

  1. 使用模拟框架而不是真实资源。Rspec有一个内置的模拟框架。
  2. 在你的单元测试套件上运行rcov。使用它来衡量你的单元测试套件的彻底程度。

一旦你有了适当的单元测试来替换整合测试 - 删除整合测试。重复测试只会使维护变得更糟。

3. 不要使用夹具

固定数据是有害的。使用工厂代替(machinist或factorybot)。这些系统可以构建更具适应性的数据图,更重要的是,它们可以构建内存中的对象,您可以使用这些对象,而不必从外部数据源加载数据。

4. 添加检查以防止单元测试变成集成测试

现在您已经实现了更快速的测试,是时候添加检查以防止再次发生这种情况了。

有些库可以monkey patch ActiveRecord以在尝试访问数据库时抛出错误(UnitRecord)。

您还可以尝试配对编程和TDD,因为这可以帮助强制您的团队编写更快速的测试,原因如下:

  1. 有人在检查-所以没有人会变懒
  2. 正确的TDD需要快速反馈。缓慢的测试只会使整个过程痛苦不堪。

5. 使用其他库来解决问题

有人提到spork(加速rails3测试套件的加载时间),hydra/parallel_tests-以并行方式运行单元测试(跨多个内核)。

这应该是最后使用的。您真正的问题在步骤1、2、3中。解决这个问题后,您将能够更好地部署其他基础设施。


很棒的答案。非常正确,没有高级工具可以帮助快速运行不好的测试。所以尽量只写好的测试。 - Yura Omelchuk
5
我不同意你的第三点,即不应使用fixture。如果正确使用,它们比工厂更快,因为您不必创建对象。您的数据已经存在,每次测试用例无需重新创建。 - Jacob Waller
3
这个工厂更快的说法是个笑话,对吧? - Mike Szyndel
通过有选择地避免使用Factory Girl来加速测试 - https://robots.thoughtbot.com/speed-up-tests-by-selectively-avoiding-factory-girl - geekdev

18

我喜欢这些链接,但不喜欢它们的呈现方式。索引卡片对于演讲者来说是填充演示内容的提示。 - Brian Maltzan
这个演示已经失效了。fast_context 可以加速多个 shoulda 块。quickerclip(该链接也已失效)显然可以加速 Paperclip。Hydra 已被弃用,推荐使用 parallel_tests 和 Gorgon。bootsnap 具有文件系统调整功能,并已包含在 Rails 中。最近版本的 Ruby 改进了内存分配和垃圾回收机制。 - rep

5
你可以使用Spork。它支持2.3.x版本,还可以使用./script/spec_server来支持2.x版本。另外,你可以编辑数据库配置(这样可以加快数据库查询速度等),这也会提高测试性能。请参考https://github.com/sporkrb/spork

Spork 很棒。它也可以在 Rails 3 上工作,只需要进行一些修改。但要注意的是,在 Rails 3 上使用 Spork 时似乎无法监测到控制器的更改,因此您需要定期重启它。但无论如何,Spork 真的很棒,测试速度的提升非常显著。 - AboutRuby
4
Spork 仅用于加速启动,而非测试。因此,在有大量规范时,其优势微不足道。 - Reactormonk
与上述评论相反,spork可以用于加速测试和启动,如railscast所述http://railscasts.com/episodes/285-spork。在一个非常大的应用程序中,大大改善了我的rspec测试套件。(从192秒减少到10秒!) - jamesc

4
每个示例需要10秒钟似乎非常长。我从来没有见过一个超过一秒钟的规范,大多数情况下要少得多。你是在测试网络连接吗?数据库写入?文件系统写入?
尽可能使用mock和stub——它们比直接操作数据库的代码要快得多。不幸的是,模拟和存根的编写也需要更多的时间(并且更难正确完成)。你必须平衡用于编写测试的时间和运行测试的时间。
我赞同Andrew Grimm提到的查找可以让你并行执行测试套件的CI系统。对于这样的事情,这可能是唯一可行的解决方案。

如果是10秒,我会对其进行分析,看看瓶颈在哪里。 - rogerdpack
1
我有一个庞大的项目,运行单个rspec测试需要近15-20秒。 - ExiRe

2

1

-5

删除现有的测试套件。这将非常有效。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接