提升你的 Rails Specs 性能 10 倍
人們疏于在Rails開發(fā)應(yīng)用中去駕馭規(guī)范的一個(gè)基本的原因是運(yùn)行的規(guī)范套件所需要的時(shí)間。很多工具可以用來緩和這個(gè)麻煩,比如 Spork , Zeus 和 Spring。事實(shí)上,Rails 4.1將會在春季推出標(biāo)準(zhǔn)。不幸的是,這些工具僅僅是解決問題癥狀的 一個(gè)拐杖,而不是解決問題本身。實(shí)際的問題是書寫耦合度高的代碼需要有一個(gè)完整的Rails的架構(gòu)支撐,這個(gè)架構(gòu)會緩慢啟動。
開發(fā)解耦代碼
一種解決方法是:書寫的代碼是獨(dú)立的,元件盡可能的與系統(tǒng)分離。用另外的話說,就是寫SOLID Rails 代碼。舉一個(gè)特殊的例子,可以直接寫一個(gè)類模塊去創(chuàng)建一個(gè)事例。而不是使用依賴的插入的方法去去除涉及到類的硬編碼。我們僅僅需要去保證:我們安全的采用模塊符號或者懶惰的評價(jià)去得到默認(rèn)的引用。以下是一個(gè)服務(wù),它需要在ActiveRecord模塊中創(chuàng)建一個(gè)小工具。我們采用懶惰的評價(jià)去介入的方法來替換直接的引用工具類。這可以解耦我們的代碼,同時(shí)不需要ActiveRecord載入。
- # A tightly coupled class. We don't want this.
- class MyService
- def create_widget(params)
- Widget.create(params)
- end
- end
- # We can inject in the initializer
- class MyService
- attr_reader :widget_factory
- def initialize(dependencies={})
- @widget_factory = dependencies.fetch(:widget_factory) { Widget }
- end
- def create_widget(params)
- widget_factory.create(params)
- end
- end
- # Or we can explictly inject via a setter with a lazy reader
- class MyService
- attr_writer :widget_factory
- def widget_factory
- @widget_factory ||= Widget
- end
- def create_widget(params)
- widget_factory.create(params)
- end
- end
- # A specification injecting the dependency using the second method
- describe MyService do
- subject(:service) { MyService.new }
- let(:widget_factory) { double 'widget_factory', create: nil }
- before { service.widget_factory = widget_factory }
- it 'creates a widget with the factory' do
- service.create_widget({name: 'sprocket'})
- expect(widget_factory).to have_received(:create).with({name: 'sprocket'})
- end
- end
當(dāng)你采用這種方式寫代碼時(shí),你可以開始重新組織怎么建立自己的規(guī)范和最小化環(huán)境需求來運(yùn)行這些規(guī)范和滿足規(guī)則需求的代碼。典型spec_helper.rb會有一個(gè)如下的一行代碼:
- require File.expand_path("../../config/environment", __FILE__)
這個(gè)將會載入整個(gè)的Rails程序且降低測試運(yùn)行速度。為了讓規(guī)范達(dá)到更快的速度,可以使用一個(gè)不含有上面那行代碼的配置文件。那么讓我們開始創(chuàng)建一個(gè)輕量級的rb包:base_sepc_helper.rb:
- ENV["RAILS_ENV"] ||= 'test'
- require 'rubygems'
- RAILS_ROOT = File.expand_path('../..', __FILE__)
- Dir[File.join(RAILS_ROOT, 'spec/support/**/*.rb')].each {|f| require f}
- RSpec.configure do |config|
- config.mock_with :rspec
- config.order = 'random'
- # Your prefered config options go here
- end
- require 'active_support'
- require 'active_support/dependencies'
我們通過請求active_support和active_support/dependencies包來訪問Rails使用的自動裝載機(jī),實(shí)際上并沒有導(dǎo)入所有的Rails。它是相當(dāng)?shù)妮p量級并且方便性超過了損耗。在每個(gè)需要這個(gè)base包的helper里,我們將會添加我們程序相對應(yīng)的部分到ActiveSupport::Dependencies.autoload_paths中。
簡單的Ruby對象說明
取決于你指定的應(yīng)用程序部分,你可以在任意一個(gè)上下文中創(chuàng)建一個(gè)你所需要的輔助細(xì)則。例如,最簡單的是指定一個(gè)任意類型的Ruby純類作為服務(wù)類。如下面services_spec_helper.rb例子
- require 'base_spec_helper'
- Dir[File.join(RAILS_ROOT, "spec/support_services/**/*.rb")].each {|f| require f}
- ActiveSupport::Dependencies.autoload_paths << "#{RAILS_ROOT}/app/services"
裝飾說明
于你的裝飾而言,你可能會選擇布商,你的decorators_spec_helper.rb就如以下所看到的。
- require 'base_spec_helper'
- require 'draper'
- Draper::ViewContext.test_strategy :fast
- Dir[File.join(RAILS_ROOT, "spec/support_decorators/**/*.rb")].each {|f| require f}
- ActiveSupport::Dependencies.autoload_paths << "#{RAILS_ROOT}/app/decorators"
模塊規(guī)范
測試模塊還需要做一點(diǎn)事情. 假設(shè)你現(xiàn)在正在用ActiveRecord你會需要建立一個(gè)和數(shù)據(jù)庫的連接. 我們并不需要將defactory_girl或者database_cleaner加入你的測試中,而且并不會真的創(chuàng)建對象. 實(shí)際上,唯一需要進(jìn)行創(chuàng)建數(shù)據(jù)庫對象的地方就是當(dāng)你進(jìn)行特定對象測試的時(shí)候.當(dāng)你確實(shí)需要創(chuàng)建一些對象的時(shí)候,你只需要手動的進(jìn)行清理和轉(zhuǎn)換. 這就是一個(gè)樣例models_spec_helper.rb:
- require 'base_spec_helper'
- require 'active_record'
- # RSpec has some nice matchers for models so we'll pull them in
- require 'rspec/rails/extensions/active_record/base'
- Dir[File.join(RAILS_ROOT, "spec/support_models/**/*.rb")].each {|f| require f}
- # Manually connect to the database
- ActiveRecord::Base.establish_connection(
- YAML.load(File.read(RAILS_ROOT + '/config/database.yml'))['test']
- )
- ActiveSupport::Dependencies.autoload_paths << "#{RAILS_ROOT}/app/models"
特點(diǎn)說明
最后, 當(dāng)我們創(chuàng)建特色應(yīng)用時(shí), 我們會需要Rails全套知識并且feature_spec_helper.rb看起來就和spec_helper.rb差不多了.
作為總結(jié)
我自己也開始在項(xiàng)目中加入這些改變并且這也讓我能用更加簡單的代碼去完成一個(gè)項(xiàng)目. 你們可以在Github上找到:https://github.com/Originate/rails_spec_harness
當(dāng)在項(xiàng)目中引入這些變化時(shí)候,我發(fā)現(xiàn)速度至少增長了8-12倍. 變化最大的一個(gè)項(xiàng)目竟然增長了27倍同時(shí)也包括了這些對應(yīng)的編程效率上的提高.舉個(gè)例子,我開始寫一個(gè)含有4個(gè)簡單例子的Ruby類. 然后我使用time命令行工具去衡量運(yùn)行的效率,并且之后我能得到如下的結(jié)果,F(xiàn)ULL 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 |
寫牛逼的代碼,隔離你的單獨(dú)模塊,然后,享受編碼的樂趣吧。
英文原文:Speed Up Your Rails Specs by 10x
原文鏈接:http://www.oschina.net/translate/improve-your-rails-specification-speed-by-10x