浅谈(二):使用rspec+factory_girl+faker进行测试

老习惯,列出本文参考的文档和博客,致以崇高的敬意
1.FactoryGirl: http://www.jianshu.com/p/dfd0ca700a36
2.rspec 入门教程: http://www.jianshu.com/p/1db9ee327357
3.faker介绍:https://github.com/stympy/faker

简介

上文讲到要引入factory_girlfaker来进行自动化测试,下面对它们进行简要介绍:

(1) factory_girl:

factory的wiki解释是: factory method pattern is a creational pattern that uses factory methods to deal with the problem of creating objects without having to specify the exact class of the object that will be created.
简而言之:一个用来创建对象数据的工具,而且它不需要我们关注细节;
factory_girl是专门用来构造模拟数据的,完美替代Fixture的工具,Fixture的缺点很明显,经常要处理各种关联和依赖。如果测试要求的数据量大,还要自己手动制造数据。 这些在Factory Girl中都有了好的解决。生成大量数据可以用association来解决对象间的关联,sequence来解决生成大量测试数据的问题

(2) faker:

用来创建比较真实的假数据,下面是真实的实例。

Faker::Name.name                       #=> "Christophe Bartell"
Faker::Internet.email                  #=> "kirsten.greenholt@isher.info"
Faker::Geolocation.lat                 #=> 38.4685363769531
Faker::Geolocation.lng                 #=> -87.888795
Faker::Address.street_address          #=> "95519 Evans Oval"
Faker::Lorem.sentence                  #=> "Aut recusandae quam cum omnis aut dolores."
Faker::Lorem.paragraph                 #=> "Earum et est qui id totam adipisci sint. 

Step 1: 创建应用,进行配置

话不多讲,还是新建一个rails应用来开始今天的旅途:

rails new demo160810

修改Gemfile文件的配置信息,将所需的gem包添加进去

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

执行bundle install来安装使用的gem.

bundle install

初始化rspec, 生成rspec相关文件夹和配置文件

rails generate rspec:install

生成的文件信息如下:



使用脚手架生成相关内容(我们习惯性的还是创建一个Girl,作为我们的例子):

rails g scaffold Girl

创建的文件信息如下:



直观信息见下图,可以看到, 在spec/目录下生成controllers,models、views、 request、factories等相应的文件夹和文件, 我们可以在里面编写相应的测试。


Step 2:创建模型方法

修改app/models/girl.rb文件

class Girl < ApplicationRecord
  attr_accessor :name, :phone_number, :address, :single, :beautiful, :age

  def have_chance?
    return self.single
  end

  # 符合胃口的条件: (25岁以下,单身即可)||(25岁以上,单身且漂亮)
  def your_taste?
    return (self.age <= 25 && self.single) || (self.age > 25 && self.beautiful && self.single)
  end

  # 给你一个数组,里面包含多个girl, 筛选出符合你胃口 的 girl
  # 要求封装一个response[Hash],作为该方法的返回值
  def self.choose_some_from_many(girls = [])

    response = {
        code: false, # 存在的话 true, 不存在的话(result内部为空) false
        result: {}  # 存储 id=>girl  id从0开始递增
    }
    id = 0
    girls.each do |girl|
      if girl.your_taste?
        response[:result][id] = girl
        id = id + 1
      end
    end

    if response[:result].size() > 0
      response[:code] = true
    end

    return response
  end

end

较比之前有三处改动
1、模型Girl的属性增加到6个(姓名,手机号,地址,是否单身,是否漂亮,年龄)
2、改了符合胃口的条件:(25岁以下,单身即可)||(25岁以上,单身且漂亮)
3、为广大男同胞提供了一个选择女孩的方法(choose_some_from_many),可以从海量女孩里面选出符合自己胃口的女孩,(这是个类方法,直接使用[类名.methodName]调用,实例对象无法调用)。

回顾下 通过手动创建对象数据的方法 进行测试的过程

目的:我们要测试 这个模型方法Girl.choose_some_from_many返回的信息是否正确。
所以我们最好构建出:能够尽可能涵盖 你模型方法中 的逻辑判断条件 所有状态的数据(众所周知,逻辑判断条件如果越复杂,排列组合的状态总数就会很大),手工提供这么多组case,会很麻烦;
既然是回顾,我手工创建了三个girl对象数据,用来测试
spec/models/girl_spec.rb

require 'rails_helper'

RSpec.describe Girl, :type => :model do

  before(:all){
    params_1 = {
        name: "rose", phone_number: "13245454545", address: "zhangheng road",
        single: true, beautiful: true, age: 22
    }
    params_2 = {
        name: "hanmeime", phone_number: "13212345678", address: "zhangheng road",
        single: false, beautiful: true, age: 24
    }
    params_3 = {
        name: "lili", phone_number: "18911547847", address: "zhangheng road",
        single: true, beautiful: true, age: 30
    }
    @girl_1 = Girl.new(params_1)
    @girl_2 = Girl.new(params_2)
    @girl_3 = Girl.new(params_3)
  }

  # 数据: 3个女孩的信息, 测试choose_some_from_many方法的 返回结果是否正确
  it "test choose_some_from_many( three girls)" do
    response = Girl.choose_some_from_many(girls = [@girl_1, @girl_2, @girl_3])
    p response

    if response[:code] == false
      expect(response[:result]).blank?
    elsif response[:code] == true
      response[:result].each do |k, v|
        expect(v.your_taste?).to eq(true)
      end
    end

  end

end

在测试用例中,我们对模型方法Girl.choose_some_from_many返回的response进行验证,看其是否满足我们的需求,如果code为fasle,则result应该为空,如果code为true,则result里保存的众多girl,应该要满足我们之前定义的筛选条件。
执行测试命令:

rspec spec/models/girl_spec.rb

得到如下结果:


仍然,用一个极富深意的点,告诉我们该测试用例通过了。
问题来了:如果要创建几十个,几百个女孩,并且要求数据尽量真实,那从哪里去搞这么多数据? 这时候手动创建测试数据,就无法满足我们的需求了。于是 factory_girl 和 faker就要出场了。

Step 3: 使用factory_girl 和 faker

3.1 Factory生成文件存放的位置

通过Factory Girl生成器生成的文件都会放在 spec/factories文件夹中,文件的名字会是用对应模型名字的复数形式(所以Girl模型的预构件文件是spec/factories/grils.rb)

3.2 spec/spec_helper.rb文件中配置如下

以前使用 create(xxx),create_list(xxx),build_stubbed(XXX),attributes_for(xxx)等
方法时候, 都必须使用FactoryGirl.create(xxx) FactoryGirl.create_list(xxx)...格式; 从Factory Girl 3.0 开始, 只要做一个设置,Rails就会变得简洁, 可以加在rails_helper.rb文件Rspec.configure块中的任何地方。

RSpec.configure do |config|
  config.include FactoryGirl::Syntax::Methods
end

然后在测试的代码中,就能使用比较简洁的语法;
可以直接使用create(xxx),create_list(xxx),build_stubbed(XXX),attributes_for(xxx)等方法(新手的话,还是建议在这些方法前加上 FactoryGirl. 个人感觉更加直观,知道这些方法来自哪里,熟练以后,再去掉也不迟)

3.3 正式开始

factory_girl 的create_list(xxx), build_list(xxx).可以创建一组对象数据
faker 可以动态生成一些类似真实数据的 假数据。
首先,我们先看看spec/factories/girls.rb长什么样子:


在factory:girl do ...end 块中,我们可以把生成对象的零件放进去,这样就能够创建出来对象来了。
修改 spec/factories/girls.rb 文件

FactoryGirl.define do
  factory :girl do
    # 方式1: 指定 (固定)
    # name "jack"
    # phone_number "13945756489"
    # address "zhangheng Road 666"
    # single true
    # beautiful false
    # age 24

    # 方式2: 使用faker,制作类似真实数据的 假数据 (不固定,产生不同的数据)
    name {Faker::Name.name }
    phone_number { Faker::PhoneNumber.cell_phone }
    address { Faker::Address.street_address }
    single { Faker::Boolean.boolean }
    beautiful { Faker::Boolean.boolean }
    age {Faker::Number.between(20,30) }
  end
end

方式1:可以固定每个零件的信息(那么制造出来的 对象,就都一模一样了)
方式2:使用faker,动态配置每个零件的信息(那么制造出来的对象,基本会有所不同[相同的概率很低,但>0])
然后修改spec/models/girl_spec.rb 文件:

require 'rails_helper'

RSpec.describe Girl, :type => :model do
  before(:all){
    @girl_1 = FactoryGirl.create(:girl)
    @girls = FactoryGirl.create_list(:girl, 20)
  }

  # 数据: 1个女孩的信息, 测试choose_some_from_many方法的 返回结果是否正确
  it "test  1 girl" do
    response = Girl.choose_some_from_many(girls = [@girl_1])
    # p response

    if response[:code] == false
      expect(response[:result]).blank?
    elsif response[:code] == true
      response[:result].each do |k, v|
        expect(v.your_taste?).to eq(true)
      end
    end

  end

  # 数据: 多个女孩的信息, 测试choose_some_from_many方法的 返回结果是否正确
  it "test ,many girls" do
    response = Girl.choose_some_from_many(girls = @girls)
    # p response

    if response[:code] == false
      expect(response[:result]).blank?
    elsif response[:code] == true
      response[:result].each do |k, v|
        expect(v.your_taste?).to eq(true)
      end
    end

  end

end

在before语句块内,我们通过factory_girl的 create方法 创建了1个girl对象数据;
通过factory_girl的 create_list方法,创建了多个girl对象数据,得到的是一个包含多个girl对象的数组。
分别用两个测试用例中分别运用这两个构建的数据,来测试方法的正确性:
运行测试命令:

rspec spec/models/girl_spec.rb

运行结果如下图:“..”表示两个测试用例均通过



另外让我们看一下factory创建出的2组数据(create方法创建出的1个girl对象,create_list方法创建出的多个(20)girl对象数组),在控制台输出,看一下:
create方法创建出的1个girl对象(通过了筛选):

Maxwell Wolff,993.452.1229,589 Barton Canyon,true,true,30

create_list方法创建出的多个(20)girl对象数组, 筛选出来的有8个。

Aglae Quitzon,(476) 919-8629,78258 Angeline Fort,true,false,23
Linnie Lebsack,(330) 092-5259,5825 Mortimer Drive,true,true,21
Megane Barton Sr.,534.363.9597,7709 Rolfson Groves,true,false,20
Miss Deangelo Bins,(173) 607-1957,9732 Maya Flats,true,false,21
Bernard Kiehn,700.037.5700,627 Cecile Island,true,true,23
Ms. Stan Kertzmann,1-528-834-5310,7121 Jaron Mountain,true,false,22
Alva Borer,1-851-850-2001,59440 Duncan Trafficway,true,false,20
Jared Treutel Sr.,359-901-8795,882 Rosalind Square,true,false,24

暂时介绍到这,后续有新的东西再补充。

推荐阅读更多精彩内容