Rails本身是提供一套自动化测试体系的,对于比较小型或者时间短促的项目采用原生的测试体系也是可以的,当然第三方自动化测试插件语法、功能、开发上相对也会更加便捷一些。 这里要介绍的插件有rspec-rail、capybara、factory_girl_rails、database_cleaner以及guard-rspec。

【1】gems introduction

首先简单对这些gems做一下介绍。 rspec-rail 是一个自动化测试插件,github地址:https://github.com/rspec/rspec-rails。 capybara是一个集成测试插件,它提供了更加语义话的语法模拟用户操作,githbu地址:https://github.com/jnicklas/capybara。 factory_girl_rails是用来替代rails fixtures的测试数据插件,gihubt地址:https://github.com/thoughtbot/factory_girl_rails,文档地址:https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md。 database_cleaner是用来清理数据库的插件,github地址:https://github.com/DatabaseCleaner/database_cleaner guard-rspec是一个自动化运行工具,他会帮你监测文件修改,并自动执行测试用例。gihubt地址:https://github.com/guard/guard-rspec。

【2】install gems 在Gemfile中添加如下代码:

group :development, :test do
  gem 'rspec-rails'
end

group :test do
	gem 'factory_girl_rails'
	gem 'capybara'
	gem 'guard-rspec'
	gem 'database_cleaner'
end

插件安装好后需要针对每一个插件进行一些必要的配置。

【3】config gems

1】执行如下命令安装rspec-rails $ rails generate rspec:install 之后rspec会在项目根目录下创建一个spec目录以及一个rails_helper.rb文件和一个spec_helper.rb文件。 下面内容将主要对rails_helper.rb文件进行配置。

2】整合capybara和rspec,首先打开rails_helper.rb文件,你会看到文件头部有自动生成如下语句:

ENV['RAILS_ENV'] ||= 'test'
require 'spec_helper'
require File.expand_path('../../config/environment', __FILE__)
require 'rspec/rails'

在这段下面添加如下语句引入capybara,这样就在测试文件里引用capybara了。

require 'capybara/rspec'

但是目前还无法直接调用capybara的方法,还需要在rails_helper.rb中进一步配置。 找到RSpec.configure代码段,并添加如下代码:

 config.include Capybara::DSL

这样就可以在测试类中调用Capybara的方法了。

3】整合factory_girl_rails,这里factory_girl_rails可以采用support配置方式来整合,也可以像Capybara一样直接在rails_helper.rb中添加引用,我先说明引用方式的整合。 同Capybara类似,在rails_helper.rb的RSpec.configure代码段中添加如下代码:

config.include FactoryGirl::Syntax::Methods

这样就可以在测试文件中使用factory_girl的方法了。 另外一种稍微复杂的方式是通过support整合,这里简单说明下如何配置,但是并不推荐,除非你有额外的需求需要配置。 首先取消rails_helper.rb中这段代码的注释

#Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

接下来在spec目录下创建一个support目录,并添加factory_girls.rb文件,代码如下:

# spec/support/factory_girl.rb
RSpec.configure do |config|
  config.include FactoryGirl::Syntax::Methods
end

这样也可以实现和第一种直接加代码段同样的效果(其实这种方式只是将配置抽离成文件形式而已)。 最后在spec目录下添加factories.rb文件,这个文件就是factory_girl_rails用来维护测试数据的,也就是取代rails fixtures功能的文件。我会在稍后简单介绍如何使用。 4】整合database_cleaner,同样在rails_helper.rb文件中加入database_cleaner的引入语句:

require 'database_cleaner'

5】初始化guard,在命令行执行如下命令: $ guard init rspec 执行如下命令启动guard,监听测试文件的修改并执行测试用例: $ guard 接下来就可以创建测试文件并实现测试方法进行测试了。

【3】how to use rspec-rails

因为rspec是整个测试过程中作为“容器”或者说做根本的插件,所以首先说明如何使用rspec。 首先说明下,当你install rspec-rails后,再执行rails g 命名自动创建文件后,rspec-rails会帮你在spec目录下创建对应的测试文件,同时原本在test目录下创建的文件将不再自动生成。这个时候针对你要进行的不同测试选择对应的文件去编程就可以了。当然除此之外你也可以生成特定的测试文件,具体命令如下: 生成单元测试文件,针对models进行测试: $ rails g rspec:model your_model_test_name 生成功能测试文件,针对controllers进行测试: $ rails g rspec:controller your_controller_test_name 生成集成测试文件,针对work flow进行测试: $ rails g integration_test your_integration_test_name 对应不同的测试文件type也是不同的,但是测试用例的书写是一样的,具体定义如下:

describe "Your tes description" do
    it "Your test case" do
      #some operate
      expect(actual_var).to expect_var
    end
    
    it "Your another test case" do
      #some operate
      expect(actual_var).to expect_var
    end
end

最后在命令行中输入如下语句检测测试文件是否通过 $ rspec spec/mdoels/your_model_test_case 也可以执行如下命令测试全部用例 $ rspec spec

【4】with factory_girl for unit test

从标题可以看出来,这部分要简单说明如何通过rspec和factory_girl一起对models进行单元测试。对models测试最核心的准备工作就是测试数据的维护,这里采用factory_girl来管理,首先说明factory_girl中的数据是如何定义的。 之前提到过在spec目录下创建了一个factories.rb,这个文件就是存放原始测试数据的。这里以user为例简单示例代码如下:

FactoryGirl.define do
	factory :user do |f|
		f.email "admin@163.com"
		f.encrypted_password "$2a$10$jJNRjL9XlQr4zhxwGCLUceiVtSX9X9GPeTTdoSjCTQ7eWnrePXwcK"
	end
end

其中:user就是一条记录的表示,而user代码段内就是对应的字段名称和值。当进行单元测试需要向数据库插入这个user记录时,在代码中通过如下语句实现:

user = create(:user, password: "your_test_password")

如果你没有在rspec配置文件中作factory_girl的配置,那么就需要直接调用FactoryGirl方法来插入数据了:

user = FatoryGirl.create(:user, password: "your_test_password")

【5】with capybara for integration test

在写集成测试用例的时候通常会用capybara提供的语法来模拟用户操作,并预期结果进行对比。而在做单元测试(针对models)和功能测试(针对controllers)时用rspec-rails本身的语法就可以。这里简单通过一个登录例子来简单展示下capybara:

RSpec.describe "LoginTests", type: :request do
  describe "GET /login_tests" do
  	before do
  		@user = create(:user, password: "admin1234")
  	end

    it "works! (now write some real specs)" do
      # user = build(:user)
      visit new_user_session_path
      expect(page).to have_content("后台管理")
      fill_in 'user_password', with: "admin1234"
      page.find('.btn-primary').click
      expect(page).to have_content("首页"), "登陆失败"
    end
  end
end

【6】auth by devise in tegration test

当你用到devise作为登陆模块的时候,想要在集成测试中设置登陆权限时需要在rails_hepler.rb中添加Warden::Test::Helpers的引用,Warden是devise用来做登陆所依赖的gem,因为devise的方法无法在requests目录下很好的兼容,所以这里直接使用它底层依赖的gem进行登陆的设置。 在rails_helper.rb顶部添加如下语句。

include Warden::Test::Helpers     
Warden.test_mode!

当需要在集成测试中添加登陆设置时,按照如下语句:

 let!(:admin) {
      @user = create(:user, password: "admin1234")
      login_as @user, scope: :user, :run_callbacks => false
    }

【7】javascript support by capybara-webkit 这里capybara-webkit是一个capybara的浏览器gem,它可以提供capybara对js语句的自动执行,也就是说当你的测试类中需要测试js的时候就需要用到这个gem,其他提供该功能的gem还有selenium,poltergeist,capybara-webkit运行效率相对要高一些,但是因为它是基于qt的,所以如果你想采用capybara-webkit作为js的支持,那么首先要安装qt。最后当你在it中要测试js的时候记得设置js: true。

  it "a integration test for js", js: true do
  end

更多用法请参考官方APIhttp://www.rubydoc.info/github/jnicklas/capybara

【附录1】rails_helper.rb

# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV['RAILS_ENV'] ||= 'test'
require 'spec_helper'
require File.expand_path('../../config/environment', __FILE__)
require 'rspec/rails'
require 'capybara/rspec'
require 'database_cleaner'

#auth for integration test.
include Warden::Test::Helpers     
Warden.test_mode!

# If you are not using ActiveRecord, you can remove this line.
#ActiveRecord::Migration.maintain_test_schema!

RSpec.configure do |config|
  #use Capybara in test files.
  config.include Capybara::DSL
  #support for javascript when integration test run.
  Capybara.javascript_driver = :webkit
  #use FactoryGirl in test files.
  config.include FactoryGirl::Syntax::Methods

  #use database_cleaner instead auto datas clear, for fix bugs in integration test.
  config.use_transactional_fixtures = false
  config.before(:suite) do
    DatabaseCleaner.clean_with :truncation
    DatabaseCleaner.strategy = :transaction
  end
  #include support conig file if you have
  #config.infer_spec_type_from_file_location!
  #config.expect_with(:rspec) { https://robots.thoughtbot.com/how-we-test-rails-applications|c| c.syntax = :should }
 
end

【附录2】单元测试用例

require 'rails_helper'

RSpec.describe Admin::Article, type: :model do

	context "will vaild" do
		it "when all field filled" do
			article = build(:article)
			expect(article).to be_valid
		end
	end

	context "will not vaild" do
		it "when artile category id is empty" do
			article = build(:article, article_category_id: nil)
			expect(article).not_to be_valid
			expect(article.errors.messages[:article_category_id]).to include "不能为空"
		end

		it "when title is empty" do
			article = build(:article, title: nil)
			expect(article).not_to be_valid
			expect(article.errors.messages[:title]).to include "不能为空"
		end

		it "when content is empty" do
			article = build(:article, content: nil)
			expect(article).not_to be_valid
			expect(article.errors.messages[:content]).to include "不能为空"
		end
	end

end

【附录3】集成测试用例

require 'rails_helper'

RSpec.describe "PageTests", type: :request do
	describe "GET /page_tests" do
		let!(:admin) {
			@user = create(:user, password: "admin1234")
			login_as @user, scope: :user, :run_callbacks => false
		}

		it "safely workflow" do
			visit admin_pages_path
			expect(page).to have_content("文档管理")
			click_link '添加'
			expect(page).to have_content("编辑文档")
			fill_in 'admin_page_title', with: "测试文档标题"
			fill_in 'admin_page_abstract', with: "测试文档摘要"
			fill_in 'admin_page_content', with: "测试文档内容"
			click_button '提交'
			expect(page).to have_content("文档添加成功。")
			click_link '修改'
			expect(page).to have_selector("input[value='测试文档标题']")
			expect(page).to have_field('admin_page_abstract', with: '测试文档摘要')
			expect(page).to have_field('admin_page_content', with: '测试文档内容')
			fill_in 'admin_page_title', with: "修改测试文档标题"
			fill_in 'admin_page_abstract', with: "修改测试文档摘要"
			fill_in 'admin_page_content', with: "修改测试文档内容"
			click_button '提交'
			expect(page).to have_content("文档更新成功。")
			click_link '返回'
			click_link '删除'
			expect(page).to have_content("文档删除成功。")
		end
	end
end

【附录4】参考播客

1】.Setting up the BDD stack on a new Rails 4 application

2】.How We Test Rails Applications

3】.Installing RSpec and Capybara in Rails 4.1