来自:
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
- 浏览 1550
- 评论(1)
- 论坛回复 / 浏览 (1 / 3430)
- 查看更多
相关推荐
- **由红变绿**:采用“红-绿-重构”循环进行开发,即首先编写失败的测试,然后编写代码使其通过测试,最后进行重构。 - **清理**:在测试通过后,对测试代码进行清理,去除不必要的部分,使其更加简洁高效。 #### ...
Ruby中的测试框架,例如RSpec或Test::Unit,是重构过程中常用的工具。 6. **Ruby特定的重构工具**:可能会介绍一些Ruby社区中流行使用的重构工具或插件,如RubyMine的重构支持、reek和Flog这样的代码质量检查工具等...
- **清晰的测试结构**:RSpec支持使用`describe`和`context`块来组织测试逻辑,使测试代码结构更加清晰。 - **使用模拟对象**:RSpec提供了强大的模拟对象功能,可以在测试中模拟外部系统或复杂依赖,以便更专注于...
从快速到慢速的代码中重构方式很容易。 如果您不性能测试,可能会发现“部分很有帮助。 内容 安装 将此行添加到您的应用程序的Gemfile中: gem 'rspec-benchmark' 然后执行: $ bundle 或自己安装为: $ gem ...
- **工具**:Ruby社区提供了一些有用的工具和库,如`rspec`用于编写测试用例,`rubocop`用于代码规范检查等。 - **资源**:除了《重构(Ruby版)》这本书外,还可以参考在线文档、博客文章以及社区论坛中的讨论。 #...
Ruby-Mutation是一个强大的突变测试工具,专门为Ruby编程语言设计。突变测试是一种软件测试方法,通过修改(或“突变”)代码来...通过定期进行突变测试,开发者可以更自信地进行代码重构和优化,降低软件的维护成本。
首先,编写一个失败的测试(红色),然后编写最简化的代码使测试通过(绿色),最后重构代码以提高设计质量。 2. **最小化实现**:在编写使测试通过的代码时,只写刚好足够的代码,避免过度设计。 3. **保持测试...
10. **持续集成与自动化测试**:理解如何将RSpec测试集成到持续集成(CI)工具中,如Jenkins或Travis CI,实现自动化的测试流程。 通过这门课程,学员不仅能学习到如何使用RSpec进行测试,还能培养起遵循TDD原则开发...
Mutation Testing(突变测试)是一种强化测试的方法,它通过引入小的、故意的代码修改(即“突变”)来检查现有测试能否检测到这些改变。如果测试通过了突变后的代码,那么这表明测试对这个特定部分的代码覆盖不足。...
《Refactoring Ruby》这本书是由Jay Fields、Shane Harvie 和 Martin Fowler 合著的,主要聚焦于如何在 Ruby 语言环境下进行有效的代码重构。该书是 Addison-Wesley Professional Ruby Series 系列中的一本,旨在为...
在本项目“W6D5-专案:CSS花絮,RSpec作业,TDD专案”中,我们涉及了三个核心主题:CSS样式设计、RSpec测试框架以及Test-Driven Development(TDD)方法。让我们逐一深入探讨这些知识点。 首先,CSS(Cascading ...
4. **代码重构**:包括代码的提取、重命名、简化、去除重复代码等,可能涉及到`refactoring_miner`这样的工具。 5. **版本控制**:熟悉Git的工作流,如何克隆、拉取、提交、推送代码等。 6. **Ruby测试框架**:如...
3. **重构**:在测试通过后,对代码进行重构,以改善其结构和可读性,同时确保所有测试仍然保持绿色,即所有功能仍然正常运行。 "超越红、绿、重构"可能意味着在这个项目中,开发者不仅关注基本的TDD循环,还可能...
在 TDD 中,你首先编写失败的测试,然后编写最小的代码使测试通过,接着重构代码以保持代码质量。这个过程反复进行,直到满足所有需求。 8. **实践与案例研究** 为了加深理解,你可以从创建一个简单的 Ruby 应用...
将旧代码重构为高质量代码,同时添加新功能。 动机 为了测试获取代码库,阅读,解释和改进的能力。 使用TDD,OOP和一致的代码样式来确保高质量的代码和实现。 设计方法 建造状态 特拉维斯CI: 代码风格 Rubocop: ...
3. **重构阶段**:一旦测试通过,开发者可以对代码进行重构,以提高可读性、可维护性和性能,同时确保所有测试仍然通过。 在Ruby中,RSpec是一种流行的BDD(行为驱动开发)框架,它提供了一种声明式的语法,使测试...
- **代码重构**:定期重构测试代码,提高代码质量和可维护性。 综上所述,前端自动化测试是一种有效的提高软件开发质量和效率的方法,而Watir作为一种强大且易于使用的自动化测试工具,在实践中发挥了重要作用。...
通常在Ruby项目中,我们可能会看到如Gemfile(记录项目依赖)、Rakefile(用于自动化任务)、README.md(项目说明)、spec目录(包含rspec测试文件)、lib目录(存放项目的核心代码)等文件。这些文件将构成一个完整...