在大型 Rails 应用程序中加速 RSpec 测试

Posted

技术标签:

【中文标题】在大型 Rails 应用程序中加速 RSpec 测试【英文标题】:Speeding up RSpec tests in a large Rails application 【发布时间】:2011-04-09 10:48:10 【问题描述】:

我有一个 Rails 应用程序,在我的 RSpec 测试中包含超过 2,000 个示例。不用说,这是一个大型应用程序,有很多需要测试的地方。在这一点上运行这些测试效率很低,而且因为它需要很长时间,我们几乎已经到了在推出新版本之前不鼓励编写它们的地步。我将 --profile 添加到我的 spec.opts 以查找运行时间最长的示例,其中至少有 10 个平均需要 10 秒才能运行。在你们的 RSpec 专家中这是正常的吗?对于一个例子来说,10 秒是否太长了?我意识到,对于 2,000 个示例,彻底测试所有内容将花费大量时间 - 但在这一点上 4 小时有点可笑。

对于运行时间最长的示例,您看到了什么样的时间?我可以做些什么来解决我现有的规范,以便找出瓶颈并帮助加快速度。在这一点上,每一分钟都非常有帮助。

【问题讨论】:

慢速测试是集成测试吗?他们在打分贝吗?如果是这样,数据库被重新加载的频率是多少,你可以模拟数据库吗? 您是否能够只运行与您正在处理的部分相关的部分规范,类似于 SeattleRB 的自动测试?你有可以运行所有测试的持续集成服务器吗? 请记住,所有事物都是相对的。我听说过“grrr,我们的测试套件永远需要”20 分钟……和 16-20 小时。这一切都在旁观者的眼中。给定测试的 10 秒通常意味着单元测试已成为如下所述的集成测试。 针对此类问题的建议:将perftools.rb 与您的测试框架一起使用,以了解您大部分时间都在使用什么。接听前 10 个电话并尝试消除/略读它们。然后重复,直到快乐。 【参考方案1】:

10 秒对于运行任何单个测试来说都是很长的时间。我的直觉是您的规范目标同时运行单元测试和集成测试。这是项目陷入的典型情况,并且在某个阶段,如果您想生产更多、更快的产品,您将需要克服这种技术债务。有许多策略可以帮助您做到这一点……我会推荐一些我过去使用过的策略。

1.将单元与集成测试分开

我要做的第一件事是将单元与集成测试分开。您可以通过以下方式做到这一点:

    移动它们(到 spec 目录下的单独文件夹中) - 并修改 rake 目标 标记它们(rspec 允许您标记测试)

理念是,您希望您的常规构建快速 - 否则人们不会很乐意经常运行它们。所以回到那个领域。让您的常规测试快速运行,并使用持续集成服务器运行更完整的构建。

集成测试是一种涉及外部依赖项的测试(例如数据库、WebService、队列,有些人认为是文件系统)。单元测试只测试您要检查的特定代码项。它应该运行得很快(45 秒内可以运行 9000 次),即它的大部分应该在内存中运行。

2。将集成测试转换为单元测试

如果您的大部分单元测试小于集成测试套件,那么您就有问题了。这意味着不一致将更容易出现。所以从这里开始,开始创建更多的单元测试来代替集成测试。在此过程中您可以做的事情有:

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

一旦您有适当的单元测试来替换集成测试 - 删除集成测试。重复测试只会让维护变得更糟。

3.不要使用固定装置

固定装置是邪恶的。改用工厂(机械师或工厂机器人)。这些系统可以构建更具适应性的数据图,更重要的是,它们可以构建您可以使用的内存对象,而不是从外部数据源加载内容。

4.添加检查以阻止单元测试成为集成测试

现在您已经进行了更快的测试,是时候进行检查以阻止这种情况再次发生。

有些库会在尝试访问数据库 (UnitRecord) 时对活动记录进行猴子补丁以引发错误。

您还可以尝试配对和 TDD,这可以帮助您的团队编写更快的测试,因为:

    有人在检查 - 所以没有人会偷懒 正确的 TDD 需要快速反馈。缓慢的测试只会让整个事情变得痛苦。

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

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

这可能应该最后使用。您真正的问题一直出现在第 1、2、3 步中。解决这个问题,您将能够更好地部署额外的基础设施。

【讨论】:

很好的答案。确实没有任何高级工具可以帮助快速运行糟糕的测试。所以试着只写好的。 我不同意你不应该使用固定装置的第三点。如果使用得当,它们比工厂更快,因为您不必创建对象。您的数据就在那里,您不必为每个测试用例创建它。 关于工厂更快的说法是个笑话,对吧? 通过选择性地避开工厂女孩来加快测试速度 - robots.thoughtbot.com/…【参考方案2】:

有关提高测试套件性能的精彩食谱,请查看Grease Your Suite 演示文稿。

他记录了通过使用以下技术将测试套件运行时间提高了 45 倍:

fast_context quickerclip hydra 可怕的文件系统调整 tcmalloc Ruby Enterprise Edition GC tuning

【讨论】:

我喜欢链接,但不喜欢演示文稿。某人演讲的索引卡是演讲者填写演讲内容的提示。 此演示文稿不再播放。 fast_context 加速多个 shoulda 块。 quickerclip(那个链接也死了)显然加速了回形针。 Hydra 不推荐用于 parallel_tests 和 Gorgon。 bootsnap 对文件系统进行了调整,现在包含在 Rails 中。 Ruby 的最新版本改进了分配和 GC。【参考方案3】:

您可以使用 Spork。它支持 2.3.x ,

https://github.com/sporkrb/spork

或 ./script/spec_server 可能适用于 2.x

您还可以编辑数据库配置(这实际上加快了数据库查询等),这也将提高测试性能。

【讨论】:

Spork 棒极了。它也可以在 Rails 3 上运行,但需要一点黑客攻击。但是,Spork on Rails 3 需要注意的一件事是,它似乎无法识别控制器中的更改,因此您需要每隔一段时间重新启动它。但不管怎样,Spork 真的很棒,测试的速度提升非常显着。 Spork 仅用于加速启动,不用于测试。所以有很多规格,优势是微不足道的。 与上面的 cmets spork 不同,可以使用 railscast railscasts.com/episodes/285-spork 加速测试和启动。在一个非常大的应用程序上大幅改进了我的 rspec 测试套件。 (从 192 秒减少到 10 秒!)【参考方案4】:

每个示例 10 秒似乎很长。我从未见过花费超过一秒钟的规范,而且大多数花费的时间要少得多。你在测试网络连接吗?数据库写入?文件系统写入?

尽可能多地使用模拟和存根 - 它们比编写访问数据库的代码快很多。不幸的是,模拟和存根也需要更多的时间来编写(并且更难正确执行)。您必须平衡编写测试所花费的时间与运行测试所花费的时间。

我赞同Andrew Grimm 关于研究 CI 系统的评论,该系统可能允许您并行化您的测试套件。对于这么大的东西,它可能是唯一可行的解​​决方案。

【讨论】:

是的,如果是 10 秒,我会分析它并查看瓶颈在哪里 我有一个庞大的项目,运行单个 rspec 测试需要将近 15-20 秒。【参考方案5】:

如果您使用 ActiveRecord 模型,您还应该考虑 BCrypt 加密的成本。

您可以在这篇博文中了解更多信息:http://blog.syncopelabs.co.uk/2012/12/speed-up-rspec-test.html

【讨论】:

【参考方案6】:

faster_require gem 可能会对您有所帮助。 除此之外,您唯一的方法是(就像您所做的那样)分析和优化,或使用 spork 或为您并行运行您的规范的东西。 http://ruby-toolbox.com/categories/distributed_testing.html

【讨论】:

【参考方案7】:

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

【讨论】:

以上是关于在大型 Rails 应用程序中加速 RSpec 测试的主要内容,如果未能解决你的问题,请参考以下文章

rails3, rspec2 ActiveRecord#changes 规格中的错误

.env 未使用 rspec 在 Rails 的测试环境中加载

设置 RSpec 以测试 gem(不是 Rails)

Rails 和 RSpec:在不同的命名空间(模块)中测试具有相同名称的控制器

Rails 5,Rspec:架构中未找到环境数据

在 Rails 3.1 中使用 Capybara、Rspec 和 Selenium 进行测试时登录失败