Rspec是Ruby社区流行的测试框架之一,另一个是Minetest。
前言
RSpec包含:
respec-core
respec-expectations
rspec-mocks
rspec-rails
rspec-support
安装了rspec后,在终端执行:rspec --version
,可以看到每个部分的版本。
对于RSpec框架,Rails项目里面用的比较多的是Rspec-rails。
最佳效果:写最少的测试,测最多的东西。
基本结构:
最简单的一个场景一个例子:
describe/it:
RSpec.describe "something" do
it "does something" do
end
end
嵌套:
RSpec.describe "something" do
context "in one context" do
it "does one thing" do
end
end
context "in another context" do
it "does another thing" do
end
end
end
在测试前,通常需要创建测试数据,一般会在每个测试案例中单独创建,也可以借助before【hook methods】来简化,减少重复创建。before调用有两种形式:
- before(:each, &block) - default mode
- before(:all, &block)
使用before(:each ) 创建的测试数据,在每个测试结束后都会回滚,即每个测试里面的数据都是独立的。每次执行context前,都会重新执行一次before(:each),而before(:all) 只执行一次,被这个describe/context下的所有it公用。使用before(:all)时,建议配after(:all) 来清空数据,保证多个describe之间的独立性,同时使用before(:each) 来重新加载数据,以获取最新的数据。
比如:
before(:all) do
@widget = Widget.create!
end
before(:each) do
@widget.reload
end
after(:all) do
@widget.destroy
end
Shared examples
shared_examples 是Rspec的类方法,用于定义class 或者 module的行为,shared的含义就是共享,个人的理解是把多个测试中,重合度高的地方抽出来,封装起来,类似于include module,在module中定义常用方法,每个类include 该module后,就拥有了该module的实例方法。
可以使用以下四种方式来将定义的shared_examples include到context中:
include_examples "name" # include the examples in the current context
it_behaves_like "name" # include the examples in a nested context
it_should_behave_like "name" # include the examples in a nested context
matching metadata # include the examples in the current context
备注:metadata是什么?describe的部分常会看到 type: :model,这个就是RSpec metadata.【官网中举了相关的例子,还不是很懂】
看个官方的例子:
require "set"
RSpec.shared_examples "a collection" do
let(:collection) { described_class.new([7, 2, 4]) }
context "initialized with 3 items" do
it "says it has three items" do
expect(collection.size).to eq(3)
end
end
describe "#include?" do
context "with an item that is in the collection" do
it "returns true" do
expect(collection.include?(7)).to be_truthy
end
end
context "with an item that is not in the collection" do
it "returns false" do
expect(collection.include?(9)).to be_falsey
end
end
end
end
RSpec.describe Array do
it_behaves_like "a collection"
end
RSpec.describe Set do
it_behaves_like "a collection"
end
带参数的shard_example:
RSpec.shared_examples "a measurable object" do |measurement, measurement_methods|
measurement_methods.each do |measurement_method|
it "should return #{measurement} from ##{measurement_method}" do
expect(subject.send(measurement_method)).to eq(measurement)
end
end
end
RSpec.describe Array, "with 3 items" do
subject { [1, 2, 3] }
it_should_behave_like "a measurable object", 3, [:size, :length]
end
RSpec.describe String, "of 6 characters" do
subject { "FooBar" }
it_should_behave_like "a measurable object", 6, [:size, :length]
end
也可以在context中定义shared_examples,然后在该context中调用,注意,可将context看作是一个作用域,出了这个context,则不能调用定义在该context 中的shared_examples:
RSpec.describe "shared examples" do
context "per context" do
shared_examples "shared examples are nestable" do
it { expect(true).to eq true }
end
it_behaves_like "shared examples are nestable"
end
end
如果在另一个context中调用,则会报错:
RSpec.describe "shared examples" do
context "per context" do
shared_examples "shared examples are nestable" do
it { expect(true).to eq true }
end
end
context "another context" do
it_behaves_like "shared examples are nestable"
end
end
Shared context
shared_context顾名思义,将多个context中共用的部分抽出来,定义成method来反复调用。
调用已定义好的shared_context的方式有两种,一是include_context,而是metadata。
看一段简单的例子,先定义一个shared_context:
### one describe
RSpec.describe "somethings 1" do
context "context 1" do
let(:a) { create :a }
let(:b) { create :b }
let(:c) { create :c }
let(:d) { create :d }
........ # do something
end
end
### another describe
RSpec.describe "somethings 2" do
context "context 2" do
let(:a) { create :a }
let(:b) { create :b }
let(:c) { create :c }
let(:d) { create :d }
........ # do something
end
end
使用shared_context简化,定义shared_context :
在spec/support下定义一个文件,比如shared_context.rb:
RSpec.shared_context "shared data" do
let(:a) { create :a }
let(:b) { create :b }
let(:c) { create :c }
let(:d) { create :d }
end
简化之前的测试:
### one describe
RSpec.describe "somethings 1" do
context "context 1" do
include_context "shared data"
........ # do something
end
end
### another describe
RSpec.describe "somethings 2" do
context "context 2" do
include_context "shared data"
........ # do something
end
end
如果使用metadata,如何调用?参考官网的例子,需要修改下刚刚创建的这个shared_data.rb文件:
RSpec.configure do |rspec|
rspec.shared_context_metadata_behavior = :apply_to_host_groups ## 用于设置metadata调用后的作用域
end
RSpec.shared_context "shared data", :shared_context => :metadata do
let(:a) { create :a }
let(:b) { create :b }
let(:c) { create :c }
let(:d) { create :d }
end
RSpec.configure do |rspec|
rspec.include_context "shared data", :include_shared => true
end
那么上面的例子,就可以这样调用了:
### one describe
RSpec.describe "somethings 1", :include_shared => true do
context "context 1" do
........ # do something
end
end
### another describe
RSpec.describe "somethings 2", :include_shared => true do
context "context 2" do
........ # do something
end
end
看到这里,说一下shared_context与shared_example的区别,一个是context层面,一个是class/module层的,如果多个describe中,含有重合度高的context/describe,那么用shared_example来抽出共同的行为进行简化,如果是在多个context中,含有重合度高的代码,比如新建各种测试数据等,更偏向setup,则使用shared_context。
其它
测试数据重置:
使用gem:database_rewinder
配置rewinder:
# spec/support/rspec_helper.rb RSpec.configure do |config| config.before :suite do DatabaseRewinder.clean_all end config.after :each do DatabaseRewinder.clean end end
Shoulda-matchers
使用Shoulda-matchers 来测试关联和验证:
require 'rails-helper' descirbe User do it { should has_many(:posts).dependent(:destroy) } it { should has_many(:groups).through(:groupships) } it { should validate_presence_of :name } end
is_expected vs expect
is_expected 等价于expect(subject)
比如下面这个例子:
it { is_expected.to validate_presence_of(:title) } # equals to it { expect(subject).to validate_presence_of(:title) }
RSpec测试缺省都有一个subject,值为described_class.new,而descirbed_class 是对应需要测试的class的实例,可以当成ruby中的self,如果是model,则是model.new, controller,则是controller.new。
参考
注:代码部分基本取自官网。