프로그래밍 언어/Java

AssertJ 필수 기능 정리 (JAVA)

제이온 (J.ON) 2021. 2. 8.

 

안녕하세요? 코딩중독입니다.

 

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 기능을 알아 보았습니다. 최근 활발히 사용되는 문법인만큼 잘 익혀두시면 되겠습니다.

 

 

출처

 

AssertJ 주요 기능 공부

AssertJ 테스트 코드 작성 시 가독성이 뛰어나고 상세한 에러 메시지를 제공해주는 라이브러리입니다. 지원해주는 대표적인 기능들을 테스트해보면서 살펴보겠습니다. 살펴본 기능에 대한 모든

sun-22.tistory.com

 

 

 

[AssertJ] JUnit과 같이 쓰기 좋은 AssertJ 필수 부분 정리

AssertJ가 core document를 새로운 github.io로 이전했네요 :) . 본 글은 AssertJ 공식 문서를 핵심 챕터를 선정하여 번역하며 정리한 글 입니다. http://joel-costigliola.github.io/assertj/assertj-core.html A..

pjh3749.tistory.com

 

추천 글