`
chenk85
  • 浏览: 43840 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
最近访客 更多访客>>
社区版块
存档分类
最新评论

Rspec测试代码重构

    博客分类:
  • Ruby
阅读更多
来自: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代码,相信每个人都想把它放进自己的项目中。。。
分享到:
评论
1 楼 不是流氓 2009-03-03  
这样是很清爽
受益菲浅阿,比it "should***" lambda强很多。。。

相关推荐

    使用RSpec 测试Rails 程序.pdf

    - **由红变绿**:采用“红-绿-重构”循环进行开发,即首先编写失败的测试,然后编写代码使其通过测试,最后进行重构。 - **清理**:在测试通过后,对测试代码进行清理,去除不必要的部分,使其更加简洁高效。 #### ...

    重构--Ruby 完整扫描清晰版--中文

    Ruby中的测试框架,例如RSpec或Test::Unit,是重构过程中常用的工具。 6. **Ruby特定的重构工具**:可能会介绍一些Ruby社区中流行使用的重构工具或插件,如RubyMine的重构支持、reek和Flog这样的代码质量检查工具等...

    The Rspec Book -- BDD methodology

    - **清晰的测试结构**:RSpec支持使用`describe`和`context`块来组织测试逻辑,使测试代码结构更加清晰。 - **使用模拟对象**:RSpec提供了强大的模拟对象功能,可以在测试中模拟外部系统或复杂依赖,以便更专注于...

    rspec-benchmark:RSpec的性能测试匹配器

    从快速到慢速的代码中重构方式很容易。 如果您不性能测试,可能会发现“部分很有帮助。 内容 安装 将此行添加到您的应用程序的Gemfile中: gem 'rspec-benchmark' 然后执行: $ bundle 或自己安装为: $ gem ...

    ruby重构中文+英文

    - **工具**:Ruby社区提供了一些有用的工具和库,如`rspec`用于编写测试用例,`rubocop`用于代码规范检查等。 - **资源**:除了《重构(Ruby版)》这本书外,还可以参考在线文档、博客文章以及社区论坛中的讨论。 #...

    Ruby-MutationRuby的突变测试工具

    Ruby-Mutation是一个强大的突变测试工具,专门为Ruby编程语言设计。突变测试是一种软件测试方法,通过修改(或“突变”)代码来...通过定期进行突变测试,开发者可以更自信地进行代码重构和优化,降低软件的维护成本。

    测试驱动开发的艺术——全书源码

    首先,编写一个失败的测试(红色),然后编写最简化的代码使测试通过(绿色),最后重构代码以提高设计质量。 2. **最小化实现**:在编写使测试通过的代码时,只写刚好足够的代码,避免过度设计。 3. **保持测试...

    rspec-course:rscpec-课程-udemy

    10. **持续集成与自动化测试**:理解如何将RSpec测试集成到持续集成(CI)工具中,如Jenkins或Travis CI,实现自动化的测试流程。 通过这门课程,学员不仅能学习到如何使用RSpec进行测试,还能培养起遵循TDD原则开发...

    Ruby-Mutations用于编写安全可重用和可维护的代码Ruby和Rails应用程序

    Mutation Testing(突变测试)是一种强化测试的方法,它通过引入小的、故意的代码修改(即“突变”)来检查现有测试能否检测到这些改变。如果测试通过了突变后的代码,那么这表明测试对这个特定部分的代码覆盖不足。...

    Refactoring Ruby

    《Refactoring Ruby》这本书是由Jay Fields、Shane Harvie 和 Martin Fowler 合著的,主要聚焦于如何在 Ruby 语言环境下进行有效的代码重构。该书是 Addison-Wesley Professional Ruby Series 系列中的一本,旨在为...

    W6D5-专案:CSS花絮,RSpec作业,TDD专案

    在本项目“W6D5-专案:CSS花絮,RSpec作业,TDD专案”中,我们涉及了三个核心主题:CSS样式设计、RSpec测试框架以及Test-Driven Development(TDD)方法。让我们逐一深入探讨这些知识点。 首先,CSS(Cascading ...

    sixamo:用于Sixamo.rb的代码读取和重构

    4. **代码重构**:包括代码的提取、重命名、简化、去除重复代码等,可能涉及到`refactoring_miner`这样的工具。 5. **版本控制**:熟悉Git的工作流,如何克隆、拉取、提交、推送代码等。 6. **Ruby测试框架**:如...

    spec_sequence:与超越红、绿、重构相关的代码

    3. **重构**:在测试通过后,对代码进行重构,以改善其结构和可读性,同时确保所有测试仍然保持绿色,即所有功能仍然正常运行。 "超越红、绿、重构"可能意味着在这个项目中,开发者不仅关注基本的TDD循环,还可能...

    from-zero-to-hero-with-RSpec

    在 TDD 中,你首先编写失败的测试,然后编写最小的代码使测试通过,接着重构代码以保持代码质量。这个过程反复进行,直到满足所有需求。 8. **实践与案例研究** 为了加深理解,你可以从创建一个简单的 Ruby 应用...

    gilded_rose_kata:经典的技术测试kata

    将旧代码重构为高质量代码,同时添加新功能。 动机 为了测试获取代码库,阅读,解释和改进的能力。 使用TDD,OOP和一致的代码样式来确保高质量的代码和实现。 设计方法 建造状态 特拉维斯CI: 代码风格 Rubocop: ...

    Ruby-TDD实战TestDrivenDevelopmentinAction

    3. **重构阶段**:一旦测试通过,开发者可以对代码进行重构,以提高可读性、可维护性和性能,同时确保所有测试仍然通过。 在Ruby中,RSpec是一种流行的BDD(行为驱动开发)框架,它提供了一种声明式的语法,使测试...

    前端自动测试之Watir

    - **代码重构**:定期重构测试代码,提高代码质量和可维护性。 综上所述,前端自动化测试是一种有效的提高软件开发质量和效率的方法,而Watir作为一种强大且易于使用的自动化测试工具,在实践中发挥了重要作用。...

    bookhouse_tdd:使用rspec在Ruby上构建的商店项目

    通常在Ruby项目中,我们可能会看到如Gemfile(记录项目依赖)、Rakefile(用于自动化任务)、README.md(项目说明)、spec目录(包含rspec测试文件)、lib目录(存放项目的核心代码)等文件。这些文件将构成一个完整...

Global site tag (gtag.js) - Google Analytics