欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

RSpec-Core 2.6 博客分类: Ruby 测试rspec 

程序员文章站 2024-02-12 14:34:58
...
主要是转载吧,文档在墙的另一边,翻过去嫌麻烦,更多详细内容:
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}
相关标签: 测试 rspec