提升你的 Rails Specs 性能 10 倍
提升你的 Rails Specs 性能 10 倍
人們疏于在Rails開發應用中去駕馭規范的一個基本的原因是運行的規范套件所需要的時間。很多工具可以用來緩和這個麻煩,比如 Spork , Zeus 和 Spring。事實上,Rails 4.1將會在春季推出標準。不幸的是,這些工具僅僅是解決問題癥狀的 一個拐杖,而不是解決問題本身。實際的問題是書寫耦合度高的代碼需要有一個完整的Rails的架構支撐,這個架構會緩慢啟動。
開發解耦代碼
一種解決方法是:書寫的代碼是獨立的,元件盡可能的與系統分離。用另外的話說,就是寫SOLID Rails 代碼。舉一個特殊的例子,可以直接寫一個類模塊去創建一個事例。而不是使用依賴的插入的方法去去除涉及到類的硬編碼。我們僅僅需要去保證:我們安全的采用模塊符號或者懶惰的評價去得到默認的引用。以下是一個服務,它需要在ActiveRecord模塊中創建一個小工具。我們采用懶惰的評價去介入的方法來替換直接的引用工具類。這可以解耦我們的代碼,同時不需要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
當你采用這種方式寫代碼時,你可以開始重新組織怎么建立自己的規范和最小化環境需求來運行這些規范和滿足規則需求的代碼。典型spec_helper.rb會有一個如下的一行代碼:
require File.expand_path("../../config/environment", __FILE__)
這個將會載入整個的Rails程序且降低測試運行速度。為了讓規范達到更快的速度,可以使用一個不含有上面那行代碼的配置文件。那么讓我們開始創建一個輕量級的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使用的自動裝載機,實際上并沒有導入所有的Rails。它是相當的輕量級并且方便性超過了損耗。在每個需要這個base包的helper里,我們將會添加我們程序相對應的部分到ActiveSupport::Dependencies.autoload_paths中。
簡單的Ruby對象說明
取決于你指定的應用程序部分,你可以在任意一個上下文中創建一個你所需要的輔助細則。例如,最簡單的是指定一個任意類型的Ruby純類作為服務類。如下面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"
模塊規范
測試模塊還需要做一點事情. 假設你現在正在用ActiveRecord你會需要建立一個和數據庫的連接. 我們并不需要將defactory_girl或者database_cleaner加入你的測試中,而且并不會真的創建對象. 實際上,唯一需要進行創建數據庫對象的地方就是當你進行特定對象測試的時候.當你確實需要創建一些對象的時候,你只需要手動的進行清理和轉換. 這就是一個樣例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"
特點說明
最后, 當我們創建特色應用時, 我們會需要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 |
寫牛逼的代碼,隔離你的單獨模塊,然后,享受編碼的樂趣吧。