这是David Chelimsky写的一篇RSpec简明指南,原文在这里。
简介
要了解RSpec,我们首先需要了解什么是行为驱动开发(Behaviour Driven Development,简称BDD),BDD是一种融合了可接受性测试驱动计划(Acceptance Test Driven Planning),域驱动设计(Domain Driven Design)以及测试驱动开发(Test Driven Development,简称TDD)的敏捷开发模型。RSpec为BDD开发提供TDD支持。
你可以简单的将RSpec看作一个传统的单元测试框架,但我们更愿意将它看成是一种领域特定语言(Domain Specific Language,以下简称DSL),它的主要作用就是描述我们对系统执行某个样例(example)的期望行为(behavior)。
这篇指南遵从TDD思想,但是我们将使用行为(behavior)和样例(example)来代替测试例(test case)和测试方法(test method),想知道我们为什么采用这样的术语,请参看Dan North, Dave Astels, 以及 Brian Marick 的相关文章。
安装
目前RSpec的最新版本是1.0.5,需要Ruby184以上版本,可以通过下面这条命令安装:
# gem install rspec
准备工作
整篇指南都围绕一个例子展开,因此在开始前,你最好先为这个例子建个目录:
$ mkdir rspec_tutorial
$ cd rspec_tutorial
开始
我们首先要了解的是RSpec DSL的”describe”与”it”方法,这两个方法有很多其它的名字(但是我们不推荐使用它们),我们之所以使用这样的命名,只是想让你站在行为(behavior)而不是结构(structure)的角度进行思考。
创建名为user_spec.rb的文件:
describe User do
end
describe方法创建一个Behavior实例,所以你可以将”describe User”理解为”描述用户的行为(describe the behaviour of the User class)”,或许这个方法叫做“
describe_the_behaviour_of”会更合适些,但这实在太冗长了,所以我们决定只选取第一个单词describe来作为这个方法的名字。
现在你可以在shell中试试这条命令:
$ spec user_spec.rb
spec命令有很多选项,但大部分超出了本指南的范围,如果你感兴趣,可以只输入spec而不带任何参数来查看帮助信息。
让我们接着回到上面那条命令,它应该会产生下面的输出:
./user_spec.rb:1: uninitialized constant User (NameError)
这是因为我们还没有创建User类,也就是说我们要描述的东西不存在,因此我们需要再创建一个user.rb来定义我们所要描述的对象:
class User
end
并在user_spec.rb中包含它:
require 'user'
describe User do
end
现在再次运行spec命令:
$ spec user_spec.rb
Finished in 6.0e-06 seconds
0 examples, 0 failures
这个输出是说我们还没有定义样例,所以现在我们就来定义一个:
describe User do
it "should be in any roles assigned to it" do
end
end
it方法返回一个Example实例,因此我们可以将it方法理解成“用户行为的一个样例”。
再次运行spec:
$ spec user_spec.rb --format specdoc
User
- should be in any roles assigned to it
Finished in 0.022865 seconds
1 example, 0 failures
specdoc参数格式化行为(describe方法创建的对象)以及样例(it方法创建的对象)的名字然后输出,这种格式来自于TestDox,一个为JUnit测试例及方法提供相似报告的工具。
现在我们开始增加Ruby代码:
describe User do
it "should be in any roles assigned to it" do
user.should be_in_role("assigned role")
end
end
这句话的意思是User应该能够胜任所有分配给他的角色,那么事实是这样么?让我们运行spec试试看:
$ spec user_spec.rb --format specdoc
User
- should be in any roles assigned to it (ERROR - 1)
1)
NameError in ‘User should be in any roles assigned to it’
undefined local variable or method `user’ for #<#:0×14ecdd8>
./user_spec.rb:6:
Finished in 0.017956 seconds
1 example, 1 failure0x14ed15c>
又出错了,是的,但在继续之前,让我们先仔细看看这段出错信息:
- “ERROR -1)”告诉我们”should be in any roles assigned to it”这个样例出错了
- “1)”则为我们详细描述了这个错误,当样例很多时,你就会发现这个编号非常有用
还有一点需要注意:这段信息没有给出RSpec代码的backtrace,如果你需要它,可以通过–backtrace选项来获取。
下面,我们继续我们的例子,上面的错误是因为我们没有创建User对象,那我们就创建一个:
describe User do
it "should be in any roles assigned to it" do
user = User.new
user.should be_in_role("assigned role")
end
end
$ spec user_spec.rb --format specdoc
User
- should be in any roles assigned to it (ERROR - 1)
1)
NoMethodError in ‘User should be in any roles assigned to it’
undefined method `in_role?’ for #
./user_spec.rb:7:
Finished in 0.020779 seconds
1 example, 1 failure0x14ec8ec>
还是失败,不过这次是因为User对象缺少role_in?方法,修改user.rb:
class User
def in_role?(role)
end
$ spec user_spec.rb --format specdoc
User
- should be in any roles assigned to it (FAILED - 1)
1)
‘User should be in any roles assigned to it’ FAILED
expected in_role?(”assigned role”) to return true, got nil
./user_spec.rb:7:
Finished in 0.0172110000000001 seconds
1 example, 1 failure
虽然又失败了,但我们的第一个目标其实已经达到了,我们得到了一段更有意义的错误描述”User should be in any roles assigned to it”。
让这段代码避免失败很简单:
class User
def in_role?(role)
true
end
$ spec user_spec.rb --format specdoc
User
- should be in any roles assigned to it
Finished in 0.018173 seconds
1 example, 0 failures
现在终于通过了,但是让我们再来看看这段代码:
describe User do
it "should be in any roles assigned to it" do
user = User.new
user.should be_in_role("assigned role")
end
end
我们可以将这个样例理解成“用户应该接受所有分配给他的角色”,但问题是我们还没有分给他角色呢?
describe User do
it "should be in any roles assigned to it" do
user = User.new
user.assign_role("assigned role")
user.should be_in_role("assigned role")
end
end
这段代码又会引发一个错误,因为User并没有assign_role这个方法:
class User
def in_role?(role)
true
end
def assign_role(role)
end
end
$ spec user_spec.rb --format specdoc
User
- should be in any roles assigned to it
Finished in 0.018998 seconds
1 example, 0 failures
样例再次通过,但是我们的任务还没结束,只要你再回头看看我们目前的代码,就会发现这个User的行为与我们的目标还有距离。
现在,我们只是解决了“用户必须接受所有分配给他的角色”,但是还有一个问题就是”用户不应该接受没有分配给他的角色“。所以我们需要为用户行为再增加一个样例:
describe User do
it "should be in any roles assigned to it" do
user = User.new
user.assign_role("assigned role")
user.should be_in_role("assigned role")
end
it “should NOT be in any roles not assigned to it” do
user = User.new
user.should_not be_in_role(”unassigned role”)
end
end
$ spec user_spec.rb --format specdoc
User
- should be in any roles assigned to it
- should NOT be in any roles not assigned to it (FAILED - 1)
1)
‘User should NOT be in any roles not assigned to it’ FAILED
expected in_role?(”unassigned role”) to return false, got true
./user_spec.rb:12:
Finished in 0.019014 seconds
2 examples, 1 failure
失败了,用户接受了没有分给他的角色,这需要我们对User的实现做些改动:
class User
def in_role?(role)
role == "assigned role"
end
def assign_role(role)
end
end
现在,一切都搞定了,但是我们的代码与样例有些重复(它们都使用了”assigned role”),因此,有必要对User类进行重构:
class User
def in_role?(role)
role == @role
end
def assign_role(role)
@role = role
end
end
随后,让我们再来测试一下:
$ spec user_spec.rb --format specdoc
User
- should be in any roles assigned to it
- should NOT be in any roles not assigned to it
Finished in 0.018199 seconds
2 examples, 0 failures
事情就这么结束了么?你可能还有些疑惑,因为我们甚至可以将一个数字分配给用户,但这与”用户应该接受任何分配给他的角色”是吻合的,所以,这时候 我们应该征求下我们的客户的意见,“每个用户在同一时间只能担当一个角色吗?”,如果客户的回答是Yes,那么很幸运,我们不需要对我们的代码进行改动, 而只需对样例的描述进行一些修改,但如果客户的回答是No,那我们恐怕还得再做些工作。
相关推荐
原版The RSpec Book并附有源码
rspec出入门者学习理解,介绍的非常的详细、清楚; 欢迎下载学习~
rspec 下载请注意:英文文档
rspec_api_documentation, 从RSpec自动生成API文档 RSpec Doc为你的Rails API生成漂亮的。查看一个示例文件。更改请查看维基以了解最新的更改。安装将rspec_api_documentation添加到你的文件gem 'rspec_a
RSpec样式指南榜样很重要。 -军官Alex J. Murphy / RoboCop 小费您可以在找到本指南的精美版本,并对其导航进行了改进。 该RSpec样式指南概述了推荐给现实世界程序员的最佳实践,以编写其他现实世界程序员可以维护的...
jruby-1.5.5+OperaWatir+RSpec
使用RSpec 测试Rails 程序.pdf
rspec-api-blueprint-formatter, 从RSpec测试自动生成API文档 ! RSpec APIBlueprint格式化程序从RSpec测试自动生成API文档 !像这样it 'retrievs the patients medications' do retrieve_medications
使用RSpec测试Rails程序。本书基于Rails 4.1,使用完整应用 来演示TDD的流程。
rspec-collection_matchers, 集合基数匹配器,从rspec期望中提取 RSpec::CollectionMatchers RSpec::CollectionMatchers 让你在一个例子中表达一个对象集合的预期结果。expect(account.shopping_cart).to have_
RSpec.Essentials 英文
ruby(rails)单元测试相关的gem,rspec、cucumeber。BDD
rspec 是目前bdd測試的使用工具,很適合描述測試的內容
rspec_junit_formatter, RSpec结果格式化为你的CI可以读取的JUnit RSpec JUnit格式化程序 RSpec 2 & 3结果, Jenkins可以读取。 可能还有其他的CI服务。灵感来自于的工作,在的RSpec格式化程序在对 Reporter的失望...
RSpec和Minitest匹配器来预防N 1查询问题
Api-rspec_api_documentation.zip,从rspecrspec api doc generator自动生成api文档,一个api可以被认为是多个软件设备之间通信的指导手册。例如,api可用于web应用程序之间的数据库通信。通过提取实现并将数据放弃到...
rspec_demo Rspec演示
db-query-matchers, 用于数据库查询的RSpec匹配器 db-query-matchers 用于数据库查询的RSpec匹配器。安装在你的应用程序中添加这一行,最好在你的test 组中:gem 'db-query-matchers'然后执行:bundle
在他们下载的chm始终打不开或有问题。 ... 使用Cucumber+Rspec玩转BDD(1)——用户注册 使用Cucumber+Rspec玩转BDD(2)——邮件激活 ...使用Cucumber+Rspec玩转BDD(3)——用户...使用Cucumber+Rspec玩转BDD(7)——测试重构