RSPEC으로 모델 테스트하기

상황

아래처럼 굉장히 많은 제약(validates) 가 있는 모델이 있다고 하자

class WarRequest < ApplicationRecord
  belongs_to :rule
  has_one :war, dependent: :destroy
  has_many :war_statuses, dependent: :destroy
  has_many :guilds, through: :war_statuses
  validates :status, inclusion: { in: ["pending", "accepted", "canceled"], message: "상태를 잘못 입력하셨습니다." }
  validates :rule_id, inclusion: { in: 1..7, message: "요청하신 룰이 존재하지 않습니다." }
  validates :target_match_score, inclusion: { in: [3, 5, 7, 10], message: "목표 점수를 잘못 입력하셨습니다." }
  validates :max_no_reply_count, inclusion: { in: 3..10, message: "최대 미응답 개수를 잘못 입력하셨습니다." }
  validates :bet_point, inclusion: { in: (1000..10000).step(500), message: "배팅 포인트를 잘못 입력하셨습니다."}
  validates :start_date, presence: { message: "시작일을 입력해주세요."}
  validate :start_date_after_now
  validate :start_date_after_max_end_date
  validate :end_date_after_start_date

  ....
end

이 모델을 테스트 하려면 어떻게 할까? 이전까지는 하나하나 .... 일일이 상황을 만들고 브라우저를 키고.. 레일즈 콘솔에 들어가서 하고.... 정말 많은 작업을 통해 테스트를 진행해야 했다.

RAILS 는 당연하게도 이런 불편함을 덜어주기 위해 다양한 테스트 방법을 제공한다. 그중에서 우리가 사용할 방법은 RSPEC

해결 - RSPEC 를 사용하

현재 war_request.rb 에는 아래와 같은 클래스 메서드가 있다.

def self.create_by(params)
  war_request = self.new(
    rule_id: params[:rule_id],
    bet_point: params[:bet_point],
    start_date: Date.parse(params[:start_date]),
    end_date: Date.parse(params[:start_date]) + params[:war_duration].to_i.days,
    war_time: Time.new(1 ,1 ,1 , params[:war_time].to_i),
    max_no_reply_count: params[:max_no_reply_count],
    include_ladder: params[:include_ladder],
    include_tournament: params[:include_tournament],
    target_match_score: params[:target_match_score],
    )
  if war_request.invalid?
    @error_message = war_request.errors[war_request.errors.attribute_names.first].first
    raise WarRequestError.new(@error_message)
  end
  war_request.save!
  war_request
end

뭔가 이상하지 않은가? 그렇다.. 아래를 보면 war_requestcreate 하는 코드에 war_statuses 를 만들어 준다!

왜냐하면 war_request 가 만들어지는게 성공한다면 이와 동시에 war_statuschallengerenemy 양쪽에 생겨야 하기 때문이다.

여기서 문제가 생기는데 war_requestvalid 체크를 하기 위해서는 enemy_guildchallenger_guild 의 정보가 필요하지만, 이 두 정보는 war_request 의 속성으로 존재하지 않고 war_status 의 속성으로 존재한다.

따라서 정상적인 방법으로는 war_requestvalid 여부를 확인하지 못하게 되는거고 이 때문에 create_by 라는 이상한 클래스 메서드를 만들어서 war_request 를 만든다음에 war_statuse 를 만들게 된다.

그리고 앞서 말한 challenger_guildenemy_guildvalidate 검사는 war_statuse 에서 하게 된다. war_statusevalid 체크가 끝나면 그제서야 war_requestcreate_by 라는 메서드는 종료가 된다...

위의 과정에서 valid 를 통과하지 못하면 throw 가 던져지게 된다.

throw 가 던져지는 상황을 어떻게 테스트 할까?

자 이제 우리의 war_request 모델을 테스트 하기 위해서는 클래스 메서드인 self.create_by(params) 를 사용해야 함을 알았다.

그리고 이 war_request 가 valid 하지 않다면 throw 가 던저지는거 또한 알았다.

따라서 우리가 이 모델을 테스트 하기 위해서는 인위적으로 throw 를 던지는 상황을 만들고, 이를 catch 해서 해당 error 의 class 를 조사해야 하는 과정을 거치면 되는거다! 아마 그 그림은 아래와 같을 것이다.

def validation_of_war_request

    begin
    war_request = WarRequest.create_by(params)
    ...
    ...
  rescue SomethingError => e
    ...
  rescue AnotherError => e
    # 이 예외가 발생하면 테스트 성공!!!
  rescue
    ...
  else
    ...
  end 
end

흠.. 한눈에 보기에도 아주 힘들어 보인다...

RSPEC 를 이용한 Exception 테스트

자 이제 RSPEC를 이용해서 진행해 보자.

1. 폴더와 파일을 만든다.

# /spec/models/war_request_spec.rb

2. 테스트 코드 작성

먼저 상황을 정리하자.

  1. 총 11개의 길드가 존재한다.

  2. 그 중 4개가 전쟁중이다

    • 전쟁중일 때는 war-request 를 만들 수 없다!!

    • exception 의 던져진다!

따라서 총 11개의 길드를 each 로 돌면서 만약 해당 길드가 전쟁중이라면 request 를 날리는 테스트를 진행해보자. 그 결과로 4번의 exception 이 던져질 것이고 우리는 이를 except 하고 있다가 해당 exception 이 던져지면 테스트는 통과 될 것임.

코드는 아래와 같다.

Guild.all.each do |guild|
  it "Cannot create war-request which is in-war" do
    p = params.clone
    p[:guild_id] = guild.id
    expect{ WarRequest.create_by!(p) }.to raise_error if guild.in_war?
  end
end

Last updated