浅谈(一):在rails中如何用 rspec 写模型的测试

话说昨日,健哥让我分享下怎么用rspec写模型的测试,顿时一脸懵逼,因为只会些拳脚猫功夫,赶紧百度谷歌相关知识,七凑八凑,凑出来下面这篇总结,参考过的文档和博客如下(感兴趣的可以去看看):

  1. rspec入门教程: http://www.jianshu.com/p/1db9ee327357
  2. 1000 个小时学会 Rails - 003 RSpec 行为驱动测试简介: https://ruby-china.org/topics/2848
  3. RSpec 中 let 和 subject 的区别:https://ruby-china.org/topics/9271
  4. Ruby on Rails 自动化测试: https://ihower.tw/rails4/testing.html
  5. let和before的区别: http://stackoverflow.com/questions/5974360/rspec-difference-between-let-and-before-block

Step1. 新建一个rails应用

话不多说,让我们创建一个rails 新应用,从头开始体验这一奇妙的旅程;

rails new demo

该应用结构如下:

Paste_Image.png

可以看到,在rails中,默认使用的测试工具是Test::Unit,而不是Rspec,怎么替换掉呢?

Step 2: 安装 Rspec

在Rails的配置文件Gemfile配置文件中,配置下面信息

group :development, :test do 
    gem 'byebug', platform: :mri  
    gem 'rspec-rails', '3.5.1' 
    # gem 'factory_girl_rails', '4.7.0'  
    # gem 'faker', '1.6.6'
end

我们没有必要单独的安装RSpec, 因为它是rspec-rails的依赖件会被自动安装, 执行bundle install 或者bundle install --without production来安装使用的gem.
执行安装RSpec的命令:

rails generate rspec:install

该命令执行完毕之后,会产生一个文件夹spec,该文件夹下面有spec/spec_helper.rb这个文件,spec_helper.rb用来设置测试的配置信息.

Paste_Image.png

下面是spec的固定的规范,固定的格式.

describe XXX do 
  it XXX do ...... 
  end
end

包含了一个 describe 块以及其中的一个测试用例(sample),以 it "..." do 开头的代码块就是一个用例.
可以看到,spec文件夹以及相关文件创建完成。


Paste_Image.png

Step 3:创建模型和方法

既然我们要介绍模型测试的方法,那么让我们创建一个模型: Girl(女孩,相信大家都比较喜欢),来做一个简单的测试演示:

rails generate model Girl 

该命令创建的文件如下:


Paste_Image.png

note:在spec/models/ 创建的 girl_spec.rb文件就是我们写测试用例的地方(一般这种文件的后缀常用_spec)。
创建模型后, 执行迁移命令:

bin/rake db:migrate

下面让我们开始编写模型方法:
既然是girl,大家肯定都关心其是否单身,是否漂亮。
如果单身的话,就代表自己有机会追到她,如果既单身又漂亮的话,一般就会符合我等屌丝的口味。
我们定义这两个属性,以及对应的方法;
app/models/girl.rb 文件内容如下:

class Girl < ApplicationRecord  
    attr_accessor :single, :beautiful
    def initialize(params)    
        self.single = params[:single]    
        self.beautiful = params[:beautiful]  
    end  
    # 是否有机会  
    def have_chance?    
        self.single  
    end  
    # 是否符合你的口味  
    def suit_my_taste??    
        self.single && self.beautiful  
    end
end

Step 4: 编写测试用例 并执行

针对我们编写的两个模型方法,我们开始在girl_spec.rb文件中编写相应的测试:
以 it "..." do 开头的代码块就是一个用例.
首先构建一个单身,但不漂亮的女孩,来作为我们的测试数据;
expect 语句 可以对返回的结果进行期望值处理,满足expect语句,即代表通过此测试用例;

require 'rails_helper'
RSpec.describe Girl, type: :model do
  # 测试用例1 
  # 创建了一个 单身 但不漂亮的 女孩, 来验证 have_chance? 方法
  it "have chance? {single but not beautiful}" do
    params = { single: true, beautiful: false }
    girl = Girl.new(params)
    expect(girl.have_chance?).to eq(true)
  end
end

rspec测试命令格式: ruby rspec file_path/file_name
运行如下测试命令:运行girl_spec.rb文件中的所有测试用例

rspec spec/models/girl_spec.rb

运行结果如下图:

Paste_Image.png

Test::Unit一样,用一个极富深意的点,告诉我们测试通过!太棒了!这是我们写的第一个 RSpec测试,欢呼!
1 example, 0 failures 代表: 总共1个测试用例,有0个运行失败;
接下来,书写第2个测试用例-(测试该女孩是否符合你的口味)
girl_spec.rb文件中,添加第2个测试用例:

require 'rails_helper'
RSpec.describe Girl, type: :model do

  # 测试用例1
  # 创建了一个 单身 但不漂亮的 女孩, 来验证 have_chance? 方法
  it "have chance? {single but not beautiful}" do
    params = { single: true, beautiful: false }
    girl = Girl.new(params)
    expect(girl.have_chance?).to eq(true)
  end

  # 测试用例2
  # 创建了一个 单身 但不漂亮的 女孩, 来验证 suit_my_taste? 方法
  it "suit my taste? {single but not beautiful}" do
    params = { single: true, beautiful: false }
    girl = Girl.new(params)
    expect(girl.suit_my_taste?).to eq(true)
  end
end

运行如下测试命令:运行girl_spec.rb文件中的所有测试用例

rspec spec/models/girl_spec.rb

运行结果如下图:

picture

如图所示:首行返回了 .F, "."代表第1个测试用例通过,"F"第2个测试用例没通过(False)
Failures(失败详情中 )可以看到:
expected:true, got:false,(期望值true, 实际获得false), 所以没通过。
2 examples, 1 failure 代表: 总共2个测试用例,有1个运行失败;
解释:很显然,在我们expect语句中,期望值是true,实际上,该女孩单身,但不漂亮;suit_my_taste?方法 会返回false; 所以无法通过expect语句,则该测试用例会返回false,代表没通过此测试用例;

接下来,在第3个测试用例中,创建一个 既单身又漂亮的女孩 来测试 suit_my_taste? 方法 的返回结果;

require 'rails_helper'
RSpec.describe Girl, type: :model do

  # 测试用例1
  # 创建了一个 单身 但不漂亮的 女孩, 来验证 have_chance? 方法
  it "have chance? {single but not beautiful}" do
    params = { single: true, beautiful: false }
    girl = Girl.new(params)
    expect(girl.have_chance?).to eq(true)
  end

  # 测试用例2
  # 创建了一个 单身 但不漂亮的 女孩, 来验证 suit_my_taste? 方法
  it "suit my taste? {single but not beautiful}" do
    params = { single: true, beautiful: false }
    girl = Girl.new(params)
    expect(girl.suit_my_taste?).to eq(true)
  end

  # 测试用例3
  # 创建了一个 既单身又漂亮的 女孩, 来验证 suit_my_taste? 方法
  it "suit my taste? {single but not beautiful}" do
    params = { single: true, beautiful: true }
    girl = Girl.new(params)
    expect(girl.suit_my_taste?).to eq(true)
  end
end
Tips 1:如果只想运行girl_spec.rb测试文件中的某一个测试用例,该怎么处理?

rspec 测试单个用例的命令格式: ruby rspec file_path/file_name:LineNumber 即:加上冒号和it语句块对应的行号;
运行如下测试命令:运行girl_spec.rb文件中的第3个测试用例:(第3个it ...do 对应的行号为23)

rspec spec/models/girl_spec.rb:23

运行结果如下图:


解释:"."代表 该测试用例通过。

Step 5: 优化

Tip 2: 为了遵循Ruby的 DRY原则(Don't repeat yourself), 下面介绍一下before,subject,let的用法;

1. before的用法

before(:each)会在每个测试用例执行前, (每段it之前執行)
before(:all) 会所有测试用例执行前,只执行一次,(整段describe前只執行一次)

假设要为构建出的不同实例对象,测试它们的每个模型方法,
在此,我选用before(:all)来优化代码:

require 'rails_helper'

RSpec.describe Girl, type: :model do

  before(:all) do
    params_1 = { single: true, beautiful: false }
    params_2 = { single: true, beautiful: true }
    @girl_1 = Girl.new(params_1)
    @girl_2 = Girl.new(params_2)
  end

  ########################################################
  # 测试用例1-1
  # 创建了一个 单身 但不漂亮的 女孩, 来验证 have_chance? 方法
  it "have chance? {single but not beautiful}" do
    expect(@girl_1.have_chance?).to eq(true)
  end

  # 测试用例1-2
  # 创建了一个 单身 但不漂亮的 女孩, 来验证 suit_my_taste? 方法
  it "suit my taste? {single but not beautiful}" do
    expect(@girl_1.suit_my_taste?).to eq(true)
  end

  ########################################################
  # 测试用例2-1
  # 创建了一个 单身 但不漂亮的 女孩, 来验证 have_chance? 方法
  it "have chance? {single but not beautiful}" do
    expect(@girl_2.have_chance?).to eq(true)
  end

  # 测试用例2-2
  # 创建了一个 既单身又漂亮的 女孩, 来验证 suit_my_taste? 方法
  it "suit my taste? {single but not beautiful}" do
    expect(@girl_2.suit_my_taste?).to eq(true)
  end

end

运行如下测试命令:运行girl_spec.rb文件中的所有测试用例

rspec spec/models/girl_spec.rb

运行结果如下图:

Paste_Image.png

解释:".F.."代表第2个测试用例没通过,其他测试用例均通过。

2. subject用法

subject { ... } 定义了一个叫做 subject 的方法, 它返回了代码块内定义的那个对象.
subject可以用来配合should进行隐式调用,何为隐式调用?(见参考链接3,本文暂不讲述)
如果你要使用主动式的 expect,那么可以给subject起名字(非隐式调用):
在上述girl_spec.rb文件中 添加如下代码 也可行

# 使用 subject
  subject(:girl_1){ @girl_1 }
  subject(:girl_2){ @girl_2 }

  it "girl_1.have chance?" do
    expect(girl_1.have_chance?).to eq(true)
  end
  it "girl_1.suit my taste?" do
    expect(girl_1.suit_my_taste?).to eq(true)
  end

  it "girl_2.have chance?" do
    expect(girl_2.have_chance?).to eq(true)
  end
  it "girl_2.suit my taste?" do
    expect(girl_2.suit_my_taste?).to eq(true)
  end

3. let的用法

let和before 有区别,详见参考链接5
在上述girl_spec.rb 文件中 添加如下代码 也可行

  # 使用let
  let(:girl1){ Girl.new({ single: true, beautiful: false }) }
  let(:girl2){ Girl.new({ single: true, beautiful: true }) }

  it "girl1.have chance?" do
    expect(girl1.have_chance?).to eq(true)
  end
  it "girl1.suit my taste?" do
    expect(girl1.suit_my_taste?).to eq(true)
  end

  it "girl2.have chance?" do
    expect(girl2.have_chance?).to eq(true)
  end
  it "girl2.suit my taste?" do
    expect(girl2.suit_my_taste?).to eq(true)
  end

结语

是不是不过瘾?大家是否也发现,要对自己的模型方法进行一个完善的测试,需要构建合法的数据,手动构建数据的量太少,无法满足大量测试数据的需求,于是乎,工厂女孩(factory_girl)就要出场了, 另外一个角色就是faker(这个可不是LOL界的faker大魔王), 它能够构建类似真实的数据,两者结合,威力无穷,欲知后事如何,且听下回分解...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容