RSpec入门指南

如何来描述(describe)你的method

首先要清楚你要描述的是什么类型的方法。用 Ruby 文档的一个惯例举例,提到类方法时使用.(或者::),提到实例方法的时候用#来描述。

#BAD
describe ‘the authenticate method for user‘ do
describe ‘if the user is an admin‘ do
#GOOD
describe ‘.authenticate‘ 
dodescribe ‘#admin?‘ do

使用上下文环境contexts

contexts 非常强大,它能让测试更清晰,有条理。在漫长的开发过程中,让你的测试一直保持高度的可读性。

#BAD
it ‘has 200 status code if logged in‘ do
  response.should respond_with 200
end
it ‘has 401 status code if not logged in‘ do
  response.should respond_with 401
end
#GOOD
context ‘when logged in‘ do
  it { is_expected.to respond_with 200 }
end
context ‘when logged out‘ do
  it { is_expected.to respond_with 401 }
end

在描述一个context时用“when”或者"with"作为开头

保持简洁的description

一个测试的描述永远也不应该超过40个字符。如果超过了,那就应该用context来分割

#BAD
it ‘has 422 status code if an unexpected params will be added‘ do
#GOOD    
context ‘when not valid‘ do
  it { should respond_with 422 }
end

在这个例子中,我们用测试的期望 it { should respond_with 422 } 替换了描述中相关的 status code。 如果你用 rspec filename 执行这个测试,你依然能得到非常可读的测试报告。

#测试输出
when not valid
  it should respond with 422

唯一的测试条件

“唯一的测试条件”可以被宽泛的定义为“每一个测试都应该只有一个断言”。 这样做的能帮助你找到可能的问题,直接前往失败的测试,并且让你的代码具有可读。

在独立的单元测试中,你希望每一个具体的测试都只定义一个而且是只有一个行为。多重的测试期望条件在同一个具体测试中 说明你可能定义了多个行为。

不过,在像那些涉及了数据库,外部 Web Service,或者从一个系统到另一个系统的测试中,你可能会 消耗大量的资源来不断的重复一些准备工作,就因为要进行不同的单一条件测试。在这些非常消耗时间的测试中, 定义多个行为是可以接受的。

#GOOD (独立的)
it { should respond_with_content_type(:json) }
it { should assign_to(:resource) }
#GOOD(非独立的)
it ‘creates a resource‘ do
  response.should respond_with_content_type(:json)
  response.should assign_to(:resource)
end

测试所有可能性

测试是件好事,但如果你不进行边界测试,测试就不会真正的有效。有效和无效的边界情形都需要被测试,用下面的例子来做示范。

#destroy action
before_filter :find_owned_resources
before_filter :find_resource

def destroy
  render ‘show‘
  @consumption.destroy
end

对于这个例子来说,非常常见的错误就是只测试这个资源有没有被摧毁。但这个 action 至少还包括了两个边界情形:当这个资源 找不到的时候或者这个资源没有权限被摧毁。记得这个通用的原则:考虑所有可能的输入并对他们都进行测试。

#bad 
it ‘shows the resource‘
#good
describe ‘#destroy‘ do

  context ‘when resource is found‘ do
    it ‘responds with 200‘
    it ‘shows the resource‘
  end

  context ‘when resource is not found‘ do
    it ‘responds with 404‘
  end

  context ‘when resource is not owned‘ do
    it ‘responds with 404‘
  end
end

善用subject

如果你有好几个测试都是用了同一个 subject,使用subject{} 来避免重复。

#bad
it { assigns(‘message‘).should match /it was born in belville/ }
it { assigns(‘message‘).creator.should match /topolino/ }
#good
subject { assigns(‘message‘) }
it { should match /it was born in billville/ }

RSpec 也可以给 subject 命名。

#good
subject(:hero) { hero.first }
it "carries a sword" do
  hero.equipment.should include "sword"
end

使用let和let!

当你需要给一个变量赋值时,使用 let 而不是 before 来创建这个实例变量。let 采用了 lazy load 的机制,只有在第一次用到的时候才会加载,然后就被缓存,直到测试结束。 这里有一个讲的非常好非且有深度的描述什么 是 let  的帖子Stackoverflow Answer

#bad
describe ‘#type_id‘ do
  before { @resource = FactoryGirl.create :device }
  before { @type     = type.find @resource.type_id }

  it ‘sets the type_id field‘ do
    @resource.type_id.should equal(@type.id)
  end
end
#good
describe ‘#type_id‘ do
  let(:resource) { FactoryGirl.create :device }
  let(:type)     { type.find resource.type_id }

  it ‘sets the type_id field‘ do
    resource.type_id.should equal(type.id)
  end
end
#good
context ‘when updates a not existing property value‘ do
  let(:properties) { { id: settings.resource_id, value: ‘on‘} }

  def update
    resource.properties = properties
  end

  it ‘raises a not found error‘ do
    expect { update }.to raise_error Mongoid::Errors::DocumentNotFound
  end
end

如果你想让这个变量在定义的时候就被初始化,使用let!。这事一个在测试数据库查询或者scope语句中是非常有用的技巧

下面是let的实例

#good
#这一段
let(:foo){Foo.new}

#基本上完全等同这一段
def foo
    @foo ||= Foo.new
end

不要用mock

只创建你需要的数据

如果你曾经在一个中型(小型也是)的项目工作过,你会发现跑测试针的是非常沉重的。解决这个问题就是不要载入你不需要的数据。

如果你需要大量的数据,可以使用:factory_girl

#good
describe "user"
  describe ".top" do
    before { FactoryGirl.create_list(:user, 3) }
    it { User.top(2).should have(2).item }
  end
end

选择Factory,抛弃fixtures

这个话题有点炒冷饭,但是还是值得被记住。不要使用 fixtures 是因为它们很难被控制和维护。 改用 factories 来减少冗长的数据准备过程。

#bad
user = user.create(
  name: ‘genoveffa‘,
  surname: ‘piccolina‘,
  city: ‘billyville‘,
  birth: ‘17 agoust 1982‘,
  active: true
)
#good
user = FactoryGirl.create :user

还有一点很重要。当谈到单元测试的时候,最佳的实践是既不用 fixtures 也不用 factories。尽可能的把你的业务逻辑 放在那些不需要复杂的而又非常费时的 factories 或者 fixtures。这篇文章可以让你了解更多。

一目了然的matcher

使用易读的或是RSpec 自带的 matchers

#bad
lambda { model.save! }.should raise_error Mongoid::Errors::DocumentNotFound
#good
expect { model.save! }.to raise_error Mongoid::Errors::DocumentNotFoundf

共用的测试

写测试非常棒,你会因此变得越来越自信。但到后来你会开始看到重复的代码出现在各个地方。用共用的测试来 DRY 你的测试。

#bad
describe ‘get /devices‘ do
  let!(:resource) { FactoryGirl.create :device, created_from: user.id }
  let(:uri) { ‘/devices‘ }

  context ‘when shows all resources‘ do
    let!(:not_owned) { FactoryGirl.create factory }

    it ‘shows all owned resources‘ do
      page.driver.get uri
      page.status_code.should be(200)
      contains_owned_resource resource
      does_not_contain_resource not_owned
    end
  end

  describe ‘?start=:uri‘ do
    it ‘shows the next page‘ do
      page.driver.get uri, start: resource.uri
      page.status_code.should be(200)
      contains_resource resources.first
      page.should_not have_content resource.id.to_s
    end
  end
end
#good
describe ‘get /devices‘ do

  let!(:resource) { FactoryGirl.create :device, created_from: user.id }
  let(:uri)       { ‘/devices‘ }

  it_behaves_like ‘a listable resource‘
  it_behaves_like ‘a paginable resource‘
  it_behaves_like ‘a searchable resource‘
  it_behaves_like ‘a filterable list‘
end

不过从我们的经验来看,共用的测试主要是用于 controller。因为 model 之间的行为迥异,他们(经常)没用太多共通的逻辑。

测你所见

 尽可能详尽的测试你的 Model 和程序的行为(集成测试)。不要为 Controller 去写那些复杂而又没用的测试。

我一开始测试我的项目的时候,我测的就是 Controller,现在我却不这么做了。 现在我只用 RSpec 和 Capybara 来写一些集成测试。为什么?因为我真的觉得你应该 测试那些你能看到的,而 Controller 测试对于我来说是多余的。你最终会发现,你大部分的测试 都是涉及到 Model。集成测试能被轻松的整理到共用的测试来构建清晰而又可读的测试。

这是一个在 Ruby 社区中非常开放的争论,而且两边都是有理有据。支持测试 Controller 的那一方 会告诉你集成测试并不能覆盖所有的情况,而且很慢。

但他们都错了。你可以很轻易的覆盖所有的情形,并且使用一些像 Guard 这样的自动化测试工具来跑一个个单独的测试。 这样你就可以在不停止你的工作流程的情况下闪电般的只运行那些你正需要的测试。

别用should

当你在描述你的测试的时候,不要使用 should,使用第三人称现在时。更进一步,你可以使用新的expectation语法。

#bad
it ‘should not change timings‘ do
  consumption.occur_at.should == valid.occur_atend
#good
it ‘does not change timings‘ do
  expect(consumption.occur_at).to equal(valid.occur_at)
end

the should_notthe should_clean 这两个 Gem 在 RSpec 中贯彻这条实践并清除那些以 should 开头的测试。

用Guard进行自动化测试

每次你修改了你的项目就要重新跑所有的测试用力真的是一种负担。这会消耗很多时间并且打断了你的工作。 使用 Guard 你可以自动化的运行那些和你正在修改的测试,Model,Controller 或者文件有关的测试。

#good
bundle exec guard

这是一个有些基本加载规则的 Guardfile 的例子。

#good
guard ‘rspec‘, cli: ‘--drb --format fuubar --color‘, version: 2 do
  # 执行所有被修改的测试
  watch(%r{^spec/.+_spec\.rb$})  # 执行与 lib 文件夹下有关联的文件被修改的测试
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }  # 执行与被修改 Model 相关的测试
  watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }  # 执行与被修改的 View 相关的测试
  watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }  # 执行与被修改的 Controller 相关的集成测试
  watch(%r{^app/controllers/(.+)\.rb}) { |m| "spec/requests/#{m[1]}_spec.rb" }  # 当 application_controller 被修改时执行所有的集成测试
  watch(‘app/controllers/application_controller.rb‘) { "spec/requests" }end

Guard 虽然好用但是它还是无法满足你搜有的需求。有时候你的 TDD 工作流程需要一些能够测试你想要测试文件的快捷键来让它变得完美。 而在你需要 push 代码的时候用 rake task 来跑完整的测试。这里有些给 Vim 用的快捷键配置

了解更多关于 Guard RSpec.

伪装HTTP请求

有时候你需要用到一些外部的服务。在你不能真的使用这些外部服务的时候你应该用类似 webmock 这样的工具来进行伪装。

#good
context "with unauthorized access" do
  let(:uri) { ‘http://api.lelylan.com/types‘ }
  before    { stub_request(:get, uri).to_return(status: 401, body: fixture(‘401.json‘)) }
  it "gets a not authorized notification" do
    page.driver.get uri
    page.should have_content ‘Access denied‘
  end
end

了解更多关于 webmockVCR。 这还有一个非常不错的关于如何结合他们使用的视频

时间: 2024-10-14 05:55:02

RSpec入门指南的相关文章

Quartz.NET简介及入门指南

Quartz.NET简介 Quartz.NET是一个功能完备的开源调度系统,从最小的应用到大规模的企业系统皆可适用. Quartz.NET是一个纯净的用C#语言编写的.NET类库,是对非常流行的JAVA开源调度框架 Quartz 的移植. 入门指南 本入门指南包括以下内容: 下载 Quartz.NET 安装 Quartz.NET 根据你的特定项目配置 Quartz 启动一个样例程序 下载和安装 你可以下载 zip 文件或使用 Nuget 程序包.Nuget 程序包只包含 Quartz.NET 运

Java程序员的Golang入门指南(上)

Java程序员的Golang入门指南 1.序言 Golang作为一门出身名门望族的编程语言新星,像豆瓣的Redis平台Codis.类Evernote的云笔记leanote等. 1.1 为什么要学习 如果有人说X语言比Y语言好,两方的支持者经常会激烈地争吵.如果你是某种语言老手,你就是那门语言的"传道者",下意识地会保护它.无论承认与否,你都已被困在一个隧道里,你看到的完全是局限的.<肖申克的救赎>对此有很好的注脚: [Red] These walls are funny.

【翻译Autofac的帮助文档】1.入门指南

[写在前面]尝试做完一件工作之外自我觉得有意义的一件事,那就从翻译Autofac的帮助文档吧. 入门指南 将Autofac集成你的应用程序的步骤通常很简单,一般是: 时刻以IOC(控制反转)的思想来规划你的应用程序 在你的Porject中添加Autofac引用 按照如下步骤设计应用程序的启动环节 创建一个ContainerBuilder 向ContainerBuilder注册组件 通过ContainerBuilder的Build()方法获得Container(后续需用到) 在应用程序运行环节时,

Markdown——入门指南

导语: Markdown 是一种轻量级的「标记语言」,它的优点很多,目前也被越来越多的写作爱好者,撰稿者广泛使用.看到这里请不要被「标记」.「语言」所迷惑,Markdown 的语法十分简单.常用的标记符号也不超过十个,这种相对于更为复杂的 HTML 标记语言来说,Markdown 可谓是十分轻量的,学习成本也不需要太多,且一旦熟悉这种语法规则,会有一劳永逸的效果. Ulysses for Mac 一,认识 Markdown 在刚才的导语里提到,Markdown 是一种用来写作的轻量级「标记语言」

Win32编程API 基础篇 -- 1.入门指南 根据英文教程翻译

入门指南 本教程是关于什么的 本教程的目的是向你介绍使用win32 API编写程序的基础知识(和通用的写法).使用的语言是C,但大多数C++编译器也能成功编译,事实上,教程中的绝大多数内容都适用于任何可以连接API的语言,包括Java.Assembly和Visual Basic:我不会向你呈现任何跟这些语言相关的代码,这需要你在本教程的指导下自己去完成,有一些人在本API的基础上使用其他语言进行编程取得了相当的成功. 本教程不会教你C语言,也不会告诉你怎样去运行你特定的编译器(Borland C

[转载]TFS入门指南

[原文发表地址] Tutorial: Getting Started with TFS in VS2010 [原文发表时间] Wednesday, October 21, 2009 1:00 PM 本月初,我们发布了TFS新基础配置.该配置为建立支持源码管理,工作项和生成(builds)的TFS版本提供了便利. 这是一个好机会将你在VSS(Visual Source Safe)上的资源迁移到TFS,并且还可以选用一些新的特性.现在VS2010 Beta2的正式版已经发布了,下面是该系统的入门指南

编程入门指南

前言 如今编程成为了一个越来越重要的「技能」:作为设计师,懂一些编程可能会帮你更好地理解自己的工作内容:作为创业者,技术创始人的身份则会让你的很多工作显得更容易.而作为刚想入门的新手,面对眼前海量的信息,或许根本不知道从哪里开始:入门轻松度过初级材料的学习后,发现学习越来越困难,陡峭的学习曲线又让你望而却步:你知道如何在页面上打印输出一些文本行,但是你不知道何时该进行一个真正的有用的项目:你不清楚自己还有哪些不知道的东西,你甚至搞不清下一步该学什么. 这篇文章的内容对此不仅会有一些方向性的建议,

物联网操作系统HelloX开发者入门指南

HelloX开发者入门指南 HelloX是聚焦于物联网领域的操作系统开发项目,可以通过百度搜索"HelloX",获取详细信息.当前开发团队正在进一步招募中,欢迎您的了解和加入.如果您希望加入HelloX的开发团队,建议参照下列步骤进行操作: 1.      首先,请亲手熟悉和操作HelloX操作系统,这是我们开发的核心组件之一,所有其它组件(包括后台组件,终端产品等)都围绕HelloX操作系统展开.这一步很容易操作,请从github(github.com/hellox-project/

STM32F10X入门指南---AD转换

首先,点击下面的链接下载我们需要使用的代码.链接 1.添加必要的文件: 之前我们说过,有三个文件是必须添加的,这三个文件分别是:startup_stm32f10x_xd.s ,stm32f10x_rcc.c ,system_stm32f10x.c.其中,前面的xd是根据你的芯片的容量来选择的.这三个文件都可以在千帆提供的代码中找到.文件路径:Core.rar\Core\STM32\Source\Must . 另外,如果想操作IO口,必须添加千帆的一个库文件DeviceBase.cpp.文件路径: