AssertJ 필수 기능 정리 (JAVA)
안녕하세요? 코딩중독입니다.
JUnit가 같이 쓰기 좋은 AssertJ의 필수 기능들에 대해 소개하려고 합니다. 공식 문서는 이곳에서 참고하실 수 있으며, 틀리거나 미흡한 부분은 지적해 주시면 감사하겠습니다.
AssertJ란?
자바 테스트를 위해 좀 더 풍부한 문법을 제공하고 메서드 체이닝을 통해 직관적인 테스트 흐름을 작성할 수 있도록 개발된 오픈소스 라이브러리입니다.
간단한 예제
@Test
public void split() {
String[] values = "1,2".split(",");
assertThat(values).containsExactly("1", "2");
values = "1".split(",");
assertThat(values).containsExactly("1");
}
JUnit5의 경우, assertEquals(expected, actual)과 같이 두 개의 인자를 받아서 비교를 하지만, AssertJ는 메소드 체이닝을 통해 가독성을 높여주는 특징이 있습니다. assertEquals()는 왼쪽이 expected인지 actual인지 혼동될 여지가 있지만, assertThat()은 actual 인자 하나만 요구하고 그 뒤로 메소드 체이닝을 하므로 actual과 expected를 명확하게 구분지어준다는 장점이 있는 것이죠.
Test Fail Message
JUnit5의 경우, 마지막 인자값에 선택적으로 메시지를 넣어줌으로써 테스트 실패 메시지를 명시할 수 있는데, AssertJ에서는 as()를 호출하여 사용합니다. 단, assertion이 수행되기 전에 사용해야합니다.
TolkienCharacter frodo = new TolkienCharacter("Frodo", 33, HOBBIT);
// failing assertion, remember to call as() before the assertion, not after !
assertThat(frodo.getAge()).as("check %s's age", frodo.getName()).isEqualTo(100);
만약, frodo의 나이가 100과 같지 않다면 "check 33's age"와 같은 오류 메시지를 출력하는 것입니다.
Filtering assertions
import static org.assertj.core.api.Assertions.in;
import static org.assertj.core.api.Assertions.not;
import static org.assertj.core.api.Assertions.notIn;
...
// filters use introspection to get property/field values
assertThat(fellowshipOfTheRing).filteredOn("race", HOBBIT)
.containsOnly(sam, frodo, pippin, merry);
// nested properties are supported
assertThat(fellowshipOfTheRing).filteredOn("race.name", "Man")
.containsOnly(aragorn, boromir);
// you can apply different comparison
assertThat(fellowshipOfTheRing).filteredOn("race", notIn(HOBBIT, MAN))
.containsOnly(gandalf, gimli, legolas);
assertThat(fellowshipOfTheRing).filteredOn("race", in(MAIA, MAN))
.containsOnly(gandalf, boromir, aragorn);
assertThat(fellowshipOfTheRing).filteredOn("race", not(HOBBIT))
.containsOnly(gandalf, boromir, aragorn, gimli, legolas);
// you can chain multiple filter criteria
assertThat(fellowshipOfTheRing).filteredOn("race", MAN)
.filteredOn("name", not("Boromir"))
.containsOnly(aragorn);
예제 코드에서는 단순히 인자의 정보를 갖는 객체만 필터링하고 있지만, 람다식을 사용하여 필터링을 해도 됩니다.
Assertions on extracted properties/fields of iterable/array elements
만약, 이름과 나이 정보 등이 있는 특정 객체를 담는 리스트가 있다고 가정해 봅시다. 그리고 특정 객체의 이름을 테스트한다고 하면, 우리는 아래와 같이 이름만 담는 리스트를 따로 빼 내서 테스트를 할 것입니다.
// extract the names ...
List<String> names = new ArrayList<String>();
for (TolkienCharacter tolkienCharacter : fellowshipOfTheRing) {
names.add(tolkienCharacter.getName());
}
// ... and finally assert something
assertThat(names).contains("Boromir", "Gandalf", "Frodo", "Legolas");
하지만, 이 방법은 효율적이지 못합니다. 대신, extracting()을 사용하여 특정 필드를 추출하여 테스트를 할 수 있습니다.
// "name" needs to be either a property or a field of the TolkienCharacter class
assertThat(fellowshipOfTheRing).extracting("name")
.contains("Boromir", "Gandalf", "Frodo", "Legolas")
.doesNotContain("Sauron", "Elrond");
이외에 extracing()의 기능을 좀 더 살펴보겠습니다. 위 예시처럼 extracting()의 인자가 하나일 경우 좀 더 강하게 타입을 명시해 줄 수 있습니다.
assertThat(fellowshipOfTheRing).extracting("name", String.class)
.contains("Boromir", "Gandalf", "Frodo", "Legolas")
.doesNotContain("Sauron", "Elrond");
이름은 String이므로 해당 클래스의 이름을 명시해주는 것이죠.
만약 여러 필드를 검사하고 싶은 경우, 아래처럼 튜플을 사용하여 테스트가 가능합니다.
import static org.assertj.core.api.Assertions.tuple;
// extracting name, age and and race.name nested property
assertThat(fellowshipOfTheRing).extracting("name", "age", "race.name")
.contains(tuple("Boromir", 37, "Man"),
tuple("Sam", 38, "Hobbit"),
tuple("Legolas", 1000, "Elf"));
Soft assertions
보통 테스트를 진행할 때. 하나의 assertThat()이 실패하면 해당 테스트 자체가 중단이 됩니다. 하지만, Soft assertions을 사용한다면, 모든 assertions을 실행한 후 실패 내역만 확인할 수 있습니다.
@Test
public void host_dinner_party_where_nobody_dies() {
Mansion mansion = new Mansion();
mansion.hostPotentiallyMurderousDinnerParty();
// use SoftAssertions instead of direct assertThat methods
SoftAssertions softly = new SoftAssertions();
softly.assertThat(mansion.guests()).as("Living Guests").isEqualTo(7);
softly.assertThat(mansion.kitchen()).as("Kitchen").isEqualTo("clean");
softly.assertThat(mansion.library()).as("Library").isEqualTo("clean");
softly.assertThat(mansion.revolverAmmo()).as("Revolver Ammo").isEqualTo(6);
softly.assertThat(mansion.candlestick()).as("Candlestick").isEqualTo("pristine");
softly.assertThat(mansion.colonel()).as("Colonel").isEqualTo("well kempt");
softly.assertThat(mansion.professor()).as("Professor").isEqualTo("well kempt");
// Don't forget to call SoftAssertions global verification !
softly.assertAll();
}
사용 방법은 간단합니다. SoftAssertions 객체를 하나 생성한다음 그 객체의 내장 메소드 assertThat()을 이용하여 테스트문을 작성하는 것입니다.
org.assertj.core.api.SoftAssertionError:
The following 4 assertions failed:
1) [Living Guests] expected:<[7]> but was:<[6]>
2) [Library] expected:<'[clean]'> but was:<'[messy]'>
3) [Candlestick] expected:<'[pristine]'> but was:<'[bent]'>
4) [Professor] expected:<'[well kempt]'> but was:<'[bloodied and dishevelled]'>
이런식으로 실패한 assertions에 대해서만 오류 메시지를 띄워주는 것을 알 수 있습니다.
Exception assertions
예외를 테스트하는 방법을 알아보겠습니다.
@ParameterizedTest
@ValueSource(strings = {"", "spring"})
@DisplayName("이름 길이가 0 이하 또는 5 이상일 때 에러 확인")
void car_name_exception(String name) {
assertThatThrownBy(() -> new Car(name))
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining("이름 길이는 0이하 또는 5이상이어야 합니다.");
}
JUnit5의 경우, assertThrows()를 사용하여 첫 번째 인자에는 기대하는 예외 클래스를, 두 번째 인자에는 예외가 발생하는 코드를 작성합니다. 이것도 마찬가지로 인자의 위치가 혼동될 여지가 있는데, AssertJ는 actual을 인자로 받고, 그 다음 기대하는 예외 클래스를 적고, 마지막으로 원하면 에러 메시지를 출력해 줄 수 있습니다.
Custom comparison in assertion
isEqualTo()를 사용하여 해당 객체의 참조가 같을 경우 테스트가 성공합니다. 이때, Comparator을 정의하여 비교 작업을 수행할 수도 있습니다.
@Test
void equalTest() throws Exception{
Item item1 = new Item("item1");
Item item2 = new Item("item1");
// 테스트 실패
// assertThat(item1).isEqualTo(item2);
assertThat(item1).isNotEqualTo(item2);
assertThat(item1)
.usingComparator(new Comparator<Item>() {
@Override
public int compare(Item o1, Item o2) {
return o1.getName().compareTo(o2.getName());
}
})
.isEqualTo(item2);
assertThat(item1)
.usingComparator((a, b) -> a.getName().compareTo(b.getName()))
.isEqualTo(item2);
}
Item 객체 2개를 비교할 때, comparator 없이 비교하면 당연히 두 객체는 다릅니다. 따라서, 일정한 기준을 갖고 비교를 해 주기 위하여 usingComparator()을 사용합니다.
@Test
void equalTest2() throws Exception{
Item item1 = new Item("item1");
Item item2 = new Item("item1");
Item item3 = new Item("item1");
List<Item> itemList = new ArrayList<>();
itemList.add(item1);
itemList.add(item2);
assertThat(itemList).contains(item1, item2).doesNotContain(item3);
assertThat(itemList)
.usingElementComparator((a, b) -> a.getName().compareTo(b.getName()))
.contains(item1, item2, item3);
}
마찬가지로 특정 리스트 안에서 데이터들끼리 비교를 해 줄 수 있는데, 이때 usingElementComparator()을 사용합니다. itemList 안의 item1, item2가 존재하는데, item3의 이름이 item1, item2와 같으므로 테스트는 통과합니다.
정리
지금까지 꼭 알아야 하는 AssertJ 기능을 알아 보았습니다. 최근 활발히 사용되는 문법인만큼 잘 익혀두시면 되겠습니다.
출처
'프로그래밍 언어 > Java' 카테고리의 다른 글
Java Collections Framework(JCF)란? - JCF의 계층 구조와 List (JAVA) (0) | 2021.02.11 |
---|---|
Java Collections Framework(JCF)란? - JCF의 정의와 특징 (JAVA) (0) | 2021.02.10 |
JUnit5 필수 개념 정리 (JAVA) (0) | 2021.02.06 |
Stream이란? - 최종 처리 메소드의 종류와 사용 방법 [3 - groupingBy()] (JAVA) (0) | 2021.01.10 |
Stream이란? - 최종 처리 메소드의 종류와 사용 방법 [2 - collect()] (JAVA) (0) | 2021.01.08 |
댓글