본문 바로가기
카테고리 없음

테스트 쉽게 하기2

by 경자꿈사 2025. 1. 6.

이번 글에서는 Minitest에서 제공하는 여러 가지 어설션 메서드들을 살펴보려고 한다. 
아래 Minitest 모듈의 Assertions 모듈에서 정의한 어설션 메서드들이 보인다. 이 중 자주 사용될 만한 것들을 위주로 살펴보자.

아래 그림을 보면 4개의 서로 다른 어설션 메서드를 사용하여 검증 코드를 작성했는데, 모든 테스트가 성공하도록 작성하였다.
어설션 메서드의 이름만 봐도 어떤 검증을 하는지 쉽게 알 수 있다.
assert_empty는 어떤 객체(보통 문자열, 배열, 해시 등)가 값(문자 또는 요소나 항목)을 하나도 포함하고 있지 않은지 검증하고,
assert_includes는 어떤 객체(첫 번째 인수)가 특정 값(두 번째 인수)을 포함하고 있는지 검증하고,
assert_instance_of는 어떤 객체(두 번째 인수)가 특정 클래스(첫 번째 인수)의 인스턴스인지 검증하고,
assert_kind_of는 어떤 객체(두 번째 인수)가 특정 클래스(첫 번째 인수)의 인스턴스 또는 그것을 상속한 클래스의 인스턴스인지 검증한다.
아래 assert_empty의 예제처럼 인수가 올 위치에 식을 작성하면 식을 먼저 평가하고 그 결괏값이 인수가 되므로, 결국 해당 식의 결괏값을 검증하게 되는 것이다.

assert_kind_of 메서드의 첫 번째 인수에는 클래스뿐만 아니라 모듈도 가능한데 아래 그림을 보면 instance_of? 메서드와 kind_of? 메서드의 차이점을 알 수 있다.
모듈을 통해서는 객체를 생성할 수 없기 때문에 instance_of? 메서드의 인수로 모듈이 오면 결괏값은 항상 false가 된다.
그리고 kind_of? 메서드는 is_a? 메서드의 별칭이기 때문에 두 메서드는 동일하게 동작한다.

그리고 앞의 테스트 코드를 다시 보면 실제 어설션 메서드를 사용해 작성한 검증 구문은 4개인데, 테스트 결과를 보면 '6 assertions'라고 나온다.
이유는 assert_empty와 assert_includes 메서드가 내부적으로 assert_respond_to라는 또 다른 어설션 메서드를 호출하기 때문이다.
assert_empty는 검증을 위해 empty? 메서드를 사용하고 assert_includes는 include? 메서드를 사용한다.
그래서 먼저 검증 대상 객체에 대해 empty? 메서드 또는 include? 메서드를 호출할 수 있는지 검사해야 하고 이때 assert_respond_to 메서드를 사용해서 검사하는 것이다.
아래 그림처럼 empty? 메서드와 include? 메서드가 없는 객체를 대상으로 assert_empty와 assert_includes 어설션 메서드로 검증 코드를 작성해 보면 확실히 알 수 있다.

테스트 결과를 보면 '2 assertions'이라고 나오는데, assert_empty와 assert_includes 둘 다 assert_respond_to를 사용한 검증에서 실패했기 때문이다.
실패 메시지를 보면 범위 1..2 와 숫자 1이 각각 'empty?' 와 'include?' 메서드 호출에 응답할 수 있기를 기대했지만 그렇지 않았다는 내용이다.
아래 그림을 보면 Integer 클래스의 인스턴스 메서드 중에는 실제 empty? 와 include? 메서드가 포함되어 있지 않음을 알 수 있다.
그리고 respond_to? 메서드를 사용하여 숫자, 문자열, 배열, 범위에 대해 empty?와 include? 메서드의 호출 가능 여부를 직접 확인해 보았다.

계속해서 또 다른 어설션 메서드를 살펴보자.
아래 그림을 보면 5개의 서로 다른 어설션 메서드가 보이는데, 이 메서드들 역시 어떠한 검증을 하는지 이름이 명확하다.
assert_match는 어떤 문자열이 특정 패턴에 매칭되는 부분을 포함하고 있는지 검증하고,
assert_nil은 어떤 객체 또는 아래 예제처럼 식의 결괏값이 nil 인지 검증하고,
assert_raises는 블록을 실행했을 때 특정 예외가 발생하는지 검증하는데, 예외 클래스를 여러 개 지정할 수 있고 그 예외들 중 어느 하나가 발생하거나 또는 그 하위 예외가 발생하면 검증 성공이다.
assert_respond_to는 앞서 얘기한 것처럼 어떤 객체가 특정 메서드 호출에 응답 가능한지 검증하고,
assert_same은 어떤 객체(두 번째 인수)가 특정 객체(첫 번째 인수)와 동일한 객체인지 검증한다.

테스트 실행 결과를 보면 '6 assertions'로 실제 어설션 메서드를 사용해 작성한 검증 구문 수보다 1개 더 많게 나오는데, 이것은 assert_match 내부에서 첫 번째 인수로 받은 객체에 대해 =~ 연산자를 호출할 수 있는지 assert_respond_to를 사용해서 검증하기 때문이다.
=~ 은 패턴 매칭 연산자로 아래 그림처럼 문자열에서 해당 정규 표현식이 처음으로 매칭되는 부분의 시작 인덱스를 반환한다.
String 클래스에도 =~ 연산자가 정의되어 있으므로 정규 표현식과 문자열을 서로 바꿔서 작성해도 된다.
매칭되는 부분을 찾지 못하면 nil을 반환한다.
패턴 매칭 연산자 및 정규 표현식과 관련해서 좀 더 자세한 내용은 '문자열과 친해지기3'글을 참고하길 바란다.

어설션 메서드 중 가장 기본이 되는 assert 메서드가 있는데, 이것은 어떤 객체 또는 식의 결괏값이 참으로 평가되는지 검증한다.
그래서 아래 그림처럼 assert 메서드만을 사용해서도 검증 구분을 작성할 수 있다.

하지만 앞서 살펴본 여러 어설션 메서드들은 메서드 이름을 통해 어떠한 검증을 하려는 건지 쉽게 알 수 있으므로 테스트 코드의 가독성을 높여준다.
특히, assert_raises 어설션 같은 경우에는 assert로 대신하기에는 다소 무리가 있어 보인다.
그리고 assert 메서드는 검증 실패 시 기본으로 보여주는 메시지가 그대로 사용하기에는 정보가 부족할 수 있어 별도의 실패 메시지를 인수로 전달해야 하는 번거로움이 있을 수 있다.
아래 그림을 보면 assert 메서드가 기본으로 보여주는 메시지만으로는 실제 어떤 검증이 왜 실패했는지 알 수 없지만, assert_equal과 assert_includes 메서드의 메시지는 실제 어떤 검증이 왜 실패했는지 비교적 잘 나타내 준다.

따라서 가능하면, 검증하려고 하는 내용에 적합한 별도의 어설션 메서드가 있다면 그것을 사용해 검증 구문을 작성하는 것이 좋다.
See you again~~