测试 Rails 测试驱动开发 Factory Girl 用例重构实践

caiqinghua · 2015年07月15日 · 最后由 ywjno 回复于 2015年07月17日 · 2771 次阅读

写在前面

一个理科生,没什么文化功底,写的不好,大家多多鼓励,希望能每每有心得或者学到新东西都分享给大家。 重构中还有很多细节不知道怎么表达出来,因为是事后回忆重构过程,所以贴的代码和过程可能有点乱,各位看官要有点耐心。

先写个重构要点吧,以免看不到文章最后的同学,可以知道本文主要讲些啥玩意

  1. 在 data_help 中准备好测试数据,避免测试过程创建数据重复问题
  2. 用 find_by 先查找数据库解决数据重复创建问题
  3. 用 traits 解决数据排列组合问题
  4. 用 factory girl seed 改善重复创建问题和用例执行慢的问题

一、本文测试对象介绍

开发员工管理的特性,基本功能:

  • 超级管理员能管理所有员工
  • 公司管理员能管理本公司的所有员工
  • 一般员工只能管理自己

二、本文涉及的对象

员工角色

  • 超级管理员
  • 公司管理员
  • 一般员工

公司

  • 总公司
  • 子公司

三、初始代码

require 'rails_helper'

RSpec.describe Cpanel::StaffsController, type: :controller do

  login_staff
  describe "GET #index" do
    it "returns http success" do
      get :index
      expect(response).to have_http_status(:success)
    end

    it "super admin can see all the staffs" do 
      admin = create(:admin)
      get :index
      expect(assigns(:staffs)).to match_array(Staff.all)
    end

    it "company admin can see all the staffs in the company" do
      staff = create(:staff_jack)
      get :index
      expect(assigns(:staffs)).to match_array(Staff.where(company: staff.company))
    end

    it "normal staff can only see himself" do
    end
  end

  describe "GET #new" do
    .....
  end

  describe "staffs associate shops" do
    it "a staff associate with no shop" do
      staff = create(:staff_jack)
      expect(staff.name).to eq "jack"

      staff.shops = Shop.find([])
      expect(staff.shops.count).to eq 0
    end

    .....

  end

end

存在的问题

  • describe "staffs associate shops" 属于 model 测试,应该移到 model 测试文件中
  • index 测试用登录有问题 login_staff,用的同一个用户登录

四、重构一:超级管理员,公司管理员和普通员工分别登录测试

RSpec.describe Cpanel::StaffsController, type: :controller do

  describe "GET #index" do

    describe "render index" do
      login_super_admin
      it "returns http success" do
        get :index
        expect(response).to have_http_status(:success)
      end
    end

    describe "super admin render index" do
      login_super_admin
      it "can manage all staffs" do   
        get :index
        expect(assigns(:staffs)).to match_array(Staff.all)
      end
    end

    describe "company admin render index" do
      login_head_company_admin_staff
      it "can manage all staffs in the company" do            
        get :index
        expect(assigns(:staffs)).to match_array(Staff.where(company: seed(:head_company)))
      end

      it "can not manage the staffs from other company" do 
        get :index
        expect(assigns(:staffs)).not_to include(Staff.where(company: seed(:branch_company)))
      end
    end

    describe "normal staff render index" do
      login_head_company_normal_staff
      it "can only manage himself" do 
        get :index
        expect(assigns(:staffs)).to match_array(seed(:head_company_normal_staff))
        expect(assigns(:staffs).size).to eq(1)
      end
    end
  end
  • 问题 login_super_admin 角色创建重复了

五、重构二:解决 login_super_admin 中重复创建 staff_super_admin_role 问题

重构前代码

staffs.rb
factory :super_admin, class: Staff do
    name 'super_admin'
    mobile '13600000001'
    password '1' * 6

    before(:create) do |staff|
      staff.staff_roles = [ create(:staff_super_admin_role) ]
    end
  end


def login_super_admin
     before(:each) do
       staff = FactoryGirl.create(:super_admin)
       controller.send(:login_as, staff)
     end
 end

解决办法:在 Datahelps 中统一准备好测试数据,避免重复重建

重构后代码

module DataHelpers

  def set_staffs
    before(:each) do
      @super_admin_role = create(:staff_super_admin_role)
      @company_admin_role = create(:staff_company_admin_role)
      @normal_staff_role = create(:staff_normal_role)

      @head_company = create(:head_company)
      @branch_company = create(:branch_company) 

      @super_admin = create(:super_admin)
      @super_admin.add_role(@super_admin_role.name)

      @head_company_admin_staff = create(:head_company_admin_staff)
      @head_company_admin_staff.add_role(@company_admin_role.name)
      @head_company_admin_staff.company = @head_company

      @head_company_normal_staff = create(:head_company_normal_staff)
      @head_company_normal_staff.add_role(@normal_staff_role.name)
      @head_company_normal_staff.company = @head_company

      @branch_company_admin_staff = create(:branch_company_admin_staff)
      @branch_company_admin_staff.add_role(@company_admin_role.name)
      @branch_company_admin_staff.company = @branch_company

      @branch_company_normal_staff = create(:branch_company_normal_staff)
      @branch_company_normal_staff.add_role(@normal_staff_role.name)
      @branch_company_normal_staff.company = @branch_company

    end
  end

end

六、重构三:用 traits 解决排列组合数据问题(ps.用 find_by 先查找数据库解决数据重复创建问题)

重构前的问题

@head_company_admin_staff = create(:head_company_admin_staff)
      @head_company_admin_staff.add_role(@company_admin_role.name)
      @head_company_admin_staff.company = @head_company

仔细查看重构二步骤中的 set_staffs 代码,如何增加一个 staff 的区域属性,发现需要修改多个地方,需要继续改善。

重构后代码

spec/factories/staffs.rb

FactoryGirl.define do

  factory :staff, aliases: [:random_staff] do
    mobile
    password '1' * 6
    name { generate(:random_name) }

    trait :super_admin do
      after(:create) do |staff| 
        super_admin = StaffRole.find_by(name: 'super_admin') || FactoryGirl.create(:staff_super_admin_role)
        staff.add_role(super_admin.name) 
      end
    end

    trait :company_admin do
      after(:create) do |staff| 
        company_admin = StaffRole.find_by(name: 'company_admin') || FactoryGirl.create(:staff_company_admin_role)
        staff.add_role(company_admin.name) 
      end
    end

    trait :normal_staff do
      after(:create) do |staff| 
        normal_staff = StaffRole.find_by(name: 'normal_staff') || FactoryGirl.create(:staff_normal_role)
        staff.add_role(normal_staff.name) 
      end
    end

    trait :head_company do 
      after(:create) do |staff|
        staff.company = Company.find_by(name: 'head_company') || FactoryGirl.create(:head_company)
      end
    end

    trait :branch_company do 
      after(:create) do |staff|
        staff.company = Company.find_by(name: 'branch_company') || FactoryGirl.create(:branch_company)
      end
    end

  end
end
spec/support/data_helpers.rb 

module DataHelpers

....
  def set_staffs
    before(:each) do 
      @super_admin = create :staff, :super_admin, :head_company
      @head_company_admin_staff = create :staff, :company_admin, :head_company
      @head_company_normal_staff = create :staff, :normal_staff, :head_company
      @branch_company_admin_staff = create :staff, :company_admin, :branch_company
      @branch_company_normal_staff = create :staff, :normal_staff, :branch_company
    end
  end

end

重构四:用 factory girl seed 改善重复创建问题和用例执行慢的问题

用 factory girl seed 改善重复创建问题和用例执行慢的问题

自己顶一下,测试太冷了。大家写测试用例都没有遇到问题吗?还是都不写测试用例?

看来大家都是不用写测试用例的。。。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号