Here’s an example using rspec-core:
rubyRSpec.describe Order do it "sums the prices of the items in its line items" do order = Order.new order.add_entry(LineItem.new(:item => Item.new( :price => Money.new(1.11, :USD) ))) order.add_entry(LineItem.new(:item => Item.new( :price => Money.new(2.22, :USD), :quantity => 2 ))) expect(order.total).to eq(Money.new(5.55, :USD)) endend
The describe
and it
methods come from rspec-core. TheOrder
, LineItem
, Item
and Money
classes would be fromyour code. The last line of the exampleexpresses an expected outcome. Iforder.total == Money.new(5.55, :USD)
, thenthe example passes. If not, it fails with a message like:
expected: #
got: #
ruby
expect(actual).to eq(expected) # passes if actual == expected
expect(actual).to eql(expected) # passes if actual.eql?(expected)
expect(actual).not_to eql(not_expected) # passes if not(actual.eql?(expected))
Note: The new expect
syntax no longer supports the==
matcher.
ruby
expect(actual).to be(expected) # passes if actual.equal?(expected)
expect(actual).to equal(expected) # passes if actual.equal?(expected)
ruby
expect(actual).to be > expected
expect(actual).to be >= expected
expect(actual).to be <= expected
expect(actual).to be < expected
expect(actual).to be_within(delta).of(expected)
ruby
expect(actual).to match(/expression/)
Note: The new expect
syntax no longer supports the=~
matcher.
ruby
expect(actual).to be_an_instance_of(expected) # passes if actual.class == expected
expect(actual).to be_a(expected) # passes if actual.is_a?(expected)
expect(actual).to be_an(expected) # an alias for be_a
expect(actual).to be_a_kind_of(expected) # another alias
ruby
expect(actual).to be_truthy # passes if actual is truthy (not nil or false)
expect(actual).to be true # passes if actual == true
expect(actual).to be_falsy # passes if actual is falsy (nil or false)
expect(actual).to be false # passes if actual == false
expect(actual).to be_nil # passes if actual is nil
expect(actual).to_not be_nil # passes if actual is not nil
ruby
expect { ... }.to raise_error
expect { ... }.to raise_error(ErrorClass)
expect { ... }.to raise_error("message")
expect { ... }.to raise_error(ErrorClass, "message")
ruby
expect { ... }.to throw_symbol
expect { ... }.to throw_symbol(:symbol)
expect { ... }.to throw_symbol(:symbol, 'value')
```rubyexpect { |b| 5.tap(&b) }.to yield_control # passes regardless of yielded args
expect { | b | yield_if_true(true, &b) }.to yield_with_no_args # passes only if no args are yielded |
expect { | b | 5.tap(&b) }.to yield_with_args(5) |
expect { | b | 5.tap(&b) }.to yield_with_args(Fixnum) |
expect { | b | “a string”.tap(&b) }.to yield_with_args(/str/) |
expect { |b| [1, 2, 3].each(&b) }.to yield_successive_args(1, 2, 3)expect { |b| { :a => 1, :b => 2 }.each(&b) }.to yield_successive_args([:a, 1], [:b, 2])```
ruby
expect(actual).to be_xxx # passes if actual.xxx?expect(actual).to have_xxx(:arg) # passes if actual.has_xxx?(:arg)
ruby
expect(1..10).to cover(3)
```ruby
expect(actual).to include(expected)
expect(actual).to start_with(expected)
expect(actual).to end_with(expected)
expect(actual).to contain_exactly(individual, items)# …which is the same as:expect(actual).to match_array( expected_array ) ```
ruby
expect([1, 2, 3]).to include(1)
expect([1, 2, 3]).to include(1, 2)
expect([1, 2, 3]).to start_with(1)
expect([1, 2, 3]).to start_with(1, 2)
expect([1, 2, 3]).to end_with(3)
expect([1, 2, 3]).to end_with(2, 3)
expect({:a => 'b'}).to include(:a => 'b')
expect("this string").to include("is str")
expect("this string").to start_with("this")
expect("this string").to end_with("ring")
expect([1, 2, 3]).to contain_exactly(2, 3, 1)
expect([1, 2, 3]).to match_array([3, 2, 1])
You can also create compound matcher expressions usingand
or or
:
ruby
expect(alphabet).to start_with("a").and end_with("z")
expect(stoplight.color).to eq("red").or eq("green").or eq("yellow")
Many of the built-in matchers are designed to take matchers asarguments, to allow you to flexibly specify only the essentialaspects of an object or data structure. In addition, all of thebuilt-in matchers have one or more aliases that provide betterphrasing for when they are used as arguments to another matcher.
```ruby
expect { k += 1.05 }.to change { k }.by( a_value_within(0.1).of(1.0) )
expect { s = “barn” }.to change { s } .from( a_string_matching(/foo/) ) .to( a_string_matching(/bar/) )
expect([“barn”, 2.45]).to contain_exactly( a_value_within(0.1).of(2.5), a_string_starting_with(“bar”))
expect([“barn”, “food”, 2.45]).to end_with( a_string_matching(“foo”), a_value > 2)
expect([“barn”, 2.45]).to include( a_string_starting_with(“bar”) )
expect(:a => “food”, :b => “good”).to include(:a => a_string_matching(/foo/))
hash = { :a => { :b => [“foo”, 5], :c => { :d => 2.05 } }}
expect(hash).to match( :a => { :b => a_collection_containing_exactly( a_string_starting_with(“f”), an_instance_of(Fixnum) ), :c => { :d => (a_value < 3) } })
expect { |probe| [1, 2, 3].each(&probe)}.to yield_successive_args( a_value < 2, 2, a_value > 2 )```
You always need to load rspec/expectations
even if you only want to use one part of the library:
ruby
require 'rspec/expectations'
Then simply include RSpec::Matchers
in any class:
```ruby
class MyClass include RSpec::Matchers
def do_something(arg)
expect(arg).to be > 0
# do other stuff
end
end```
should
and should_not
syntaxFrom the beginning RSpec::Expectations provided should
and should_not
methodsto define expectations on any object. In version 2.11expect
method wasintroduced which is now the recommended way to define expectations on an object.
should
to expect
should
and should_not
work by being added to every object. However, RSpecdoes not own every object and cannot ensure they work consistently on every object.In particular, they can lead to surprising failures when used with BasicObject-subclassedproxy objects.
expect
avoids these problems altogether by not needing to be available on all objects.
Before version 2.11 expect
was just a more readable alternative for blockexpectations. Since version 2.11expect
can be used for both block and valueexpectations.
expect(actual).to eq(expected)
expect { ... }.to raise_error(ErrorClass)
Seehttp://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntaxFor a detailed explanation
The one-liner syntax supported byrspec-core usesshould
even whenconfig.syntax = :expect
. It reads better than the alternative, and does notrequire a global monkey patch:
describe User do
it { should validate_presence_of :email }
end
expect
or should
or bothBy default, both expect
and should
syntaxes are available. In the future,the default may be changed to only enable theexpect
syntax.
If you want your project to only use any one of these syntaxes, you can configureit:
RSpec.configure do |config|
config.expect_with :rspec do |c|
c.syntax = :expect # disables `should`
# or
c.syntax = :should # disables `expect`
# or
c.syntax = [:should, :expect] # default, enables both `should` and `expect`
end
end
SeeRSpec::Expectations::Syntax#expectfor more information.
The should
and should_not
methods can be used to define expectations on anyobject.
actual.should eq expected
actual.should be > 3
[1, 2, 3].should_not include 4
actual.should eq(expected) # passes if actual == expected
actual.should == expected # passes if actual == expected
actual.should_not eql(expected) # passes if actual.eql?(expected)
Note: we recommend the eq
matcher over==
to avoid Ruby's "== in auseless context" warning when the ==
matcher is used anywhere but thelast statement of an example.
actual.should be(expected) # passes if actual.equal?(expected)
actual.should_not equal(expected) # passes if actual.equal?(expected)
actual.should be > expected
actual.should be >= expected
actual.should be <= expected
actual.should be < expected
actual.should be_within(delta).of(expected)
actual.should match(/expression/)
actual.should =~ /expression/
actual.should be_an_instance_of(expected)
actual.should_not be_a_kind_of(expected)
actual.should be_true # passes if actual is truthy (not nil or false)
actual.should be_false # passes if actual is falsy (nil or false)
actual.should be_nil # passes if actual is nil
actual.should be_xxx # passes if actual.xxx?
actual.should_not have_xxx(:arg) # passes if actual.has_xxx?(:arg)
(1..10).should cover(3)
actual.should include(expected)
actual.should start_with(expected)
actual.should end_with(expected)
[1,2,3].should include(1)
[1,2,3].should include(1, 2)
[1,2,3].should start_with(1)
[1,2,3].should start_with(1,2)
[1,2,3].should end_with(3)
[1,2,3].should end_with(2,3)
{:a => 'b'}.should include(:a => 'b')
"this string".should include("is str")
"this string".should start_with("this")
"this string".should end_with("ring")
因为expect
的实现侵入性比较低,expect
方法会返回一个代理对象,上面绑定了一些可以用来进行断言的方法。
而should
的实现是将should
一类的方法直接注入到所有的对象里,破坏性比较高。如果你自己定义了一些和
should
一类同名的方法就悲剧了。
require ‘spec_helper’:目的是加载’spec/spec_helper.rb’文件中的RSpec配置,运行时需要。
describe()方法:
我们用describe()方法定义一个测试用例组,describe()方法的参数可以是我们要测试的对象(如例中的People),
可以是一个字符串,用来描述该组,describe方法后的字符串所描述的内容可以对应一个用户故事。
注意describe()方法是可以嵌套的,两个describe()方法衔接起来可以更细化一个用户故事,如上边的里面的
describe()方法内就表示:”People have enough money pay for house”。
it()方法:
我们用it()方法定义一个具体的测试用例(在RSpec中,称一个测试用例为一个example)。其后的字符串为该方
法的参数,用来描述一个具体的场景,it方法体就是我们对系统在该场景下的行为的定义。
It()方法和describe()方法衔接起来,可以完整的描述一个系统行为,以上边的最后的一个测试用例为:”People
have enough money pay for house should travel ”。
context()方法:
Context()方法和describe()方法的作用一样,不同点在于describe倾向于描述我们的测试对象,而context()方法
倾向于用字符串描述用户故事。
before()和after():
这两个方法很多测试框架都支持,需要说明的是这两个方法的参数,例子中为符号’:each’,表明对于每一个测
试用例,先执行 before()方法中的代码,用例完成后执行after()方法中的代码,若参数为’:all’,则表示在所有的测
试用例执行之前,先执行 before()方法中的代码,所有的用例完成后执行after()方法中的代码。
RSpec还提供around()方法,暂不懂其用法,之后的报告中补上。
帮助方法:
所谓的帮助方法就是把多个测试用例中重复的操作抽出作为一个公用的方法,提高代码重用度。如例子中的
work_hard()方法。
共享行为:
系统的某一个行为是很多场景下都具有的,那么我们可以把它定义为一个共享行为,我们通过
share_examples_for()方法 定义共享行为,使用it_behaves_like()方法共享定义过的系统行为,如例子中的
share_examples_for “any people”, it_behaves_like “any people”。
pending()方法:
我们确定系统拥有一个行为,但是还没有具体定义,这时我们可以将该行为使用pending()方法来设置该行为为待
定义,其后的字符串参数将在生成的报告中显示。
pending()方法的另外一种用法就是,当一个测试用例失败时,我们可以利用pending方法设置其为一个待修复的
bug,方法体内包含使用例失败的代码。例如最后一个测试用例的内容
The rspec-core gem installs an rspec
executable. Run the rspec
command with
the --help
flag to see the available options:
rspec --help
Begin with a very simple example that expresses some basic desired behaviour.
# game_spec.rb
RSpec.describe Game do
describe "#score" do
it "returns 0 for an all gutter game" do
game = Game.new
20.times { game.roll(0) }
expect(game.score).to eq(0)
end
end
end
Run the example and watch it fail.
$ rspec game_spec.rb
uninitialized constant Object::Game (NameError)
Now write just enough code to make it pass.
# game_spec.rb
require './game'
...
# game.rb
class Game
def roll(pins)
end
def score
0
end
end
Run the example and bask in the joy that is green.
$ rspec game_spec.rb --color --format doc
Game
#score
returns 0 for all gutter game
Finished in 0.00057 seconds
1 example, 0 failures