Rspec测试代码重构

本文介绍如何通过自定义Rspec宏减少冗余代码,并提供了一个示例项目SkinnySpec,用于简化Rails应用中的Controller及ActiveRecord模型测试。
来自:[url]http://redworld.blog.ubuntu.org.cn/2008/06/23/refactoring_rspec_code/[/url]

消除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的测试宏,页面的测试宏,还有一个生成器:
[url]http://github.com/rsl/skinny_spec/tree[/url]
使用很简单,只要用script/plugin安装就好了。不过这个还有些不足,比如还没有shoulda中那个should_be_restful 这个最魔幻的方法,在控制器中做出请求动作的那个方法的定义是实现一个名为shared_request方法,在其中加入请求的方法和参数。

其实我对Rspec还有很多想法,比如更加DSL化的测试,对Mock测试数据时更加强大的控制,测试中描述信息的国际化,动态生成测试方法等等。

清爽的Rspec代码,相信每个人都想把它放进自己的项目中。。。
代码下载地址: https://pan.quark.cn/s/bc087ffa872a "测控电路课后习题详解"文件.pdf是一份极具价值的学术资料,其中系统地阐述了测控电路的基础理论、系统构造、核心特性及其实际应用领域。 以下是对该文献的深入解读和系统梳理:1.1测控电路在测控系统中的核心功能测控电路在测控系统的整体架构中扮演着不可或缺的角色。 它承担着对传感器输出信号进行放大、滤除杂音、提取有效信息等关键任务,并且依据测量与控制的需求,执行必要的计算、处理与变换操作,最终输出能够驱动执行机构运作的指令信号。 测控电路作为测控系统中最具可塑性的部分,具备易于放大信号、转换模式、传输数据以及适应多样化应用场景的优势。 1.2决定测控电路精确度的关键要素影响测控电路精确度的核心要素包括:(1)噪声与干扰的存在;(2)失调现象与漂移效应,尤其是温度引起的漂移;(3)线性表现与保真度水平;(4)输入输出阻抗的特性影响。 在这些要素中,噪声干扰与失调漂移(含温度效应)是最为关键的因素,需要给予高度关注。 1.3测控电路的适应性表现测控电路在测控系统中展现出高度的适应性,具体表现在:* 具备选择特定信号、灵活实施各类转换以及进行信号处理与运算的能力* 实现模数转换与数模转换功能* 在直流与交流、电压与电流信号之间进行灵活转换* 在幅值、相位、频率与脉宽信号等不同参数间进行转换* 实现量程调整功能* 对信号实施多样化的处理与运算,如计算平均值、差值、峰值、绝对值,进行求导数、积分运算等,以及实现非线性环节的线性化处理、逻辑判断等操作1.4测量电路输入信号类型对电路结构设计的影响测量电路的输入信号类型对其电路结构设计产生显著影响。 依据传感器的类型差异,输入信号的形态也呈现多样性。 主要可分为...
(IHAOAVOABPvsAOBPvsAVOABPvsPSOBP)非洲秃鹫融合天鹰优化BP天鹰优化BP非州秃鹫BP粒子群(Matlab代码实现)内容概要:本文档主要围绕多种智能优化算法在不同工程领域的应用展开,重点介绍了非洲秃鹫优化算法(AVOA)、天鹰优化算法(AO)与BP神经网络的融合改进,并与其他经典算法如粒子群优化(PSO)进行对比分析。所有案例均提供Matlab代码实现,涵盖电力系统优化、路径规划、微电网调度、无人机控制、信号处理等多个方向,强调算法的科研复现能力与实际仿真价值。文档还展示了丰富的技术服务体系,涉及机器学习、深度学习、路径规划、通信与电力系统等多个前沿领域。; 适合人群:具备一定Matlab编程基础,从事科研工作或工程仿真的研究生、高校教师及企业研发人员,尤其适用于从事智能优化算法研究与应用的相关技术人员。; 使用场景及目标:①用于学术论文复现,特别是SCI/EI期刊中关于优化算法的实验部分;②为电力系统、无人机、储能调度等领域提供算法仿真技术支持;③帮助研究人员快速掌握多种智能算法的实现方法并进行性能对比。; 阅读建议:建议结合文档提供的网盘资源下载完整代码,按照目录顺序逐步学习,重点关注算法原理与Matlab实现的结合方式,同时可将文中案例作为自身课题的参考模板进行二次开发与创新。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值