主要是转载吧,文档在墙的另一边,翻过去嫌麻烦,更多详细内容:
[url]http://relishapp.com/rspec[/url]
[color=red][b]shared examples[/b][/color]
有3种方法导入shared example group
include_examples "name"
it_behaves_like "name"
it_should_behave_like "name"
警告:包含shared groups的文件必须在使用前首先被加载。下面有一些开发人员要遵守的约定,RSpec并不做任何特殊处理(例如自动加载)。因为那需要对文件命名进行严格的规定,有可能会破坏已存在的测试代码。
[quote="约定"]
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跟使用它们的代码放在同一个文件当中?不过反正知道原则了,见机行事吧。)[/quote]
最简单的用法:
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
[b]shared context[/b]
shared context允许你像复用module中定义的方法一样,复用整个shared context中定义的内容,包括:
[i]方法定义
before/after
let
subject[/i]
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
[b][color=red]命令行:[/color][/b]
--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
[b][color=red]pending[/color][/b]
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
[color=red][b]Hooks[/b][/color]
[b]before/after[/b]
支持:all、:each,默认为:each
#此处略去我也不知道多少字。
[b]around[/b]
简单示例:
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
[b]Filter[/b]
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)。
[color=red][b]subject[/b][/color]
假如传递给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
[b]subject的属性[/b]
可以用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
[color=red][b]Helper Methods[/b][/color]
[b]let 和 let![/b]
生成一个返回值被[url="http://www.iteye.com/topic/810957"]memoized[/url]的方法
$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
[color=red][b]Metadata[/b][/color]
可以通过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
[b]用户自定义的metadata[/b]
可以通过给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
[color=red][b]Filtering[/b][/color]
[b]inclusion filters[/b]
运行测试时的example过滤器,其实就是用前面提到的tag,只是不在执行命令时指定,而是写在配置文件里:
RSpec.configure do |c|
# filter_run is short-form alias for filter_run_including
c.filter_run :focus => true
end
[b]exclusion filters[/b]
和inclusion filters相反。
[b]:if and :unless[/b]
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
[b]run all when everything filtered[/b]
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
[color=red][b]配置[/b][/color]
[b]read command line configuration options from files[/b]
前面提到的--color 和--format可以配置在一个名为.rspec的文件里
[b]fail fast[/b]
让RSpec在运行中遇到第一个通不过的测试就中断。
RSpec.configure {|c| c.fail_fast = true}