RSpec 정리

Tags:

목차

Rails의 Behaviour-driven Development (BDD)인 Rspec의 설정과 사용법에 대한 듀토리얼입니다. 근무중인 회사에서 Teamcity와 연동하여 git에 commit 할 때마다 Rspec 테스트를 자동으로 하게 해놨다. 테스트 프레임워크는 리팩토링할 때 다른 곳에서 나타나는 버그를 잡아주는 역할을 해준다. 사실 rspec 설정을 어렵지 않고 테스트 케이스 만드는 작업이 시간이 오래 걸린다. 보통 에러가 나는 엣지 케이스(최소값, 최대값), 에러가 안나는 정상 경우, 에러가 나는 경우, 특정 경우에 따른 분기 테스트 정도를 한다.

준비하기

설치할것 - RSpec, Capybara, Shoulda-Matchers, Database Cleaner

Shoulda-Matchers

matcher를 하나의 라인으로 간략하게 사용할 수 있게해준다.

### Gemfile

group :test do
  gem 'shoulda-matchers', '~> 3.0', require: false
end
### spec/rails_helper.rb

require 'shoulda/matchers'

Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :rspec
    with.library :rails
  end
end

Database Cleaner

테스트시 디비의 상태를 깨끗하게 해준다.

### Gemfile

group :test do
  gem 'database_cleaner', '~> 1.5'
end
### spec/rails_helper.rb

config.use_transactional_fixtures = false
### spec/support/database_cleaner.rb

RSpec.configure do |config|

  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
  end

  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end

  config.before(:each, :js => true) do
    DatabaseCleaner.strategy = :truncation
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end
end

Capybara

사용자의 행동을 시뮬레이션하는 테스트기능을 제공한다. gem에서 :test 그룹에 넣어주자.

### Gemfile

group :test do  
  gem 'capybara', '~> 2.5'
end
### spec_helper.rb

require 'capybara/rspec'

Faker, Factory Gril

Faker : 랜덤한 데이터를 생성
Factory girl : 가짜 객체를 생성. Faker로 랜덤한 데이터를 입력한 객체를 생성할 때 같이 사용한다

### Gemfile

gem 'faker', '~> 1.6.1'
gem 'factory_girl_rails', '~> 4.5.0'

테스트하기

  • Model Specs, Controller Spec, Feature Spec 3가지를 테스트함.

Model Specs

Model를 테스트함. valide를 테스트할 수 있다. User 모델을 예로 들어보자. 먼저 User 템플릿을 factory를 사용해서 만든다. 팩토리로 만든 user로 테스트를 할 거다.

### spec/factories.rb

FactoryGirl.define do
  factory :user do
    name { Faker::Name.name }
    email { Faker::Internet.email }
    password { 'password1' }
  end
end
### spec/models/user_spec.rb

require 'rails_helper'
RSpec.describe User, type: :model do  
  describe "validations" do
    let(:user) { FactoryGirl.build(:user) }
    # factories.rb를 참조해 user를 만들어준다

    # email과 name가 존재하는지에 대한 valide 를 체크.
    it { is_expected.to validate_presence_of(:email) }
    it { is_expected.to validate_presence_of(:name) }
    
    it '잘못된 이메일' do
      user.email = 'invalid_mail'
      expect(user).not_to be_valid
      expect(user).to have(1).errors_on(:email)
      expect(user.errors.messages[:email].join).to include('잘못된 이메일 형식입니다')
    end

    it '단순한 비밀번호' do
      user.password = '123456'
      expect(user).not_to be_valid
      expect(user).to have(1).errors_on(:password)
      expect(user.errors.messages[:password].join).to include('영문/숫자 등을 조합해 주세요.')
    end      
  end
end

user의 email과 name의 존재해야하는 validate_presence_of를 만족하는것에 대한 테스트, email과 password의 입력형식에 대한 테스트이다. user 모델에 email,name의 validate 설정이 되어 있어야하고, email, password의 validate와 에러 메시지가 설정되어있어한다. 서비스에 구현한 모델을 테스트해보자.

터미널에서 테스트한다

rspec spec/models/contact_spec.rb

위 예제에서 describe는 테스트 그룹을 만든다. 인자로 모델, 타입을 넘길 수 있다.

RSpec.describe User, type: :model do
..

내부에도 describe를 만들수 있다. user 예제에서는 user 모델에서 validation을 테스트할 그룹을 만든것이다.

it는 하나의 테스트 케이스를 나타낸다. 위 예제에선 잘못된 이메일이 나타나는 경우와 단순한 비밀번호일 경우를 테스트한 것이다. itcontext와 조합을 해서 사용한다. 뒤에서 다루겠지만 context언제를 나타낸다. 단순한 validate를 체크할 때는 shoulda-matchers를 사용해서 코드를 줄일 수 있다.

it "email이 존재" do
  expect(user).to validate_presence_of(:email)
end

위 코드는 email의 validate_presence_of 테스트이다. 단순한 validate 체크이기 때문에 아래와 같이 줄일 수 있으며, shoulda-matchers가 이를 가능하게 해준다.

it { is_expected.to validate_presence_of(:email) }

expect는 아웃풋이 뭔지를 판단한다. tdd에서 assert.~~의 역할을 한다고 보면된다. expect.to, .not_to등을 사용해서 matcher(be_valid, have(1).errors_on(), include(), eq() 등)의 결과물의 판단 유무를 리턴한다. 즉 matcher의 결과와 같냐 틀리냐에 대한 예측에 따라 테스트의 통과 유무를 판단한다.

Controller Specs

컨트롤러의 get/post 함수가 제대로 동작하는지, 혹은 render를 한다면 어떤 view/layout을 랜드하는지, 어떤 내용을 담아야하는지 혹은 create를 한다면 제대로 추가가 됐는지 를 테스트한다. 본 예제에서는 post 컨트롤러가 있다고 가정하고 테스트해보자. 각자 가지고 있는 컨트롤러를 테스트하면 된다.

### spec/controllers/post_controller_spec.rb

require 'rails_helper'

RSpec.describe PostController, type: :controller do
  render_views
  let(:user) { FactoryGirl.build(:user)}
  let(:post) { FactoryGirl.create(:post) }

  describe 'show' do    
    before do
      sign_in user
    end
    it "post 페이지 보인다" do
      get :show, :id => post.id
      expect(response).to have_http_status :success
    end
  end  
end

factory를 사용하려면 factories.rb에 추가해줘야하는것을 잊지말자.

### factories.rb

FactoryGirl.define do
  factory :user do
    name { Faker::Name.name }
    email { Faker::Internet.email }
    password { 'password1' }
  end
  factory :post do
    ..
  end  
end

위 예제는 post 컨트롤러에서 show를 하면 잘 동작하는것을 테스트하는 것이다. before 블록은 테스트 하기전에 수행할 블록이다. sign_in은 해당 유저로 로그인하는데, 만약 해당 컨트롤러에서 유저의 로그인 여부 코드가 있다면 이를 통해 테스트 할 수 있다. sign_in이 만약 없는 함수로 에러가 나면 아래 설정을 추가해 주면 된다.

### spec/rails_helper.rb

config.include Devise::Test::ControllerHelpers, :type => :controller

각 함수에 params을 넘기고 싶으면 params: {key: value, key: value}로 하면되지만, router에서 필요로 하는 params (주로 id)는 위 예처럼 :id => post.id 로 넘겨줘야 에러가 안난다. rails4 에서만 이러는듯 하다. 다 넘겨주고 싶으면

get :show, :id => post.id, params: {key: value, key: value}

하면된다.

터미널에서 테스트

rspec spec/controllers/post_controller_spec.rb

Feature Specs

사용자의 행동에 따른 결과를 테스트 할 수 있다. 예를들어 입력 form에 값을 입력하고 submit 버튼을 누르면 어떤 페이지가 렌더링 되며, 그 페이지에는 어떠한 값이 있어야하는지를 테스트 할 수 있다. 하이레벨의 테스트를 한다고 생각하면 될 것 같다.

각자의 입력폼을 가진 view와 그에 따른 controller가 있으면 그걸 가지고 테스트하면 된다. 여기서는 회원가입 페이지에서 가입 정보를 입력하고 가입 버튼을 눌렀을 때 원하는 페이지 그리고, 가입이 잘 됐는지를 테스트 해본다.

sign_up이라는 회원가입 페이지에서 이메일과 닉네임과 비밀번호, 비밀번호 확인, 그리고 약관동의와 개인정보 수집 이용 동의를 클릭하고 가입하기 버튼을 클릭 한 경우일 때

  1. 홈으로 잘 가는지.
  2. 가입된 회원이 있는지

이 두가지를 테스트 한다.

### spec/features/register_user_spec.rb
require 'rails_helper'

RSpec.feature "계정 생성 테스트", type: :feature do
  scenario "Create a new user" do
     visit "/sign_up"     # 회원 가입 페이지
     email = "in1004kyu@gmail.com"
     # 가입 폼에 데이터를 입력하는 시뮬레이션 
     # input 의 value name에 (name: 'user[email]'), 값을 입력한다 (with: email)
     fill_in name: 'user[email]', with: email
     fill_in name: 'user[nickname]', with: "codly"
     fill_in name: 'user[password]', with: "1234567a"
     fill_in name: 'user[password_confirmation]', with: "1234567a"     
     # 체크박스가 있다면 체크를 한다. (이용약관 개인정보 동의. #~~ 는 input id 이다.)
     find(:css, "#user_terms").set(true)
     find(:css, "#user_privacy").set(true)
     # 버튼을 클릭한다. 버튼의 text를 인자로 넘겨준다
     click_button '가입하기'
     # 테스트 : 가입이 잘되면 홈으로 가야한다.
     expect(current_path).to eq(root_path)
     user = User.find_by_email(email)
     # 테스트 : 가입이 잘되면 해당 회원이 있어야한다.
     expect(user).not_to be_nil
  end
end

커버리지 체크

SimpleCov gem은 어플리케이션 중에서 얼마나 테스트가 됐는지 체크해주는 커버리지 툴이다. Gemfiel의 test 그룹에 추가해주자.

### Gemfile
gem 'simplecov', :require => false

설정을 추가해주자. 여기선 테스트를 스킵할 것을 필터를 사용할 수 있다.

### spec/rails_helper.rb
require 'simplecov'
SimpleCov.start 'rails' do
  # admin 폴더는 커버리지 대상에서 제외함.
  add_filter '/app/admin/'
end

터미널에서 테스트를 해보면 coverage 디렉토리에 index.html에서 커버리지 분석 결과를 볼 수 있다.

rspec spec