nacyot profile image

루비(Ruby) 테스트 프레임워크 RSpec 2.14 매쳐(Matchers)

프로그래밍 2014년 04월 07일 발행

루비에서는 테스트를 하기 위해 minitestBDD 프레임워크인 RSpec이 많이 사용됩니다. 유닛 테스트에 친숙하신 분들은 minitest를 선호하지만, 좀 더 설명적인 테스트가 가능한 RSpec도 많이 사용되고 있습니다. 이 글에서는 RSpec 테스트에서 사용할 수 있는 빌트인 테스트 매쳐(Matcher)들을 간단히 소개합니다.

테스트 준비

일반적으로 테스트 파일은 프로그램 파일과 별개로 작성됩니다. 여기서는 편의상 pry 위에서 바로 테스트를 진행합니다. 따라서 이 글의 내용은 irb나 pry를 통해서 따라해볼 수 있습니다. 필요한 경우 먼저 rspec과 pry 패키지를 설치합니다

$ gem install rspec pry

다음으로 pry를 실행합니다

$ pry
[1] pry(main)>

다음으로 Matecher를 사용하기 위해 rspecRSpec::Matchers를 읽어들입니다.

184번째 입력:
require 'rspec'
include RSpec::Matchers
184번째 평가:
Object

테스트 기초

테스트는 기본적으로 아래와 같은 형식으로 쓰여집니다

expect(actual).to be(expected)

여기서 actual에는 실제로 테스트하고자 하는 객체가 들어가며, expected에는 예상하는 값이 들어갑니다. 그리고 beactualexpected를 비교하기 위한 매쳐를 지정합니다

또한 단언문과 마찬가지로 테스트가 실패하면 예외를 발생시킵니다. 즉, 리턴값이 true라고 해서 테스트가 성공을 의미하는 게 아니며, 거꾸로 false라고 해서 실패를 의미하는 게 아닙니다. 테스트가 실패하면 RSpec::Expectations::ExpectationNotMetError 예외가 발생합니다.

동일값 테스트 매쳐

가장 기본적이고 가장 많이 쓰이는 매쳐는 실제값과 예상되는 값이 같은 지를 비교하는 매쳐입니다

185번째 입력:
# Equivalence
expect("프로그래밍 언어 루비").to eq("프로그래밍 언어 루비")
185번째 평가:
true
186번째 입력:
expect("프로그래밍 언어 루비").to eql("프로그래밍 언어 루비")
186번째 평가:
true
187번째 입력:
expect("프로그래밍 언어 루비").to be == "프로그래밍 언어 루비"
187번째 평가:
true

동일성 테스트 매쳐

eq가 실제값과 예상값이 같은지를 평가하는 매쳐라면, equal은 비교하는 두 객체가 같은 객체인지를 테스트합니다. 루비에선 같은 값을 가진 심볼은 항상 같은 객체입니다. 하지만 같은 값을 가지고 있더라도 별개로 생성된 문자열은 다른 문자열입니다. 따라서 같은 값을 가진 심볼은 동일성 테스트를 통과하지만, 아래 문자열 테스트는 실패합니다

188번째 입력:
# Identity
expect(:Hello_world).to equal(:Hello_world)
188번째 평가:
true
189번째 입력:
expect(:Hello_world).to be(:Hello_world)
189번째 평가:
true
190번째 입력:
expect("Hello, World").to equal("Hello, World")
RSpec::Expectations::ExpectationNotMetError: 
expected #<String:70158862817480> => "Hello, World"
     got #<String:70158862817540> => "Hello, World"

Compared using equal?, which compares object identity,
but expected and actual are not the same object. Use
`expect(actual).to eq(expected)` if you don't care about
object identity in this example.


연산자 매쳐

동일값 테스트만큼 많이 사용되는 매쳐는 실제값과 예상값을 비교하는 연산자 매쳐입니다. 기본적으로 동일값을 비교하는 ==을 비롯해 >, >=, <=, < 등을 사용할 수 있습니다.

191번째 입력:
# Comparisons
expect(100 + 1).to be > 100
191번째 평가:
true
192번째 입력:
expect(100 - 1).to be < 100
192번째 평가:
true

match=~는 특정 문자열이 주어진 정규표현식에 매치되는 지 여부를 테스트합니다.

193번째 입력:
expect("나랏말 싸미 듕국과 달라").to match(/듕국/)
193번째 평가:
#<MatchData "듕국">
194번째 입력:
expect("나랏말 싸미 듕국과 달라").to be =~ /듕국/
194번째 평가:
7

숫자 범위 매쳐

be_within.of 매쳐는 실제값이 예상값 +- 특정 범위에 포함되는 지 여부를 테스트합니다.

195번째 입력:
expect(100).to be_within(5).of(100 + 3)
195번째 평가:
true
196번째 입력:
expect(100).to be_within(5).of(100 + 6)
RSpec::Expectations::ExpectationNotMetError: expected 100 to be within 5 of 106

타입 매쳐

어떤 객체가 어떤 클래스로부터 만들어졌는지 테스트할 수 있습니다.

197번째 입력:
# Types and classes
expect("Ruby").to be_instance_of(String)
197번째 평가:
true
198번째 입력:
expect("Ruby").to be_an_instance_of(String)
198번째 평가:
true
199번째 입력:
expect("Ruby").to be_a(String)
199번째 평가:
true
200번째 입력:
expect("Ruby").to be_kind_of(String)
200번째 평가:
true

참거짓 매쳐

어떤 객체가 참인지 거짓인지 테스트할 수 있습니다.

201번째 입력:
# Truthiness and existentialism
expect("").to be_true
201번째 평가:
true
202번째 입력:
expect(nil).to be_false
202번째 평가:
true
203번째 입력:
expect(true).to be
203번째 평가:
true
204번째 입력:
expect(7).to be
204번째 평가:
true
205번째 입력:
expect(nil).not_to be
205번째 평가:
false
206번째 입력:
expect(nil).to be_nil
206번째 평가:
true

변화 매쳐

change 매쳐는 expect에 주어진 블록이 실행되었을 때 change에 주어진 블록의 평가 결과가 변화하는 지를 테스트합니다. 변화했다면 테스트가 통과합니다. 필요에 따라서 얼마만큼 변화했는지를 테스트할 수 있는 byexpect 블록을 실행시키기 전의 값을 테스트할 수 있는 from, 평가한 이후의 값을 테스트 할 수 있는 to 메소드와 함께 사용될 수 있습니다

207번째 입력:
arr = []
expect{arr << 1}.to change{arr.count}
207번째 평가:
true
208번째 입력:
expect{arr << 1}.to change{arr.count}.by(1)
208번째 평가:
true
209번째 입력:
str = "Hello, world."
expect{ str.upcase! }.to change{ str }.from("Hello, world.").to("HELLO, WORLD.")
209번째 평가:
true

예외 메쳐

expect에 주어진 블록의 평가 결과가 예외를 일으키는 지 테스트합니다. expect가 블록을 받는다는 데 주의가 필요합니다.

210번째 입력:
# Expecting errors
expect{ raise }.to raise_error
210번째 평가:
true
211번째 입력:
expect{ non_existance }.to raise_error
211번째 평가:
true
212번째 입력:
expect { non_existance }.to raise_error(NameError)
212번째 평가:
true

Have 매쳐

컬렉션 객체가 몇 개의 항목을 가지고 있는 지 테스트합니다.

213번째 입력:
arr = "Hello".split("")
213번째 평가:
["H", "e", "l", "l", "o"]
214번째 입력:
expect(arr).to have(5).items
214번째 평가:
true
215번째 입력:
expect(arr).to have_exactly(5).items
215번째 평가:
true
216번째 입력:
expect(arr).to have_at_least(1).items
216번째 평가:
true
217번째 입력:
expect(arr).to have_at_most(10).items
217번째 평가:
true

have_key 매쳐

해시에 특정 키가 포함되어있는 지 테스트합니다.

218번째 입력:
expect({name: "nacyot", blog: "blog.nacyot.com"}).to have_key(:name)
218번째 평가:
true

포함 매쳐

객체에 특정한 값이나 키가 포함되어있는 지 테스트합니다.

219번째 입력:
expect([1, 2, 3]).to be_include(1)
219번째 평가:
true
220번째 입력:
expect({name: "nacyot", blog: "blog.nacyot.com"}).to be_include(:name)
220번째 평가:
true
221번째 입력:
expect("Hello").to be_include("H")
221번째 평가:
true

start_with / end_with 매쳐

객체의 처음이나 끝이 특정 항목으로 시작하거나 끝나는지 테스트합니다

222번째 입력:
expect("Hello, world").to start_with("Hello")
222번째 평가:
true
223번째 입력:
expect("Hello, world").to end_with("world")
223번째 평가:
true
224번째 입력:
expect([1, 2, 3, 4, 5]).to start_with(1, 2)
224번째 평가:
true
225번째 입력:
expect([1, 2, 3, 4, 5]).to end_with(5)
225번째 평가:
true

커버 매쳐

범위 객체가 특정 값을 포함하는 지 테스트합니다.

226번째 입력:
expect(100..1000).to cover(101)
226번째 평가:
true
227번째 입력:
expect(100..1000).not_to cover(99)
227번째 평가:
false

respond_to 매쳐

객체가 특정 메시지를 받을 수 있는 지 테스트 합니다.

228번째 입력:
expect("Hello").to respond_to(:upcase)
228번째 평가:
true

satisfy 매쳐

블록의 평가 결과가 참인지를 평가합니다. 이 때 expect에 주어진 값이 평가 대상이 되며 satisfy 블록의 인자로 넘겨집니다. 이 블록이 참이면 테스트가 통과합니다.

229번째 입력:
expect(false).to satisfy{|value| !value}
229번째 평가:
true

Yielding

메소드 내에서 yield하는 부분에 대해서 테스트합니다.

230번째 입력:
expect {|b| 5.tap(&b)}.to yield_control
230번째 평가:
true
231번째 입력:
expect { |b| 5.tap(&b) }.to yield_with_args(5)
231번째 평가:
true
232번째 입력:
expect { |b| 5.tap(&b) }.to yield_with_args(Fixnum)
232번째 평가:
true
233번째 입력:
expect { |b| "a string".tap(&b) }.to yield_with_args(/str/)
233번째 평가:
true
234번째 입력:
expect { |b| [1, 2, 3].each(&b) }.to yield_successive_args(1, 2, 3)
234번째 평가:
true
235번째 입력:
expect { |b| { :a => 1, :b => 2 }.each(&b) }.to yield_successive_args([:a, 1], [:b, 2])
235번째 평가:
true

Predicate matchers

참거짓을 리턴하는 메소드를 사용해 테스트합니다. 이 때 매쳐 이름은 be_메소드이름이 됩니다.

241번째 입력:
expect(7).not_to be_zero # 7.zero?
241번째 평가:
false

결론

여기까지 소개한 매쳐들이 RSpec 빌트인 매쳐로 테스트하는데 가장 많이 사용됩니다. 조금 기교스러운 매쳐들도 있어서 익숙해지는데 시간이 걸릴지도 모르겠습니다만, 기본적인 원리만 이해하고 빌트인 매쳐들만 활용해도 대부분의 테스트를 하는 데는 크게 무리가 없습니다. 좀 더 자세한 사항은 Relish의 Rspec 문서에서 확인하실 수 있습니다. 또한 아직 베타이지만 조만간 업데이트 예정인 메이저 버전 3에서 추가되는 매쳐도 확인할 필요가 있습니다. 역시 공식문서에서 확인할 수 있습니다.


comments powered by Disqus