RSpec-Core 2.6 博客分类: Ruby 测试rspec
程序员文章站
2024-02-12 14:34:58
...
主要是转载吧,文档在墙的另一边,翻过去嫌麻烦,更多详细内容:
http://relishapp.com/rspec
shared examples
有3种方法导入shared example group
警告:包含shared groups的文件必须在使用前首先被加载。下面有一些开发人员要遵守的约定,RSpec并不做任何特殊处理(例如自动加载)。因为那需要对文件命名进行严格的规定,有可能会破坏已存在的测试代码。
The simplest approach is to require files with shared examples explicitly from the files that use them. Keep in mind that RSpec adds the spec directory to the LOAD_PATH, so you can say require 'shared_examples_for_widgets' to require a file at #{PROJECT_ROOT}/spec/shared_examples_for_widgets.rb.
1, 最简单的方法是直接在使用shared examples的地方显式导入(require)。RSpec默认会把spec目录加入LOAD_PATH中,所以你可以用require 'shared_examples_for_widgets'来导入 #{PROJECT_ROOT}/spec/shared_examples_for_widgets.rb。
Put files containing shared examples in spec/support/ and require files in that directory from spec/spec_helper.rb:
This is included in the generated spec/spec_helper.rb file in rspec-rails
2, 另一个方法是把所有包含shared examples的文件放到spec/suppoer目录下,并且在spec/spec_helper.rb中导入它们:
rspec-rails自动生成的spec/spec_helper.rb就是这么干的。
3, When all of the groups that include the shared group, just declare the shared group in the same file.(说实话,没太理解。把shared group跟使用它们的代码放在同一个文件当中?不过反正知道原则了,见机行事吧。)
最简单的用法:
可以通过block给shared examples提供上下文(当然在这个例子中,上面的代码更DRY):
还可以给shared examples传递参数:
给it_should_behave_like起个别名:
shared context
shared context允许你像复用module中定义的方法一样,复用整个shared context中定义的内容,包括:
方法定义
before/after
let
subject
也可以用metadata的方式include shared examples:
命令行:
--color: 让测试的运行结果以彩色显示
例:rspec some_spec.rb --color
--example [pattern]: 可指定要运行的example的名字,支持正则表达式。
例:rspec some_spec.rb --example 'first .* example'
--format [format]: 指定测试运行结果的输出格式。文档中好像只提到progress和documentation两种格式,我记得之前支持nested格式,试了一下,发现和documentation格式一样。而progress格式就是默认的格式(输出一串点号)。另外documentation可简写为doc。
例:rspec some_spec.rb --format doc
--out [filename]: 可以把测试运行结果输出到指定文件
例:rspec some_spec.rb --color --format documentation --out rspec_result.txt
--line_number [number]: 可指定要运行哪一行的example。
例:rspec some_spec.rb --line_number 8
还有一种简单的指定方法:rspec some_spec.rb:8
--tag [tag]: 不知道什么时候起(也许一开始)rspec也像cucumber一样支持tag了。
例:有以下代码
可以用这些方式指定运行以上的example:
rspec some_spec.rb --tag focus
rspec some_spec.rb --tag @focus
rspec some_spec.rb --tag type:special
rspec some_spec.rb --tag @type:special
在tag前面加~符号可以跳过该tag,执行其它全部的example。
rspec some_spec.rb --tag ~focus
rspec some_spec.rb --tag ~@focus
pending
pending的几种方式:
pending还支持条件:
Hooks
before/after
支持:all、:each,默认为:each
#此处略去我也不知道多少字。
around
简单示例:
另一种写法:
全局hook:
Filter
支持的filter还有 after(:each)、around(:each)、before(:all)、after(:all)。
subject
假如传递给describe的第一个参数是个Class,那么在这个example groups中的每个example里调用subject都将获得一个该Class的实例。
还可以在example group的内部清晰的定义subject的返回值(与describe的第一个参数无关):
subject的属性
可以用its方法来直接读取subject的属性,并对其属性进行测试:
当should方法被直接调用(没有显式的接收者)的时候,它的接收者是subject,例:
Helper Methods
let 和 let!
生成一个返回值被memoized的方法
区别在于,let的block里的代码是延迟到该方法第一次被调用时执行,而let!的block里的代码则是在每个example执行之前被隐式的before调用。源码如下:
可以在一个example group(describe或context的block范围)内定义helper方法,这个方法可以被当前example group以及它的sub example group调用,但不可以被parent example group调用。
helper方法可以被提到一个module中重用。
首先在某文件例如helpers.rb中定义如下module:
然后:
可以指定在某一类example group 中使用helper:
Metadata
可以通过example方法访问example本身以及本身的一些元数据:
如果传递给describe的第一个参数是一个类,可以用described_class访问它。
用户自定义的metadata
可以通过给describe、context和it方法的最后一个参数传递hash的方式自定义metadata。
在describe或context中定义的example group中定义metadata,可以被当前example group的sub example group和example直接访问和覆盖。
另外,有一个选项允许用户通过symbol来直接定义metadata: treatsymbolsasmetadatakeyswithtrue_values
Filtering
inclusion filters
运行测试时的example过滤器,其实就是用前面提到的tag,只是不在执行命令时指定,而是写在配置文件里:
exclusion filters
和inclusion filters相反。
:if and :unless
run all when everything filtered
配置
read command line configuration options from files
前面提到的--color 和--format可以配置在一个名为.rspec的文件里
fail fast
让RSpec在运行中遇到第一个通不过的测试就中断。
http://relishapp.com/rspec
shared examples
有3种方法导入shared example group
include_examples "name" it_behaves_like "name" it_should_behave_like "name"
警告:包含shared groups的文件必须在使用前首先被加载。下面有一些开发人员要遵守的约定,RSpec并不做任何特殊处理(例如自动加载)。因为那需要对文件命名进行严格的规定,有可能会破坏已存在的测试代码。
约定 写道
The simplest approach is to require files with shared examples explicitly from the files that use them. Keep in mind that RSpec adds the spec directory to the LOAD_PATH, so you can say require 'shared_examples_for_widgets' to require a file at #{PROJECT_ROOT}/spec/shared_examples_for_widgets.rb.
1, 最简单的方法是直接在使用shared examples的地方显式导入(require)。RSpec默认会把spec目录加入LOAD_PATH中,所以你可以用require 'shared_examples_for_widgets'来导入 #{PROJECT_ROOT}/spec/shared_examples_for_widgets.rb。
Put files containing shared examples in spec/support/ and require files in that directory from spec/spec_helper.rb:
Dir["./spec/support/**/*.rb"].each {|f| require f}
This is included in the generated spec/spec_helper.rb file in rspec-rails
2, 另一个方法是把所有包含shared examples的文件放到spec/suppoer目录下,并且在spec/spec_helper.rb中导入它们:
Dir["./spec/support/**/*.rb"].each {|f| require f}
rspec-rails自动生成的spec/spec_helper.rb就是这么干的。
3, When all of the groups that include the shared group, just declare the shared group in the same file.(说实话,没太理解。把shared group跟使用它们的代码放在同一个文件当中?不过反正知道原则了,见机行事吧。)
最简单的用法:
require 'set' 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 collection.size.should == 3 end end describe '#include?' do context 'with an item that is in the collection' do it 'returns true' do collection.should include(7) end end context 'with an item that is not in the collection' do it 'returns false' do collection.should_not include(9) end end end end describe Array do it_behaves_like 'a collection' end describe Set do it_behaves_like 'a collection' end
可以通过block给shared examples提供上下文(当然在这个例子中,上面的代码更DRY):
require 'set' shared_examples 'a collection' do context 'initialized with 3 items' do it 'says it has three items' do collection.size.should == 3 end end describe '#include?' do context 'with an item that is in the collection' do it 'returns true' do collection.include?(7).should be_true end end context 'with an item that is not in the collection' do it 'returns false' do collection.include?(9).should be_false end end end end describe Array do it_behaves_like 'a collection' do let(:collection){Array.new([7,2,4])} end end describe Set do it_behaves_like 'a collection' do let(:collection){Set.new([7,2,4])} end end
还可以给shared examples传递参数:
shared_examples "a measurable object" do |measurement, measurement_methods| measurement_methods.each do |measurement_method| it "should return #{measurement} from ##{measurement_method}" do subject.send(measurement_method).should == measurement end end end describe Array, "with 3 items" do subject { [1, 2, 3] } it_should_behave_like "a measurable object", 3, [:size, :length] end describe String, "of 6 characters" do subject { "FooBar" } it_should_behave_like "a measurable object", 6, [:size, :length] end
给it_should_behave_like起个别名:
RSpec.configure do |c| c.alias_it_should_behave_like_to :it_has_behavior, 'has behavior:' end
shared_examples 'sortability' do it 'responds to <=>' do sortable.should respond_to(:<=>) end end describe String do it_has_behavior 'sortability' do let(:sortable) { 'sample string' } end end
shared context
shared context允许你像复用module中定义的方法一样,复用整个shared context中定义的内容,包括:
方法定义
before/after
let
subject
shared_context "shared stuff", :a => :b do before { @some_var = :some_value } def shared_method "it works" end let(:shared_let) { {'arbitrary' => 'object'} } subject do 'this is the subject' end end describe "group that includes a shared context using 'include_context'" do include_context "shared stuff" it "has access to methods defined in shared context" do shared_method.should eq("it works") end it "has access to methods defined with let in shared context" do shared_let['arbitrary'].should eq('object') end it "runs the before hooks defined in the shared context" do @some_var.should be(:some_value) end it "accesses the subject defined in the shared context" do subject.should eq('this is the subject') end end
也可以用metadata的方式include shared examples:
describe "group that includes a shared context using metadata", :a => :b do it "has access to methods defined in shared context" do shared_method.should eq("it works") end it "has access to methods defined with let in shared context" do shared_let['arbitrary'].should eq('object') end it "runs the before hooks defined in the shared context" do @some_var.should be(:some_value) end it "accesses the subject defined in the shared context" do subject.should eq('this is the subject') end end
命令行:
--color: 让测试的运行结果以彩色显示
例:rspec some_spec.rb --color
--example [pattern]: 可指定要运行的example的名字,支持正则表达式。
例:rspec some_spec.rb --example 'first .* example'
--format [format]: 指定测试运行结果的输出格式。文档中好像只提到progress和documentation两种格式,我记得之前支持nested格式,试了一下,发现和documentation格式一样。而progress格式就是默认的格式(输出一串点号)。另外documentation可简写为doc。
例:rspec some_spec.rb --format doc
--out [filename]: 可以把测试运行结果输出到指定文件
例:rspec some_spec.rb --color --format documentation --out rspec_result.txt
--line_number [number]: 可指定要运行哪一行的example。
例:rspec some_spec.rb --line_number 8
还有一种简单的指定方法:rspec some_spec.rb:8
--tag [tag]: 不知道什么时候起(也许一开始)rspec也像cucumber一样支持tag了。
例:有以下代码
describe "group with tagged specs" do it "example I'm working now", :focus => true do; end it "special example with string", :type => 'special' do; end it "special example with symbol", :type => :special do; end it "slow example", :skip => true do; end it "ordinary example", :speed => 'slow' do; end it "untagged example" do; end end
可以用这些方式指定运行以上的example:
rspec some_spec.rb --tag focus
rspec some_spec.rb --tag @focus
rspec some_spec.rb --tag type:special
rspec some_spec.rb --tag @type:special
在tag前面加~符号可以跳过该tag,执行其它全部的example。
rspec some_spec.rb --tag ~focus
rspec some_spec.rb --tag ~@focus
pending
pending的几种方式:
it 'should be pending' it 'should be pending' do pending('message') end pending do 'pending'.should == 'pending' end xit 'should be pending' do end
pending还支持条件:
it "is pending when pending with a true :if condition" do pending("true :if", :if => true) { run_test } end
Hooks
before/after
支持:all、:each,默认为:each
#此处略去我也不知道多少字。
around
简单示例:
class Database def self.transaction puts "open transaction" yield puts "close transaction" end end describe "around filter" do around(:each) do |example| Database.transaction(&example) end it "gets run in order" do puts "run the example" end end
另一种写法:
describe "around hook" do around(:each) do |example| puts "around each before" example.run puts "around each after" end it "gets run in order" do puts "in the example" end end
全局hook:
RSpec.configure do |c| c.around(:each) do |example| puts "around each before" example.run puts "around each after" end end
Filter
RSpec.configure do |config| config.before(:each, :foo => :bar) do invoked_hooks << :before_each_foo_bar end end describe "a filtered before :each hook" do let(:invoked_hooks) { [] } describe "group without matching metadata" do it "does not run the hook" do invoked_hooks.should be_empty end it "runs the hook for an example with matching metadata", :foo => :bar do invoked_hooks.should == [:before_each_foo_bar] end end describe "group with matching metadata", :foo => :bar do it "runs the hook" do invoked_hooks.should == [:before_each_foo_bar] end end end
支持的filter还有 after(:each)、around(:each)、before(:all)、after(:all)。
subject
假如传递给describe的第一个参数是个Class,那么在这个example groups中的每个example里调用subject都将获得一个该Class的实例。
describe Array, "when first created" do it "should be empty" do subject.should eq([]) end end
还可以在example group的内部清晰的定义subject的返回值(与describe的第一个参数无关):
describe Array, "with some elements" do subject { [1,2,3] } it "should have the prescribed elements" do subject.should == [1,2,3] end end
subject的属性
可以用its方法来直接读取subject的属性,并对其属性进行测试:
its(:size) { should eq(1) } its("length") { should eq(1) }
describe Array do context "when first created" do its(:size) { should eq(0) } end end
当should方法被直接调用(没有显式的接收者)的时候,它的接收者是subject,例:
describe Array do describe "when first created" do it { should be_empty } end end
Helper Methods
let 和 let!
生成一个返回值被memoized的方法
$count = 0 describe "let" do let(:count) { $count += 1 } it "memoizes the value" do count.should == 1 count.should == 1 end it "is not cached across examples" do count.should == 2 end end
区别在于,let的block里的代码是延迟到该方法第一次被调用时执行,而let!的block里的代码则是在每个example执行之前被隐式的before调用。源码如下:
module RSpec module Core module Let module ClassMethods def let(name, &block) define_method(name) do __memoized[name] ||= instance_eval(&block) end end def let!(name, &block) let(name, &block) before { __send__(name) } end end module InstanceMethods def __memoized # :nodoc: @__memoized ||= {} end end def self.included(mod) # :nodoc: mod.extend ClassMethods mod.__send__ :include, InstanceMethods end end end end
可以在一个example group(describe或context的block范围)内定义helper方法,这个方法可以被当前example group以及它的sub example group调用,但不可以被parent example group调用。
describe "an example" do def help :available end it "has access to methods defined in its group" do help.should be(:available) end end
describe "an example" do def help :available end describe "in a nested group" do it "has access to methods defined in its parent group" do help.should be(:available) end end end
helper方法可以被提到一个module中重用。
首先在某文件例如helpers.rb中定义如下module:
module Helpers def help :available end end
然后:
require './helpers' RSpec.configure do |c| c.include Helpers end describe "an example group" do it "has access the helper methods defined in the module" do help.should be(:available) end end
require './helpers' RSpec.configure do |c| c.extend Helpers end describe "an example group" do puts "Help is #{help}" it "does not have access to the helper methods defined in the module" do expect { help }.to raise_error(NameError) end end
可以指定在某一类example group 中使用helper:
require './helpers' RSpec.configure do |c| c.include Helpers, :foo => :bar end describe "an example group with matching metadata", :foo => :bar do it "has access the helper methods defined in the module" do help.should be(:available) end end describe "an example group without matching metadata" do it "does not have access to the helper methods defined in the module" do expect { help }.to raise_error(NameError) end end
RSpec.configure do |c| c.extend Helpers, :foo => :bar end describe "an example group with matching metadata", :foo => :bar do puts "In a matching group, help is #{help}" it "does not have access to the helper methods defined in the module" do expect { help }.to raise_error(NameError) end end describe "an example group without matching metadata" do puts "In a non-matching group, help is #{help rescue 'not available'}" it "does not have access to the helper methods defined in the module" do expect { help }.to raise_error(NameError) end end
Metadata
可以通过example方法访问example本身以及本身的一些元数据:
describe "an example" do it "knows itself as example" do example.description.should eq("knows itself as example") end end
如果传递给describe的第一个参数是一个类,可以用described_class访问它。
describe Fixnum do it "is available as described_class" do described_class.should eq(Fixnum) end end
用户自定义的metadata
可以通过给describe、context和it方法的最后一个参数传递hash的方式自定义metadata。
在describe或context中定义的example group中定义metadata,可以被当前example group的sub example group和example直接访问和覆盖。
另外,有一个选项允许用户通过symbol来直接定义metadata: treatsymbolsasmetadatakeyswithtrue_values
describe "a group with user-defined metadata", :foo => 17 do it 'has access to the metadata in the example' do example.metadata[:foo].should == 17 end it 'does not have access to metadata defined on sub-groups' do example.metadata.should_not include(:bar) end describe 'a sub-group with user-defined metadata', :bar => 12 do it 'has access to the sub-group metadata' do example.metadata[:foo].should == 17 end it 'also has access to metadata defined on parent groups' do example.metadata[:bar].should == 12 end end end
describe "a group with no user-defined metadata" do it 'has an example with metadata', :foo => 17 do example.metadata[:foo].should == 17 example.metadata.should_not include(:bar) end it 'has another example with metadata', :bar => 12, :bazz => 33 do example.metadata[:bar].should == 12 example.metadata[:bazz].should == 33 example.metadata.should_not include(:foo) end end
describe "a group with user-defined metadata", :foo => 'bar' do it 'can be overridden by an example', :foo => 'bazz' do example.metadata[:foo].should == 'bazz' end describe "a sub-group with an override", :foo => 'goo' do it 'can be overridden by a sub-group' do example.metadata[:foo].should == 'goo' end end end
RSpec.configure do |c| c.treat_symbols_as_metadata_keys_with_true_values = true end describe "a group with simple metadata", :fast, :simple, :bug => 73 do it 'has `:fast => true` metadata' do example.metadata[:fast].should == true end it 'has `:simple => true` metadata' do example.metadata[:simple].should == true end it 'can still use a hash for metadata' do example.metadata[:bug].should == 73 end it 'can define simple metadata on an example', :special do example.metadata[:special].should == true end end
Filtering
inclusion filters
运行测试时的example过滤器,其实就是用前面提到的tag,只是不在执行命令时指定,而是写在配置文件里:
RSpec.configure do |c| # filter_run is short-form alias for filter_run_including c.filter_run :focus => true end
exclusion filters
和inclusion filters相反。
:if and :unless
describe ":if => true group", :if => true do it(":if => true group :if => true example", :if => true) { } it(":if => true group :if => false example", :if => false) { } it(":if => true group no :if example") { } end describe ":if => false group", :if => false do it(":if => false group :if => true example", :if => true) { } it(":if => false group :if => false example", :if => false) { } it(":if => false group no :if example") { } end describe "no :if group" do it("no :if group :if => true example", :if => true) { } it("no :if group :if => false example", :if => false) { } it("no :if group no :if example") { } end describe ":unless => true group", :unless => true do it(":unless => true group :unless => true example", :unless => true) { } it(":unless => true group :unless => false example", :unless => false) { } it(":unless => true group no :unless example") { } end describe ":unless => false group", :unless => false do it(":unless => false group :unless => true example", :unless => true) { } it(":unless => false group :unless => false example", :unless => false) { } it(":unless => false group no :unless example") { } end describe "no :unless group" do it("no :unless group :unless => true example", :unless => true) { } it("no :unless group :unless => false example", :unless => false) { } it("no :unless group no :unless example") { } end
run all when everything filtered
RSpec.configure do |c| c.filter_run :focus => true c.run_all_when_everything_filtered = true end describe "group 1" do it "group 1 example 1" do end it "group 1 example 2" do end end describe "group 2" do it "group 2 example 1" do end end
配置
read command line configuration options from files
前面提到的--color 和--format可以配置在一个名为.rspec的文件里
fail fast
让RSpec在运行中遇到第一个通不过的测试就中断。
RSpec.configure {|c| c.fail_fast = true}