관리 메뉴

개발그래머

AssertJ 라이브러리 메서드 정리 본문

Unit Test

AssertJ 라이브러리 메서드 정리

임요환 2023. 8. 23. 22:33

목표

  • 최근 테스트 코드를 작성하면서 여러 라이브러리들의 assert가 있어서 어떤 게 어떤 라이브러리에 속한 것이고 어떤 게 어떤 부분인지 헷갈렸다.
  • 그래서 AssertJ와 Hamcrest중 그래도 많이 사용하는 AssertJ에 대해 정리하고 넘어가려고 한다.
  • 정리할 내용은 https://assertj.github.io/doc/#assertj-core-simple-example 공식 문서를 기반으로 정리할 것이다.
  • 테스트 코드는 예전 객체지향과 사실과 오해라는 책을 공부하면서 만든 엘리스, 트럼프 클래스에 대해 테스트 코드를 작성해 볼 것이다.

AssertJ Core

  • AssertJ Core 라이브러리는 자바 8 이상의 버전을 요구한다
  • 스프링부트에서는 자동적으로 AssertJ Core 라이브러리를 spirng-boot-starter-test에 포함되어있다
//maven
<properties>
<assertj.version>3.24.2</assertj.version>
</properties>

//gradle  
ext['assertj.version'] = '3.24.2'
  • 위와 같이 AssertJ Core의 버전을 조정할 수 있다
// all  
import static org.assertj.core.api.Assertions.*;

// or  
import static org.assertj.core.api.Assertions.assertThat; // main one  
import static org.assertj.core.api.Assertions.atIndex; // for List assertions  
import static org.assertj.core.api.Assertions.entry; // for Map assertions  
import static org.assertj.core.api.Assertions.tuple; // when extracting several properties at once  
import static org.assertj.core.api.Assertions.fail; // use when writing exception tests  
import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; // idem  
import static org.assertj.core.api.Assertions.filter; // for Iterable/Array assertions  
import static org.assertj.core.api.Assertions.offset; // for floating number assertions  
import static org.assertj.core.api.Assertions.anyOf; // use with Condition  
import static org.assertj.core.api.Assertions.contentOf; // use with File assertions
  • AssertJ import 하는 법
assertThat(alice.getHeight()).isEqualTo(height);  
then(alice.getHeight()).isEqualTo(height);
  • BDD style의 assertions도 사용할 수 있다.
  • assertThat 대신 then을 사용함

as() = assertion에 설명을 추가함

assertThat(alice.getHeight()).as("앨리스의 키").isEqualTo(11);
  • as()를 사용하여 해당 단언(assertion)에 설명을 줄 수 있다.

overridingErrorMessage() or withFailMessage()

Alice alice = new Alice(height, passage);  
Alice ali = new Alice(150, Place.BEAUTIFUL_GARDEN);

assertThat(alice.getHeight()).withFailMessage("should be %s", alice).isEqualTo(ali);  
assertThat(alice.getHeight()).overridingErrorMessage("should be %s", alice).isEqualTo(ali);
  • AssertJ는 유용한 오류 메시지를 주기 위해 노력하지만 overridingErrorMessage() 또는 withFailMessage()로 변경할 수 있음
assertThat(alice.getPlace() == Place.BEAUTIFUL_GARDEN).withFailMessage(() -> "expecting place is beautiful garden").isTrue();  
assertThat(alice.getPlace() == Place.BEAUTIFUL_GARDEN).overridingErrorMessage(() -> "expecting place is beautiful garden").isTrue();
  • 오류메시지를 작성하는데 비용이 많이 드는 경우 String 대신 Supplier<String>을 사용하면 단언(assertion)이 실패한 경우에만 메시지가 작성됨

avoiding incorrect usage(잘못된 사용 피하기)

// Bad case  
assertThat(actual.equals(expected));

// Good case  
assertThat(actual).isEqualTo(expected);  
assertThat(actual.equals(expected)).isTrue();

// Bad case  
assertThat(1 == 2);

// Good case  
assertThat(1).isEqualTo(2);  
assertThat(1 == 2).isTrue();
  • 단언을 호출하는 것을 잊으면 안 된다
  • assertThat()에 전달만 하고 나중에 단언을 호출하는 것을 잊는 것임
// Bad case  
assertThat(actual).isEqualTo(expected).as("description");  
assertThat(actual).isEqualTo(expected).describedAs("description");

// Good case  
assertThat(actual).as("description").isEqualTo(expected);  
assertThat(actual).describedAs("description").isEqualTo(expected);
  • as()를 단언 이후에 하면 안 된다.
// Bad case  
assertThat(actual).isEqualTo(expected).overridingErrorMessage("custom error message");  
assertThat(actual).isEqualTo(expected).withFailMessage("custom error message");

// Good case  
assertThat(actual).overridingErrorMessage("custom error message").isEqualTo(expected);  
assertThat(actual).withFailMessage("custom error message").isEqualTo(expected);
  • overridingErrorMessage()와 withFailMessage()를 단언 이후에 하면 안 된다.
// Bad case  
assertThat(actual).isEqualTo(expected).usingComparator(new CustomComparator());

// Good case  
assertThat(actual).usingComparator(new CustomComparator()).isEqualTo("a");
  • comparator를 세팅하는 것을 단언 이후에 하면 안 된다.

Common assertions(공통 검증)

https://www.javadoc.io/static/org.assertj/assertj-core/3.24.2/org/assertj/core/api/AbstractAssert.html#method.summary

주요 메서드

isEqualTo : 객체나 값이 주어진 값과 동일한지 확인(equals() 비교)

@Test
void isEqualTo() {
    int height = 160;
    Place passage = Place.PASSAGE;
    Alice alice = new Alice(height, passage);

    assertThat(height).isEqualTo(160);
    assertThat(passage).isEqualTo(Place.PASSAGE);
}

isNotEqualTo : 객체나 값이 주어진 값과 다른지 확인

@Test
void isNotEqualTo() {
    int height = 160;
    Place passage = Place.PASSAGE;
    Alice alice = new Alice(height, passage);

    assertThat(height).isNotEqualTo(11);
    assertThat(passage).isNotEqualTo(Place.BEAUTIFUL_GARDEN);
}

isNull : 객체가 null인지 확인

@Test
void isNull() {
    int height = 160;
    Place passage = Place.PASSAGE;
    Alice alice = new Alice(height, passage);

    assertThat(alice.getKey()).isNull();
}

isNotNull : 객체가 null이 아닌지 확인

@Test
void isNotNull() {
    int height = 160;
    Place passage = Place.PASSAGE;
    Alice alice = new Alice(height, passage);
    alice.get(new Key(30));

    assertThat(alice.getKey()).isNotNull();
}

isSameAs : 객체가 주어진 객체와 동일한 객체인지 확인(== 비교)

@Test
void isSameAs() {
    int height = 160;
    Place passage = Place.PASSAGE;
    Alice alice = new Alice(height, passage);
    Alice alice2 = alice;

    //Verifies that the actual value is the same as the given one, ie using == comparison.
    assertThat(alice).isSameAs(alice2);
}

isNotSameAs : 객체가 주어진 객체와 다른 객체인지 확인

@Test
void isNotSameAs() {
    int height = 160;
    Place passage = Place.PASSAGE;
    Alice alice = new Alice(height, passage);
    Alice clone = new Alice(height, passage);

    assertThat(alice).isNotSameAs(clone);
}

isInstanceOf : 객체가 주어진 클래스의 인스턴스인지 확인

@Test
void isInstanceOf() {
    int height = 160;
    Place passage = Place.PASSAGE;
    Alice alice = new Alice(height, passage);

    assertThat(alice).isInstanceOf(Alice.class);
}

Object assertions(객체 검증)

https://www.javadoc.io/static/org.assertj/assertj-core/3.24.2/org/assertj/core/api/AbstractObjectAssert.html#method.summary

주요 메서드

hasFieldOrProperty : 실제 객체에 지정된 필드 또는 속성이 있는지 확인

@Test
void hasFieldOrProperty() {
    int height = 160;
    Place passage = Place.PASSAGE;
    Alice alice = new Alice(height, passage);

    assertThat(alice).hasFieldOrProperty("height");
    assertThat(alice).hasFieldOrProperty("place");
}

hasFieldOrPropertyWithValue : 실제 객체에 지정된 값을 가진 지정된 필드 또는 속성이 있음을 확인

@Test
void hasFieldOrPropertyWithValue() {
    int height = 160;
    Place passage = Place.PASSAGE;
    Alice alice = new Alice(height, passage);

    assertThat(alice).hasFieldOrPropertyWithValue("height", height);
    assertThat(alice).hasFieldOrPropertyWithValue("place", passage);
}

extracting : 테스트 중인 객체에서 지정된 필드, 속성의 값을 목록으로 추출하며 이 새 목록은 테스트 중인 객체가 됨

@Test
void extracting() {
    int height = 160;
    Place passage = Place.PASSAGE;
    Alice alice = new Alice(height, passage);

    assertThat(alice).extracting("height", "place")
            .containsExactly(height, passage);
}

String/CharSequence assertions(문자열 검증)

https://www.javadoc.io/static/org.assertj/assertj-core/3.24.2/org/assertj/core/api/AbstractCharSequenceAssert.html#method.summary

주요 메서드

startsWith : 문자열이 주어진 접두사로 시작하는지 확인

@Test
void startsWith() {
    String name = "Alice";

    assertThat(name).startsWith("Ali");
}

endsWith : 문자열이 주어진 접미사로 끝나는지 확인

@Test
void endsWith() {
    String name = "Alice";

    assertThat(name).endsWith("ce");
}

contains : 문자열이 주어진 부분 문자열을 포함하는지 확인

@Test
void contains() {
    String name = "Alice";

    assertThat(name).contains("ic");
}

matches : 문자열이 정규 표현식과 일치하는지 확인

@Test
void matches() {
    String name = "Alice";

    assertThat(name).matches(".....");
}

Iterable and array assertions(컬렉션과 배열 검증)

https://www.javadoc.io/doc/org.assertj/assertj-core/latest/org/assertj/core/api/AbstractIterableAssert.html#method.summary
https://www.javadoc.io/doc/org.assertj/assertj-core/latest/org/assertj/core/api/AbstractObjectArrayAssert.html#method.summary

주요 메서드

contains : 컬렉션이 주어진 요소를 포함하는지 확인

@Test
void collectionContains() {
    TrumpShape[] shapes = TrumpShape.values();
    List<TrumpShape> shapeList = Arrays.stream(shapes).collect(Collectors.toList());

    assertThat(shapes).contains(TrumpShape.CLOVER);
    assertThat(shapeList).contains(TrumpShape.CLOVER);
}

containsExactly : 컬렉션이 주어진 요소를 정확히 순서대로 포함하는지 확인

@Test
void containsExactly() {
    TrumpShape[] shapes = TrumpShape.values();
    List<TrumpShape> shapeList = Arrays.stream(shapes).collect(Collectors.toList());

    assertThat(shapes).containsExactly(TrumpShape.SPADE, TrumpShape.HEART, TrumpShape.DIAMOND, TrumpShape.CLOVER);
    assertThat(shapeList).elements(0,1,2,3).contains(TrumpShape.SPADE, TrumpShape.HEART, TrumpShape.DIAMOND, TrumpShape.CLOVER);
}

hasSize : 컬렉션이 주어진 크기와 일치하는지 확인

@Test
void hasSize() {
    TrumpShape[] shapes = TrumpShape.values();
    List<TrumpShape> shapeList = Arrays.stream(shapes).collect(Collectors.toList());

    assertThat(shapes).hasSize(shapes.length);
    assertThat(shapeList).hasSize(shapeList.size());
}

isEmpty : 컬렉션이 비어 있는지 확인

@Test
void isEmpty() {
    List<Object> emptyList = List.of();
    TrumpShape[] shapes = TrumpShape.values();
    List<TrumpShape> shapeList = Arrays.stream(shapes).collect(Collectors.toList());

    assertThat(emptyList).isEmpty();
    assertThat(shapes).isNotEmpty();
    assertThat(shapeList).isNotEmpty();
}

Exception assertions(예외 검증)

https://www.javadoc.io/doc/org.assertj/assertj-core/latest/org/assertj/core/api/AbstractThrowableAssert.html#method.summary

주요 메서드

isInstanceOf: 코드 실행 시 발생한 예외가 주어진 예외 클래스의 인스턴스인지 확인

@Test
void exceptionIsInstanceOf() {
    int height = 160;
    Place passage = Place.PASSAGE;
    Alice alice = new Alice(height, passage);

    Throwable thrown = catchThrowable(() -> alice.pass(new Door(120)));
    assertThat(thrown).isInstanceOf(IllegalStateException.class);

    assertThatExceptionOfType(IllegalStateException.class)
            .isThrownBy(() -> alice.pass(new Door(120)))
            .isInstanceOf(IllegalStateException.class);
}

hasMessage: 예외의 메시지가 주어진 문자열과 일치하는지 확인

@Test
void hasMessage() {
    int height = 160;
    Place passage = Place.PASSAGE;
    Alice alice = new Alice(height, passage);

    Throwable thrown = catchThrowable(() -> alice.pass(new Door(120)));
    assertThat(thrown).hasMessage("키가 40보다 작아야합니다.");
}