提升你的 Rails Specs 性能 10 倍

开发 后端 前端
人们疏于在Rails开发应用中去驾驭规范的一个基本的原因是运行的规范套件所需要的时间。很多工具可以用来缓和这个麻烦,比如 Spork , Zeus 和 Spring。事实上,Rails 4.1将会在春季推出标准。

人们疏于在Rails开发应用中去驾驭规范的一个基本的原因是运行的规范套件所需要的时间。很多工具可以用来缓和这个麻烦,比如 Spork Zeus 和  Spring。事实上,Rails 4.1将会在春季推出标准。不幸的是,这些工具仅仅是解决问题症状的 一个拐杖,而不是解决问题本身。实际的问题是书写耦合度高的代码需要有一个完整的Rails的架构支撑,这个架构会缓慢启动。

开发解耦代码

一种解决方法是:书写的代码是独立的,元件尽可能的与系统分离。用另外的话说,就是写SOLID Rails 代码。举一个特殊的例子,可以直接写一个类模块去创建一个事例。而不是使用依赖的插入的方法去去除涉及到类的硬编码。我们仅仅需要去保证:我们安全的采用模块符号或者懒惰的评价去得到默认的引用。以下是一个服务,它需要在ActiveRecord模块中创建一个小工具。我们采用懒惰的评价去介入的方法来替换直接的引用工具类。这可以解耦我们的代码,同时不需要ActiveRecord载入。

  1. # A tightly coupled class. We don't want this.  
  2. class MyService  
  3.   def create_widget(params)  
  4.     Widget.create(params)  
  5.   end 
  6. end 
  7.  
  8. # We can inject in the initializer  
  9. class MyService  
  10.   attr_reader :widget_factory  
  11.  
  12.   def initialize(dependencies={})  
  13.     @widget_factory = dependencies.fetch(:widget_factory) { Widget }  
  14.   end 
  15.  
  16.   def create_widget(params)  
  17.     widget_factory.create(params)  
  18.   end 
  19. end 
  20.  
  21. Or we can explictly inject via a setter with a lazy reader  
  22. class MyService  
  23.   attr_writer :widget_factory  
  24.  
  25.   def widget_factory  
  26.     @widget_factory ||= Widget  
  27.   end 
  28.  
  29.   def create_widget(params)  
  30.     widget_factory.create(params)  
  31.   end 
  32. end 
  33.  
  34. # A specification injecting the dependency using the second method  
  35. describe MyService do  
  36.   subject(:service) { MyService.new }  
  37.   let(:widget_factory) { double 'widget_factory'create: nil }  
  38.   before { service.widget_factory = widget_factory }  
  39.  
  40.   it 'creates a widget with the factory' do  
  41.     service.create_widget({name'sprocket'})  
  42.     expect(widget_factory).to have_received(:create).with({name'sprocket'})  
  43.   end 
  44. end 

当你采用这种方式写代码时,你可以开始重新组织怎么建立自己的规范和最小化环境需求来运行这些规范和满足规则需求的代码。典型spec_helper.rb会有一个如下的一行代码:

  1. require File.expand_path("../../config/environment", __FILE__)  

这个将会载入整个的Rails程序且降低测试运行速度。为了让规范达到更快的速度,可以使用一个不含有上面那行代码的配置文件。那么让我们开始创建一个轻量级的rb包:base_sepc_helper.rb:

  1. ENV["RAILS_ENV"] ||= 'test' 
  2. require 'rubygems' 
  3.  
  4. RAILS_ROOT = File.expand_path('../..', __FILE__)  
  5. Dir[File.join(RAILS_ROOT, 'spec/support/**/*.rb')].each {|f| require f}  
  6.  
  7. RSpec.configure do |config|  
  8.   config.mock_with :rspec 
  9.   config.order = 'random' 
  10.   # Your prefered config options go here  
  11. end 
  12.  
  13. require 'active_support' 
  14. require 'active_support/dependencies' 

我们通过请求active_support和active_support/dependencies包来访问Rails使用的自动装载机,实际上并没有导入所有的Rails。它是相当的轻量级并且方便性超过了损耗。在每个需要这个base包的helper里,我们将会添加我们程序相对应的部分到ActiveSupport::Dependencies.autoload_paths中。

简单的Ruby对象说明

取决于你指定的应用程序部分,你可以在任意一个上下文中创建一个你所需要的辅助细则。例如,最简单的是指定一个任意类型的Ruby纯类作为服务类。如下面services_spec_helper.rb例子

  1. require 'base_spec_helper' 
  2. Dir[File.join(RAILS_ROOT, "spec/support_services/**/*.rb")].each {|f| require f}  
  3. ActiveSupport::Dependencies.autoload_paths << "#{RAILS_ROOT}/app/services" 

装饰说明

于你的装饰而言,你可能会选择布商,你的decorators_spec_helper.rb就如以下所看到的。

  1. require 'base_spec_helper' 
  2. require 'draper' 
  3. Draper::ViewContext.test_strategy :fast 
  4. Dir[File.join(RAILS_ROOT, "spec/support_decorators/**/*.rb")].each {|f| require f}  
  5. ActiveSupport::Dependencies.autoload_paths << "#{RAILS_ROOT}/app/decorators" 

模块规范

测试模块还需要做一点事情. 假设你现在正在用ActiveRecord你会需要建立一个和数据库的连接. 我们并不需要将defactory_girl或者database_cleaner加入你的测试中,而且并不会真的创建对象. 实际上,唯一需要进行创建数据库对象的地方就是当你进行特定对象测试的时候.当你确实需要创建一些对象的时候,你只需要手动的进行清理和转换. 这就是一个样例models_spec_helper.rb:

  1. require 'base_spec_helper' 
  2. require 'active_record' 
  3. # RSpec has some nice matchers for models so we'll pull them in  
  4. require 'rspec/rails/extensions/active_record/base' 
  5. Dir[File.join(RAILS_ROOT, "spec/support_models/**/*.rb")].each {|f| require f}  
  6.  
  7. # Manually connect to the database  
  8. ActiveRecord::Base.establish_connection(  
  9.   YAML.load(File.read(RAILS_ROOT + '/config/database.yml'))['test']  
  10. )  
  11.  
  12. ActiveSupport::Dependencies.autoload_paths << "#{RAILS_ROOT}/app/models" 

特点说明

最后, 当我们创建特色应用时, 我们会需要Rails全套知识并且feature_spec_helper.rb看起来就和spec_helper.rb差不多了.

作为总结

我自己也开始在项目中加入这些改变并且这也让我能用更加简单的代码去完成一个项目. 你们可以在Github上找到:https://github.com/Originate/rails_spec_harness

当在项目中引入这些变化时候,我发现速度至少增长了8-12倍. 变化最大的一个项目竟然增长了27倍同时也包括了这些对应的编程效率上的提高.举个例子,我开始写一个含有4个简单例子的Ruby类. 然后我使用time命令行工具去衡量运行的效率,并且之后我能得到如下的结果,FULL Rails VS MINIMAL:

Spec Helper Real User Sys RSpec Reported
Full Rails 4.913s 2.521s 1.183s 0.0706s
Minimal 0.492s 0.407s 0.080s 0.0057s

写牛逼的代码,隔离你的单独模块,然后,享受编码的乐趣吧。

英文原文:Speed Up Your Rails Specs by 10x

原文链接:http://www.oschina.net/translate/improve-your-rails-specification-speed-by-10x

责任编辑:林师授 来源: 开源中国社区 编译
相关推荐

2020-07-21 15:40:55

NginxJava服务器

2011-07-01 10:11:39

2017-12-13 13:09:36

NginxWeb应用

2024-10-29 08:21:05

2020-03-26 12:38:15

代码节点数据

2018-08-23 17:45:52

2019-09-26 08:33:51

Nginx技术Java

2020-07-22 08:30:02

代码开发工具

2013-04-01 00:16:41

飞鱼星无线云无线AP

2021-12-29 11:06:25

Java代码技巧

2022-11-19 18:18:22

Spring架构

2022-12-13 08:45:01

3F倾听模型

2023-06-13 13:52:00

Java 7线程池

2023-10-20 08:12:00

JDK21线程池配置

2023-02-21 13:32:09

Linux 6.3操作系统

2022-09-09 09:33:14

支付宝代码性能

2023-09-07 11:29:36

API开发

2019-08-06 16:32:24

大数据智能分析BI

2014-04-01 09:52:46

MySQL
点赞
收藏

51CTO技术栈公众号