来自:
http://redworld.blog.ubuntu.org.cn/2008/06/23/refactoring_rspec_code/
消除Spec中的冗余,减少浪费。
看到ben的Blog写了一篇关于Rspec的测试宏的文章:
http://www.benmabey.com/2008/06/08/writing-macros-in-rspec/
其实很多人都是看到Tammer Saleh在MountainWest_Ruby_Conference2008上的Shoulda演示后,和我有一样的感想,就是如果如此DSL化,如此DRY的测试宏能用在Rspec上那就好了。那时我还把Shoulda的官方文档翻译了一下=_=,还有人和我讨论为什么要用Shoulda,还说他就是喜欢Rspec,其实我一次也没有用过Shoulda,但就是觉得这个DSL写的很好。不过Shoulda的缺点也很明显,AR的测试宏依赖于 fixture,在业界不提倡使用fixture的情况下还有对测试数据的控制粒度的角度来说,这个做法是不受欢迎的。Rspec中提倡的是全部数据都是在测试时手动Mock出来,所以在1.1.4中才有了stub_mock!这个方法来减轻Mock对象的负担。
Rspec测试的粒度是比较细的,测试的覆盖面在Stroy框架出来之后也能和之前Rails提供的Unittest一样。但是Rspec的测试代码,看上去很冗余,一开始就有这种感觉,一直到看到了Shoulda才发现原来它冗余的地方是什么,这存在于很多地方,比如Controller中每次都是mock一个请求方法,然后就对各种会触发的行为和保存的状态进行断言。这些每次千篇一律的东西,我在想如果在每个context下定义一个请求方法(do_xxx之类的),然后在测试时会在测试方法内部的断言前或断言后自动调用它,这样就能减少很多冗余了。当然我在看完了Shoulda的文档后也尝试写出Rspec的测试宏,不过由于不知道怎么连接到Rspec中就放弃了,话说回来,Rspec中每个测试中上下文关系是很复杂的。
不说废话了,看看鬼佬们怎么减少Rspec中的冗余吧。
下面是一个常见的以Product为领域模型的Controller的测试,用Rspec-Rails插件生成的Scaffold的测试就类似下面这样:
describe ProductsController do
describe "handling GET /products" do
before(:each) do
@product = mock_model(Products)
Product.stub!(:find).and_return([@product])
end
def do_get
get :index
end
it "should be successful" do
do_get
response.should be_success
end
it "should render index template" do
do_get
response.should render_template('index')
end
it "should find all products" do
Product.should_receive(:find).with(:all).and_return([@product])
do_get
end
it "should assign the found products for the view" do
do_get
assigns[:products].should == [@product]
end
end
end
上面的代码一看过去就觉得很多重复吧。如果我想让它变成下面这些DSL化的测试代码要怎么办呢?
describe ProductsController do
describe "处理Get /products" do
before(:each) do
@products = mock_model(Product)
Products.stub!(:find).and_return([@product])
end
def do_get
get :index
end
it_should_response_success
it_should_render :template, "index"
it_should_receive Product, :find, :all, [@product]
it_should_assign :products, [@product]
end
end
那么首先为这些宏定义一个Module吧:
module ControllersMacro
module ExampleMethods
def do_action
verb = [:get, :post, :put, :delete].find{|verb| respond_to? :"do_#{verb}"}
raise "No do_get, do_post_ do_put, or do_delete has been defined!" unless verb
send("do_#{verb}")
end
end
module ExampleGroupMethods
def it_should_assign(variable_name, value=nil)
it "should assign #{variable_name} to the view" do
value ||= instance_variable_get("@#{variable_name}")
if value.kind_of?(String) && value.starts_with?("@")
value = instance_variable_get(value)
end
do_action
assigns[variable_name].should == value
end
end
def it_should_response_success
it "should be successful" do
do_action
response.should be_success
end
end
def it_should_receive model, action, with_value = anything, return_value = anything
it "should make #{model.to_s} #{action.to_s}" do
model.should_receive(action).with(with_value).and_return(return_value)
do_action
end
end
end
def self.included(receiver)
receiver.extend ExampleGroupMethods
receiver.send :include, ExampleMethods
end
end
这些就是Spec中的DSL,一般称为Rspec Macros,Rspec宏。那么那么把这些宏与Controller们挂钩呢?每次测试挂一次?当然不是,在ben那篇Blog的留言里,Rspec的核心开发成员David Chelimsky给出了答案,原来Rspec中本来就有接口开放了:
Spec::Runner.configure do |config|
#...
config.include(ControllerMacros, :type => :controllers)
end
这样就把测试宏挂到了Controller的Spec那里,那还等什么就直接在Spec用这些DSL来写出清爽的Spec吧,享受BDD。在这之后当然,我们不会止步于只在Controller中消除冗余,我立马就想到了Shoulda中几个Api,像下面那样,马上就能想到一些 ActiveRecord的测试宏。
module ModelsMacro
module ExampleMethods
#......
end
module ExampleGroupMethods
def it_should_require_attributes(variable_name)
it "should require #{variable_name}" do
#TODO
end
end
def it_should_require_unique_attributes variable_name
it "should require unique attributes #{variable_name}" do
#TODO
end
end
def it_should_not_allow_values_for variable_name, not_allow = []
it "should require #{variable_name}" do
#TODO
end
end
def it_should_allow_values_for variable_name, allow_values = []
it "should allow values for #{variable_name}" do
#TODO
end
end
def it_should_protect_attributes variable_name
it "should protect attributes #{variable_name}" do
#TODO
end
end
def it_should_have_one variable_name
it "should have one #{variable_name}" do
#TODO
end
end
def it_should_have_many variable_name, option => {}
it "should have many #{variable_name}" do
#TODO
end
end
def it_should_belong_to variable_name
it "should belong to #{variable_name}" do
#TODO
end
end
end
def self.included(receiver)
receiver.extend ExampleGroupMethods
receiver.send :include, ExampleMethods
end
end
如果觉得自己写很麻烦,那就用别人做好的现成的东西吧:
一个现成的Rspec宏项目,Skinny Spec。它已经完成了Controller和AR的测试宏,页面的测试宏,还有一个生成器:
http://github.com/rsl/skinny_spec/tree
使用很简单,只要用script/plugin安装就好了。不过这个还有些不足,比如还没有shoulda中那个should_be_restful 这个最魔幻的方法,在控制器中做出请求动作的那个方法的定义是实现一个名为shared_request方法,在其中加入请求的方法和参数。
其实我对Rspec还有很多想法,比如更加DSL化的测试,对Mock测试数据时更加强大的控制,测试中描述信息的国际化,动态生成测试方法等等。
清爽的Rspec代码,相信每个人都想把它放进自己的项目中。。。
分享到:
- 2008-06-23 14:19
- 浏览 1518
- 评论(1)
- 论坛回复 / 浏览 (1 / 3387)
- 查看更多
相关推荐
从快速到慢速的代码中重构方式很容易。 如果您不性能测试,可能会发现“部分很有帮助。 内容 安装 将此行添加到您的应用程序的Gemfile中: gem 'rspec-benchmark' 然后执行: $ bundle 或自己安装为: $ gem ...
将旧代码重构为高质量代码,同时添加新功能。 动机 为了测试获取代码库,阅读,解释和改进的能力。 使用TDD,OOP和一致的代码样式来确保高质量的代码和实现。 设计方法 建造状态 特拉维斯CI: 代码风格 Rubocop: ...
JumpstartLabs联系人管理器TDD驱动... 您将使用的工具包括: 使用RSpec进行测试以推动开发使用Haml和Sass创建视图模板使用辅助函数和局部函数构建可重用的视图代码重构管理身份验证和授权服务器和客户端验证部署和监控
一旦你进入守卫,运行 rspec 将从第一个测试开始,并按照你的方式进行每个练习。 如果您更愿意专注于特定部分,例如重构练习,请不要使用守卫。 相反,在您的命令行中键入: rspec/spec/string 这将使您开始了解 ...
使用功能测试来塑造您的代码库 定期重构为的(有点极端) 发布 每个功能都链接到添加该功能的拉取请求。 每次提交都以(某种程度的)细节解释了我的推理。 我建议阅读从最旧到最新的拉取请求,按提交提交,包括消息...
使用助手实验室重构视图 目标 查看创建关联对象 写一个助手有条件地显示不同的链接 ... 您可以使用rspec命令运行测试。 编写#artist_name和#artist_name=的代码,以便可以从Song实例中检索Artist并
W2D3 类继承异常、错误处理分解为对象继承、多态和 DRY 信息隐藏/封装 W2D4 国际象棋独奏项目及调试 W2D5 RSpec 简介RSpec 语法TDD 测试双打主题并让守卫-rspec 实践评估 第 3 周 W3D1 SQL 基础知识格式化 SQL 代码 ...
内容管理模块完全使用“红绿重构”方法开发的,可交付成果中要求的该方法应用的所有痕迹都可以在项目log/rspec.out的log/rspec.out文件中找到。 为了在终端中获得结果,运行了代码bundle exec rspec --format ...
通过我的代码重构以及与教学人员的一对一交流,我了解到应该如何构建、测试和展示优秀的面向对象编程。 使用的技术 Ruby 规格 完成的任务 系统内将有许多列车。 火车将在车站之间行驶。 在车站内,当火车停下来时...
still_life是测试单元,小型测试,RSpec和Capybara的测试框架增强功能,记录了在端到端或单元测试执行期间呈现的所有HTML响应正文文本。 所以呢? 您可以在任何应用更新之前和之后比较实际呈现HTML结果。 为了什么...
这是重构的kata,因此您将从遗留的代码库开始。 要使用Kata,请克隆此git存储库并签出标签“ start-here”。 请阅读以下说明,以了解涉及此kata的“规则”。与Jim版本相比的变化显然,此版本是用Java完成的。 我...
这是一个重构kata,因此您将从一个旧代码库开始。 要使用Kata,请克隆此git存储库并签出标签“ start-here”。 请阅读以下说明,以了解涉及此kata的“规则”。 与Jim版本相比的变化 显然,此版本是用Java完成的。 ...
这是一个重构kata,因此您将从一个旧代码库开始。 要使用Kata,请克隆此git存储库并签出标签“ start-here”。 请阅读以下说明,以了解涉及此kata的“规则”。与Jim版本相比的变化显然,此版本是用Java完成的。 我...
更新(11.12.14) :又一轮深度代码和测试重构,这次用Game和Puzzle类和两个模块替换原来的Grid类: PuzzleGenerator和PuzzleSolver 。 此外,更改了拼图解决方案功能,以便在拼图创建时存储解决方案,从而加快运行...
带有 RSpec 的 TDD / BDD 集成测试 内部回报率 HAML / Slim、SCSS、CoffeeScript 后台任务 按计划运行任务 缓存和优化 社交媒体认证 复杂的形状 架构设计 阿贾克斯和彗星 从头开始设置服务器 通过 capistrano 部署...
重构现有代码,以使其可以与anxn board一起使用 当前的输出是“ draw”,没有赢家,而且游戏还没有结束。 如果游戏尚未完成,请将其更改为“未完成”。 运行测试 ./bin/rspec 笔记 在5x5板上运行的测试已在测试文件...
外部接口应该相当稳定,每个 0.ab 版本都具有对 b 的任何更改(即仅重构和新功能)的向后兼容性,以及对 a 的更改可能的接口更改(尽管可能很小)。 目前不鼓励根据代码的内部结构(自述文件中未显示的任何内容)。...