많은 곳에서 JUnit에서 제공하는 공식 기능인 Jupiter가 아닌 AssertJ를 사용하고 있다.
여러 교육과정이나 인강에서도 AssertJ를 권장하는데 그 이유는 무엇일까? 간단히 알아보자.
AssertJ 이란?
테스트에 관련된 많은 기능을 제공하고 메서드 체이닝으로 가독성 높은 테스트 코드 작성을 지원하는 오픈 라이브러리다.
AssertJ Import
AssertJ의 Assertions를 사용 할 때 주의점은 것은 기존 JUnit의 Assertions와 같은 클래스명을 가진다.
import 경로는 org.assertj.core.api 다.
AssertJ 사용방법
assertThat(검증대상) 로 시작하며 메서드 체이닝을 이용하여 검증 메서드를 연쇄적으로 사용할 수 있다.
AssertJ가 제공하는 메서드는 다음과 같이 존재한다.
- 기본 검증 메서드
isEqualTo(값) | 검증대상과 동일한 값인지 비교한다. |
isSameAs(값) | 검증대상과 값을 ==로 비교한다. |
isNotNull() | 검증대상이 Not Null인지 확인한다. |
isNull() | 검증대상이 Null인지 확인한다. |
isNotEmpty() | 검증대상이 Not Empty인지 확인한다. |
isEmpty() | 검증대상이 Empty인지 확인한다. |
@Test
void name() {
String name1 = "loop";
String name2 = "loop";
assertThat(name1).isNotNull()
.isEqualTo(name2);
}
- 문자열 검증 메서드
contains(값) | 검증대상에 값이 포함되어있는지 확인한다. |
startsWith(값) | 검증대상이 시작값이 값과 동일한지 비교한다. |
endWith(값) | 검증대상의 마지막값이 값과 동일한지 비교한다. |
@Test
void name() {
String name1 = "loop";
assertThat(name1).contains("o")
.startsWith("l")
.endsWith("p");
}
- 숫자 검증 메서드
isPositive() | 검증대상이 양수인지 확인한다. |
isNegative() | 검증대상이 음수인지 확인한다. |
isZero() | 검증대상이 0인지 확인한다. |
isGreaterThan(값) | 검증대상이 값을 초과한지 확인하다. |
isLessThan(값) | 검증대상이 값보다 미만인지 확인한다. |
isGreaterThanOrEqualTo(값) | 검증대상이 값 이상인지 확인한다. |
isLessThanOrEqualTo(값) | 검증대상이 값 이하인지 확인한다. |
- 필터링 기능(filteredOn)
iterables, arrays의 값을 필터하는 filteredOn 메서드도 있다.
람다식을 사용하거나 프로퍼티에 접근해서 값을 검증할 수 있다. (filteredOnNull, filteredOnAssertions 도 있다.)
@Test
void name() {
List<String> names = new ArrayList<>();
names.add("loop");
names.add("study");
names.add("java");
names.add("spring");
assertThat(names).filteredOn(name -> name.contains("loop"))
.hasSize(1);
}
람다식을 사용한 예시다.
public class AssertJTest {
...
@Test
void car() {
List<RacingCar> cars = new ArrayList<>();
RacingCar loop = new RacingCar("loop", 100);
RacingCar study = new RacingCar("study", 150);
RacingCar java = new RacingCar("java", 200);
RacingCar spring = new RacingCar("spring", 250);
cars.add(loop);
cars.add(study);
cars.add(java);
cars.add(spring);
assertThat(cars).filteredOn("maxSpeed", 100)
.containsOnly(loop);
assertThat(cars).filteredOn("name", not("loop"))
.containsOnly(study, java, spring);
assertThat(cars).filteredOn("name", in("loop"))
.containsOnly(loop);
assertThat(cars).filteredOn("maxSpeed", notIn(250))
.containsOnly(loop, study, java);
}
}
class RacingCar {
private String name;
private int maxSpeed;
public RacingCar(String name, int maxSpeed) {
this.name = name;
this.maxSpeed = maxSpeed;
}
}
프로퍼티 접근 예제이며, 여기서 프로퍼티 접근을 보조해주는 not, in, notIn 메서드도 있다.
- 프로퍼티 추출(extracting)
리스트에 담긴 객체의 프로퍼티를 추출해야 할 경우엔 extracting()를 사용하면 된다.
@Test
void extracting() {
List<RacingCar> cars = new ArrayList<>();
RacingCar loop = new RacingCar("loop", 100);
RacingCar study = new RacingCar("study", 150);
RacingCar java = new RacingCar("java", 200);
RacingCar spring = new RacingCar("spring", 250);
cars.add(loop);
cars.add(study);
cars.add(java);
cars.add(spring);
assertThat(cars).extracting("name")
.hasSize(4)
.contains("loop", "study", "java", "spring");
}
간단한 사용예시다. 그 외에도 extracting은 클래스를 명시하거나 여러 필드를 추출하여 사용할 수 있다.
@Test
void extracting() {
...
assertThat(cars).extracting("name", String.class)
.contains("loop", "study", "java", "spring");
assertThat(cars).extracting("name", "maxSpeed")
.contains(tuple("loop", 100),
tuple("study", 150),
tuple("java", 200),
tuple("spring", 250));
}
- 예외처리
AssertJ에서 제공하는 예외처리는 다음과 같다.
...
@Test
void exceptionTest() {
assertThatThrownBy(() -> new RacingCar(null, 100));
assertThatThrownBy(() -> new RacingCar(null, 100))
.isInstanceOf(NullPointerException.class)
.hasMessage("널이다");
assertThatNullPointerException()
.isThrownBy(() -> new RacingCar(null, 100));
assertThatIllegalArgumentException()
.isThrownBy(() -> new RacingCar("abc", 100));
}
}
class RacingCar {
private String name;
private int maxSpeed;
public RacingCar(String name, int maxSpeed) {
if (name == null) {
throw new NullPointerException("널이다");
}
if (name.length() < 4) {
throw new IllegalArgumentException();
}
this.name = name;
this.maxSpeed = maxSpeed;
}
}
assertThatThrownBy는 예외 발생 여부를 확인한다.
특정 예외를 확인하려면 isInstanceOf(Class)를 통해 예외를 지정한다.
예외 메시지를 검증하는 경우엔 hasMessage(메시지값)를 통해 확인한다.
자주 발생하는 예외인 NullPointerException, IllegalArgumentException, IllegalStateException 경우
assertThatNullPointerException(), assertThatIllegalArgumentException(), assertThatIllegalStateException()를 제공해준다.
assertThatThrownBy() | 예외 발생을 확인한다. |
assertThatNullPointerException() | NullPointerException가 발생한지 확인한다. |
assertThatIllegalArgumentException() | IllegalArgumentException가 발생한지 확인한다. |
assertThatIllegalStateException() | IllegalStateExceptio가 발생한지 확인한다. |
* 이외에도 다양한 기능이 제공되고 있으며, 공식가이드를 참고해보자.
이전에 사용했던 Jupiter & hamcrest 를 간단히 알아보자.
Jupiter와 hamcrest에 대해 알아보자.
- Jupiter 방식
JUnit의 공식가이드를 참고해보면 테스트 검증하는 방법이 다음과 같은 형식으로 되어있다.
public static void assertEquals(Object expected, Object actual)
public static void assertTrue(boolean condition)
...
가이드를 기반으로 예제를 만들어보자.
@Test
@DisplayName("a, b 값 일치 여부 확인")
void junit_jupiter_assertions() {
String a = "이름";
String b = "이름";
assertEquals(a, b);
assertTrue(a.contains("이"));
}
- Jupiter 단점
잠깐이라도 사용해보면 Jupiter의 표현력 부족이 보인다.
공식 가이드에서 제공하는 메서드를 보면 단순하다.
기대값과 실제값을 넣고 일치하는지, 아니면 조건이 True 인지 False만 판단하는 기능만 구성되어있다.
무엇을 검사하는지 파악하기 힘들다.
검증이 많은 이메일 유효성 경우엔 어떻게 해야할까?
private String domainName;
private int idMaxLength;
@BeforeEach
void setUp() {
domainName = "gmail.com";
idMaxLength = 20;
}
@Test
@DisplayName("이메일 유효성 확인")
void junit_jupiter_assertions() {
String actual = "loop-study@gmail.com";
String id = actual.substring(0, actual.indexOf("@"));
assertNotNull(actual); // not null 확인
assertTrue(actual.contains("@")); // @ 포함 확인
assertTrue(actual.contains(".")); // . 포함 확인
assertTrue(actual.contains(domainName)); // gmail 확인
assertTrue(id.length() <= idMaxLength); // 길이 확인
}
이메일 검사 항목마다 검증하는 함수를 호출하거나 함수 내에 길이를 비교하는 것처럼 직접 연산해야 할 경우도 생긴다.
위의 예제를 보면 주석으로 뭘 검사하는지 적어둘 정도로 가독성이 떨어진다.
- Jupiter를 보완하는 Hamcrest
이런 단점을 보완하는 Hamcrest 프레임워크가 있다.
Hamcrest는 Matcher 라이브러리로 표현식을 자연스럽고 가독성 있게 만들어준다.
import static org.hamcrest.Matchers.anything;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
public JupiterTest {
private String domainName;
private int idMaxLength;
@BeforeEach
void setUp() {
domainName = "gmail.com";
idMaxLength = 20;
}
@Test
@DisplayName("이메일 유효성 확인")
void junit_jupiter_assertions() {
String actual = "loop-study@gmail.com";
String id = actual.substring(0, actual.indexOf("@"));
assertThat(actual, not(actual)); // not null 확인
assertThat(actual, containsString("@")); // @ 포함 확인
assertThat(actual, containsString(".")); // . 포함 확인
assertThat(actual, containsString(domainName)); // gmail 확인
assertThat(id.length(), lessThanOrEqualTo(idMaxLength)); // 길이 확인
}
}
사용 방법은 Jupiter의 assertThat()과 조합하여 사용하며, 이전보다 표현이 좋아진 걸 알 수 있다.
하지만 메서드 체이닝이 없어서 AssertJ에 비해 사용하기 불편하다.
지금까지 설명한 검증은 행위가 끝나고 난 뒤 상태를 확인하는 방법이다.
상태가 아니라 행위를 검증해야할 경우는 어떻게 해야할까?
다음은 행위를 테스트하는 방법에 대해 포스팅하겠다.
* 참고자료
[AssertJ] JUnit과 같이 쓰기 좋은 AssertJ 필수 부분 정리
공식가이드
부록 D. AssertJ 소개
'개발 & 방법론 > TDD' 카테고리의 다른 글
Mockito 알아보기 (부제 : BDD) (1) | 2021.12.06 |
---|---|
JUnit5 알아보기 (0) | 2021.10.26 |
테스트 코드를 작성하는 이유 (0) | 2021.03.03 |
테스트코드 첫걸음 (0) | 2021.02.15 |