提升你的 Rails Specs 性能 10 倍 已翻译 100%

oschina 投递于 2014/03/13 07:18 (共 6 段, 翻译完成于 03-24)
阅读 2576
收藏 27
1
加载中

One of the primary reasons people end up being lax in letting specifications drive the development of their Rails applications is the time it takes to get feedback from running the suite of specifications. A number of tools have been built to help alleviate this pain like Spork, Zeus, and Spring. In fact Rails 4.1 will now come with Spring standard. Unfortunately, these tools are just crutches that tackle the symptoms of the problem rather than the problem itself. The actual problem is writing tightly coupled code that expects to have the full Rails framework always present, which is slow to start up.

已有 1 人翻译此段
我来翻译

Developing Decoupled Code

The solution is to write code that is isolated and decouple your components from as much of the system as possible. In other words, write SOLID Rails code. As a specific example, one might typically directly use a model class to create an instance. Instead we can use dependency injection to remove hard coded references to classes. We just need to make sure we safely reference the defaults using either block notation or a lazy evaluating ||=. Below we have a service that needs to create Widgets which happen to be ActiveRecord models. Instead of directly referencing theWidget class, we use lazy evalutation in our chosen injection method. This allows us to decouple our code and not need ActiveRecord loaded.

# 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
已有 2 人翻译此段
我来翻译

A Base Rails-free Configuration

When writing your applications in this way you can then start to restructure how you setup your specifications and minimize the required environment to run both your specification and your code fulfilling the specification. The typical spec_helper.rb will have a line like this:

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

This is what loads your entire Rails application and slows down the running of your tests. To make your specifications faster, you need to use a configuration file that does not contain this line. So let’s start by creating a very light weight base_spec_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'

We are requiring active_support and active_support/dependencies so we can have access to the autoloader Rails uses without actually loading up all of Rails. It is fairly light weight and the convienence outweighs the cost. In each spec helper which requires this base we will add the relevant portions of our app into the ActiveSupport::Dependencies.autoload_paths.

已有 1 人翻译此段
我来翻译

Plain Ruby Object Specifications

Depending on the part of application that you are specifying, you can create spec helpers specific to what you need in any one context. For example, the simplest would be one for specifying any type of pure Ruby class such as a service class. A sample services_spec_helper.rb might be:

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"

Decorator Specifications

For your decorators, you might choose to use Draper and your decorators_spec_helper.rb might look like:

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"
已有 1 人翻译此段
我来翻译

Model Specifications

Testing models needs a little bit more. Assuming you are using ActiveRecord you’ll need to include that as well as establish a connection to your database. We won’t include factory_girl or database_cleaner as most of your tests should not be actually creating database objects. In fact, the only place you really need to actually create an object in the database is when testing uniqueness validations. When you do need to create something you can just manually clean it up or use a transaction. So a sample models_spec_helper.rb can look like this:

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"

Feature Specifications

Finally, when creating feature specs, we do need our full Rails stack and ourfeature_spec_helper.rb is going to look very similar to what your currentspec_helper.rb looks like.

已有 1 人翻译此段
我来翻译

Summary

I found myself using varitions on the above spec helpers in projects I work on and decided I would write a set of generators to make it easier to bootstrap the project. The gem can be found at https://github.com/Originate/rails_spec_harness

While introducing these changes into existing projects I have found speed increases of 8-12 times. The worst project experienced a 27x increase once these changes and the corresponding changes in coding habits where applied. As an example I made a specification with 4 examples for a plain Ruby class. I then used the time command line utility to measure running rspec with the minimal spec helper as well as the full Rails spec helper and found the following:

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

Write SOLID code, isolate your specifications and enjoy a fun and sane development experience.

已有 1 人翻译此段
我来翻译
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(8)

Haimei
Haimei

引用来自“catalogMan”的评论

中文愣是没看懂,看英文才看懂…特别是那个@HaiMei ,机翻太明显,什么事例(instanse),什么懒惰的评价(||=),这都翻的什么玩意,ruby都不懂,来刷分的吧?怎么上的首页,小编都瞎了么

引用来自“Haimei”的评论

我不是来刷分的,也没有这个必要!我只是工作之余,看到感兴趣的文章就贡献自己的理解。翻译的不够准确地方,您可以指出来,我虚心接受。但是趾高气昂的说别人不懂的人,您如果什么都懂,为什么还要跑来看翻译呢?您如果英文这么牛逼,为什么还要先看中文的!您如果真的看了英文才看懂,为什么不贡献你的翻译呢?

引用来自“catalogMan”的评论

我已提交第二段的翻译,等待审核通过。看过你的翻译之后,我的确可以承认懂的比你多,我可以回答一下你对我的指责:1. 我批评的语气强硬我承认,但是批评的作品同样非常让人失望,我赞成你业余时贡献的热情,但是你的水平有目共睹,不必争论。2. 我使用了 ruby 2年,包括其他所有语言和工具,我很少请教同学,或者同事,全部自行阅读官方的文档和 StackOverflow,是的,我觉得我懂得比你多。3. 由于第二点,我不怎么翻译好的文章,而是直接保存到 Pocket。ps:我把原文粘贴到了谷歌翻译,的确是‘懒惰的评价’,简直……

很高兴你能提交你的翻译。这样让更多人的能够正确地理解。我也会虚心从中学习。谢谢。对于你对我的评价和你个人的素养,我只能说,呵呵。

catalogMan
catalogMan

光看中文,第一段和第四段翻译有很明显的问题,晚些时间我再提一下这两段。可以看得出有些译者也是一知半解就把译文贴上来了,毕竟是个大站浏,浏览这么高,不要影响其他读者。

catalogMan
catalogMan

引用来自“catalogMan”的评论

中文愣是没看懂,看英文才看懂…特别是那个@HaiMei ,机翻太明显,什么事例(instanse),什么懒惰的评价(||=),这都翻的什么玩意,ruby都不懂,来刷分的吧?怎么上的首页,小编都瞎了么

引用来自“Haimei”的评论

我不是来刷分的,也没有这个必要!我只是工作之余,看到感兴趣的文章就贡献自己的理解。翻译的不够准确地方,您可以指出来,我虚心接受。但是趾高气昂的说别人不懂的人,您如果什么都懂,为什么还要跑来看翻译呢?您如果英文这么牛逼,为什么还要先看中文的!您如果真的看了英文才看懂,为什么不贡献你的翻译呢?

我已提交第二段的翻译,等待审核通过。看过你的翻译之后,我的确可以承认懂的比你多,我可以回答一下你对我的指责:1. 我批评的语气强硬我承认,但是批评的作品同样非常让人失望,我赞成你业余时贡献的热情,但是你的水平有目共睹,不必争论。2. 我使用了 ruby 2年,包括其他所有语言和工具,我很少请教同学,或者同事,全部自行阅读官方的文档和 StackOverflow,是的,我觉得我懂得比你多。3. 由于第二点,我不怎么翻译好的文章,而是直接保存到 Pocket。ps:我把原文粘贴到了谷歌翻译,的确是‘懒惰的评价’,简直……

Haimei
Haimei

引用来自“catalogMan”的评论

中文愣是没看懂,看英文才看懂…特别是那个@HaiMei ,机翻太明显,什么事例(instanse),什么懒惰的评价(||=),这都翻的什么玩意,ruby都不懂,来刷分的吧?怎么上的首页,小编都瞎了么

我不是来刷分的,也没有这个必要!我只是工作之余,看到感兴趣的文章就贡献自己的理解。翻译的不够准确地方,您可以指出来,我虚心接受。但是趾高气昂的说别人不懂的人,您如果什么都懂,为什么还要跑来看翻译呢?您如果英文这么牛逼,为什么还要先看中文的!您如果真的看了英文才看懂,为什么不贡献你的翻译呢?

catalogMan
catalogMan
中文愣是没看懂,看英文才看懂…特别是那个@HaiMei ,机翻太明显,什么事例(instanse),什么懒惰的评价(||=),这都翻的什么玩意,ruby都不懂,来刷分的吧?怎么上的首页,小编都瞎了么
水涵
水涵
ruby社区已经在慢慢讨论 如何去rails化了~~
encro
encro
机器翻译的么?
我们安全的采用模块符号或者懒惰的评价去得到默认的引用
Zoker
Zoker
学习
返回顶部
顶部