[유닛 테스트] 4.Hamcrest

rin·2020년 4월 8일
0

유닛 테스트

목록 보기
4/4
post-thumbnail

ref. Hamcrest Tutorial을 번역한다

들어가기전에

🤔 Hamcrest?

Java 프로그래밍 언어로 부터 시작한, (현재는 다양한 언어에 대해 지원하고 있다.) 소프트웨어 테스트 작성을 지원하는 프레임 워크이다.
사용자 정의 assertion Matcher 작성(Hamcrest는 'Matchers'의 anagram-동일한 알파벳을 재배열하여 만들어낸 문장이나 단어-)을 지원하여 검증 규칙을 선언적으로 정의할 수 있다.
이 Matcher는 JUnit 및 jMock과 같은 단위 테스트 프레임워크에서 사용된다.
JUnit4에 포함되었지만, JUnit5에서 생략되었다.

Hamcrest Tutorial

introduction

📌Hamcrest는 'match' 규칙을 선언적으로 정의할 수 있는 matcher 객체를 작성하기 위한 프레임 워크이다.
UI 유효성 검사 또는 데이터 필터링과 같이 매처가 중요하지 않은 상황이 많이 있지만 매처가 가장 일반적으로 사용되는 유연한 테스트 작성 영역에 있다.
이 튜토리얼에서는 유닛 테스트에 Hamcrest를 사용하는 방법을 보여준다.

테스트를 작성할 때, 테스트를 과도하게 지정(overspecifying)하여 테스트 코드에 대한 수정(관계 없는 요소를 변경했는데 테스트가 실패한다거나, 너무 기준을 맞추기가 힘든 경우)이 불가피하게 되는 것과 너무 느슨하게 지정하여 테스트 할 대상이 깨져도 계속 통과해 테스트 가치를 떨어트리는 것 사이의 균형을 맞추기가 어려운 경우가 있다. 어떤 측면에서 테스트를 진행할 것인지 정확하게 선택하고 그에 필요한 값을 정밀하게 제어할 수 있는 도구를 사용하면 정확한 테스트를 작성하는데 많은 도움이 된다. 이런 테스트는 타깃이 되는 동작이 예상된 동작을 벗어나면 실패하지만 관련이 없는 사소한 변경이 있을 때는 통과하도록 해준다.

My first Hamcrest test

우리는 매우 간단한 JUnit5 테스트를 작성하는 것으로 시작하지만, JUnit의 assertEquals 메소드를 사용하는 대신 import static으로 가져오는 Hamcrest의 assertThat구조와 표준 Matcher set을 사용할 수 있다.

import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat; 
import static org.hamcrest.Matchers.*;

public class BiscuitTest {
  @Test 
  public void testEquals() { 
    Biscuit theBiscuit = new Biscuit("Ginger"); 
    Biscuit myBiscuit = new Biscuit("Ginger"); 
    assertThat(theBiscuit, equalTo(myBiscuit)); 
  } 
} 

이 assertThat method는 test assertion을 만들기 위해 짜여진 문장이다.
이 예제에서 assertion에 필요한 것은 첫번째 매개변수인 오브젝트-theBiscuit-과 두번째 매개변수인 Matcher-myBiscuit 객체의 matcher-이다. 여기서 Object equals 메소드를 사용하여 한 객체가 다른 객체와 같은지 확인한다. 비스킷 클래스는 equals 메소드를 정의하므로 테스트에 통과한다.

만약 테스트에 둘 이상의 assertion이 있을 경우, assertion에 테스트된 값의 식별자를 포함할 수 있다.

assertThat("chocolate chips", theBiscuit.getChocolateChipCount(), equalTo(10)); 

assertThat("hazelnuts", theBiscuit.getHazelnutCount(), equalTo(3));

Other test frameworks

Hamcrest는 처음부터 다른 유닛 테스트 프레임 워크와 통합되도록 설계되었다. 예를 들어 Hamcrest는 JUnit의 모든 버전 및 TestNG와 함께 사용할 수 있다. (자세한 내용은 전체 Hamcrest 배포와 함께 제공되는 예제를 살펴보도록.) 다른 테스트 스타일이 Hamcrest와 함께 존재할 수 있으므로 기존 테스트 suite에서 Hamcrest-style assertion을 사용하여 쉽게 마이그레이션할 수 있다.

Hamcrest는 어댑터를 사용해 mock objects 프레임 워크의 Matcher 개념을 Hamcrest matcher로 연결하여 mock objects 프레임 워크와 함께 사용할 수도 있다. 예를 들어, JMock1의 제약조건은 Hamcrest의 Matcher이다. Hamcrest는 JMock 어댑터를 제공하여 JMock1 테스트에서 Hamcrest Matcher를 사용할 수 있다. JMock2은 Hamcrest를 매칭가능한 라이브러리로 사용하도록 설계되었으므로 이러한 어댑터 계층이 필요하지 않다.
Hamcrest는 EasyMock용 어댑터도 제공한다. 자세한 내용은 Hamcrest examples을 참조하면 된다.

A tour of common matchers

Hamcrest에는 유용한 매처 라이브러리가 제공된다.
가장 중요한 것들은 아래와 같다.

Core

🔎anything - 항상 일치하며, 테스트 중인 개체가 어떤 것이든 상관 없는 경우에 유용하다.

🔎describedAs - 사용자가 직접 테스트 실패에 대한 설명을 추가하는 decorator

🔎is - 가독성을 높이기 위한 decorator
(아래 “Sugar” 참조)

Logical

🔎allOf - 모든 matcher가 true를 반환하면 통과 (like Java &&)

🔎anyOf - 적어도 하나의 matcher가 true를 반환하면 통과 (like Java ||)

🔎not - 랩핑된 matcher가 false를 반환하면 통과

Object

🔎equalTo - Object.equals을 사용해 객체가 동일한지 판단한다.

🔎hasToString - Object.toString 메소드 값과 일치 여부를 판별한다.

🔎instanceOf, isCompatibleType - 동일 인스턴스인지 타입 비교

🔎notNullValue, nullValue - Null인지 아닌지 판별

🔎sameInstance - Object가 완전히 동일한지 비교. equals비교 X 주소비교 (==)

Beans

🔎hasProperty - JavaBeans properties 테스트, 해당 property를 가지고 있는지 판단

import static org.hamcrest.Matchers.hasProperty;
import static org.junit.Assert.assertThat;

// hasProperty 용
private String myProperty;
     
public void  setMyProperty(String property) {
    this.property = property;
}
  
// 위에 myProperty가 있으므로 성공
@Test
public void propertyTest() {
    assertThat(this, hasProperty("myProperty"));
}
//출처 : https://m.blog.naver.com/PostView.nhn?blogId=simpolor&logNo=221289242597&proxyReferer=https%3A%2F%2Fwww.google.com%2F

Collections

🔎array - 두 배열 내의 요소가 모두 일치하는지 판별

🔎hasEntry, hasKey, hasValue - Map요소에 대한 포함여부 판단

🔎hasItem, hasItems - 특정 요소를 포함한 컬렉션인지 판단

🔎hasItemInArray - 특정 요소를 포함한 배열인지 판단

Number

🔎closeTo - 부동 소수점 값이 주어진 값에 가까운지 테스트 (값과 오차를 인자로 받는다)

🔎greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo - 값 비교, 차례대로 >, >=, <, <=

Text

🔎equalToIgnoringCase - 대소문자 구분 없이 동일한 문자열인지 판단

🔎equalToIgnoringWhiteSpace - 문자열 내 모든 공백 제거 후 동일한 문자열인지 판단

🔎containsString, endsWith, startsWith - 특정 문자열을 포함하는가, 특정 문자열로 종료, 특정 문자열로 시작

Sugar

Hamcrest는 테스트의 가독성을 좋게 하기 위해 노력한다. 예를 들어 is matcher는 기본 matcher에 기본 동작을 추가하지 않는 wrapper일 뿐이다.
다음 assertion은 동일하다.

assertThat(theBiscuit, equalTo(myBiscuit)); 
assertThat(theBiscuit, is(equalTo(myBiscuit))); 
assertThat(theBiscuit, is(myBiscuit));

Writing custom matchers

Hamcrest에는 유용한 matcher가 번들로 제공되지만 테스트 요구사항에 맞게 때때로 적절한 matcher를 생성해야 할 때가 있다.
이는 동일한 속성들을 하나의 묶음으로 반복해서 여러 테스트 코드에서 테스팅 중인 것을 발견하고, 이를 하나의 assertion으로 묶으려하는 경우가 될 수 있다.
👍커스텀 matcher를 사용하면 코드 중복을 제거하고 테스트의 가독성을 증가시킬 수 있다.

한 예로써 값이 NaN(숫자가 아님)인지 테스트 하기 위한 커스텀 matcher를 생성해보자.
아래는 수행하고자 하는 테스트 코드이다.

@Test
public void testSquareRootOfMinusOneIsNotANumber() { 
  assertThat(Math.sqrt(-1), is(notANumber())); 
}

notANumber() 메소드를 포함하는 커스텀 matcher IsNotANumber는 아래와 같다.

package org.hamcrest.examples.tutorial;

import org.hamcrest.Description; 
import org.hamcrest.Matcher; 
import org.hamcrest.TypeSafeMatcher;

public class IsNotANumber extends TypeSafeMatcher {

  @Override 
  public boolean matchesSafely(Double number) { 
    return number.isNaN(); 
  }

  public void describeTo(Description description) { 
    description.appendText("not a number"); 
  }

  public static Matcher notANumber() { 
    return new IsNotANumber(); 
  }

} 

assertThat method는 assertion 하고자 하는 유형에 따라 매개 변수로써 Matcher를 사용하는 generic method이다.


👉assertThat에서는 notANumber()메소드를 호출하지만, 실제 메소드는 IsNotANumber의 생성자를 호출하고 있다. 오버라이딩된 matchesSafely 메소드가 실제로 해당 값이 NaN인지 판단하지만 이는 숨겨져있다.


우리는 (matchesSafely 메소드에서 알 수 있듯이) Double 값을 검증하고자 하므로 Matcher의 generic 변수가 Double로 정의되어야 함을 알 수 있다.
📌📌Matcher를 구현할 때 서브클래스인 TypeSafeMatcher를 사용하는 것이 가장 편리하다. 그리고 매개변수로 받은 Double이 NaN인지 판별하는 matchesSafely와 테스트가 실패한 경우 실패 메세지를 생성하는데 사용되는 describeTo 메소드만 구현하면 된다.

아래는 실패 메세지가 표시되는 예시이다.

assertThat(1.0, is(notANumber()));

// fails with the message

java.lang.AssertionError: Expected: is not a number got : <1.0>

커스텀 matcher의 세 번째 method는 (편리한) factory method이다. 테스트에서 matcher를 사용하기 위해 이 메소드(notANumber)를 import static으로 가져온다.

import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat; 
import static org.hamcrest.Matchers.*;
import static org.hamcrest.examples.tutorial.IsNotANumber.notANumber;

public class NumberTest {
  @Test
  public void testSquareRootOfMinusOneIsNotANumber() { 
    assertThat(Math.sqrt(-1), is(notANumber())); 
  } 
} 

notANumber 메소드가 호출될 때마다 새 matcher가 만들어지지만 이것이 matcher의 유일한 사용 패턴이라고 가정하면 안된다.(matcher가 싱글톤 객체로서 존재하고, 재활용될 수 있음을 암시) 따라서 matcher가 stateless인지 확인하고 매칭 사이에서 단일 인스턴스를 재사용할 수 있다.


해석이 잘 안돼서 박제 🤔
Even though the notANumber method creates a new matcher each time it is called, you should not assume this is the only usage pattern for your matcher. Therefore you should make sure your matcher is stateless, so a single instance can be reused between matches.


API

http://hamcrest.org/JavaHamcrest/javadoc/2.2/

profile
🌱 😈💻 🌱

0개의 댓글