본문 바로가기

우테코/Level1

[우테코-Lv1] TDD- AssertJ 라이브러리

목차

isEqualTo()/isNotEqualTo
isNull() / isNotNull()
isSameAs() / isNotSameAs()
assertThatThrownBy().isInstanceOf()
assertThatCode().doesNotThrownAnyException()
asserThat -String관련
asserThat - Collection관련
Satisfy
Match
singleelement()
Filtering
extracting
exception Handling


JUnit 테스트코드를 먼저 학습하기를 권장합니다.

https://hellobrocolli.tistory.com/137

 

[우테코-Level1] JUnit 단위 테스트 공부

JUnit5이란? : 자바 언어를 사용하는 소프트웨어 개발자를 위한 테스트 프레임 워크 : 주로 단위 테스트를 작성하고 실행하는 데에 쓰인다. 참고 사이트 요약된 표 JUnit 테스트 프레임 워크 @Test 해

hellobrocolli.tistory.com

AssertJ란?

: 테스트 코드를 더 편리하게 작성하게 도와주는 라이브러리

: JUnit과 함께 사용가능

 


isEqualTo() 

: 두 값이 같은지 비교

: assertEquals(expected, actual)과 비슷한 기능

 

@Test
@DisplayName("isEqualTo 메서드로 두 값이 같은지 비교한다")
void isEqualTo_메서드로_두_값이_같은지_비교한다() {
    final var a = 1;
    final var b = 2;
    final var actual = a + b;

    final var expected = 3;

    // TODO: JUnit5의 assertEquals 메서드를 AssertJ의 isEqualTo 메서드로 변경해보세요.
    assertThat(expected).isEqualTo(actual);
}

isNotEqualTo()

: 두 객체의 값이 다른지 비교할 때 사용

: assertNotEquals() 메서드와 비슷한 기능

@Test
@DisplayName("isNotEqualTo 메서드로 두 값이 다른지 비교한다")
void isNotEqualTo_메서드로_두_값이_다른지_비교한다() {
    final var a = 1;
    final var b = 2;
    final var actual = a + b;

    final var expected = 4;

    // TODO: JUnit5의 assertNotEquals 메서드를 AssertJ의 isNotEqualTo 메서드로 변경해보세요.
    assertThat(expected).isNotEqualTo(actual);
}

 


isNULL()

 : 객체가 Null인지 비교

JUnit : assertNull()

Test
@DisplayName("isNull 메서드로 객체가 null인지 비교한다")
void isNull_메서드로_객체가_null인지_비교한다() {
    final Object actual = null;

    // TODO: JUnit5의 assertNull 메서드를 AssertJ의 isNull 메서드로 변경해보세요.
    assertThat(actual).isNull();
}

isNotNull()

: 객체가 null이 아닌지 확인

JUnit : assertNotNull()

@Test
@DisplayName("isNotNull 메서드로 객체가 null이 아닌지 비교한다")
void isNotNull_메서드로_객체가_null이_아닌지_비교한다() {
    final Object actual = new Object();

    // TODO: JUnit5의 assertNotNull 메서드를 AssertJ의 isNotNull 메서드로 변경해보세요.
    //assertNotNull(actual);
    assertThat(actual).isNotNull();
}

isSameAs()

: 두 객체가 같은 객체인지 비교

JUnit: assertSame()

@Test
@DisplayName("isSameAs 메서드로 두 객체가 같은 객체인지 비교한다")
void isSameAs_메서드로_두_객체가_같은_객체인지_비교한다() {
    final var actual = new Object();
    final var expected = actual;

    // TODO: JUnit5의 assertSame 메서드를 AssertJ의 isSameAs 메서드로 변경해보세요.
    assertSame(actual, expected);
    assertThat(actual).isSameAs(expected);
}

isNotSameAs()

: 두 객체가 같지 않은 객체인지 비교

JUnit: assertNotSame()

 

@Test
@DisplayName("isNotSameAs 메서드로 두 객체가 같은 객체인지 비교한다")
void isNotSameAs_메서드로_두_객체가_같은_객체인지_비교한다() {
    final var actual = new Object();
    final var expected = new Object();

    // TODO: JUnit5의 assertNotSame 메서드를 AssertJ의 isNotSameAs 메서드로 변경해보세요.
    assertNotSame(actual, expected);
    assertThat(actual).isNotSameAs(expected);
}

assertThatThrownBy(ThrowingCallable).isInstanceOf()

: 특정 예외가 발생했는지 비교

JUnit : assertThrows()

@Test
@DisplayName("assertThatThrownBy 메서드로 특정 예외가 발생하는지 비교한다")
void assertThatThrownBy_메서드로_특정_예외가_발생하는지_비교한다() {
    // TODO: JUnit5의 assertThrows 메서드를 AssertJ의 assertThatThrownBy 메서드로 변경해보세요.
    assertThrows(IllegalCallerException.class, () -> {
        causeException();
    });
    
    Assertions.assertThatThrownBy(()-> {
        causeException();
    }).isInstanceOf(IllegalCallerException.class);
}

 

=> assertThrows는 예외 타입만 비교가 가능

=> assertThatThrownBy는 예외 메시지도 비교 가능

Assertions.assertThatThrownBy(this ::causeException)
        .isInstanceOf(IllegalCallerException.class)
        .hasMessage("예외가 발생했습니다.");

assertThatCode(Executable) . doesNotThrowAnyException()

: 특정 코드가 예외를 발생하지 않는지

Junit : assertDoesNotThrow

@Test
@DisplayName("assertThatCode 메서드로 특정 코드가 예외를 발생하지 않는지 비교한다")
void assertThatCode_메서드로_특정_코드가_예외를_발생하지_않는지_비교한다() {
    // TODO: JUnit5의 assertDoesNotThrow 메서드를 AssertJ의 assertThatCode 메서드로 변경해보세요.
    assertDoesNotThrow(() -> {
        final var number = Integer.valueOf(0x80000000);
    });

    assertThatCode(()-> {
        final var number = Integer.valueOf(0x80000000);
    }).doesNotThrowAnyException();
}

assertThat()

: AssertJ는 매개변수로 넘기는 타입에 맞게 메서드를 사용가능

 

 

>> String 관련 메서드

 

-.contains(expected)

: actual에 expected가 들어있는지 비교

@Test
@DisplayName("contains 메서드로 문자열에 특정 문자열이 포함되어 있는지 비교한다")
void contains_메서드로_문자열에_특정_문자열이_포함되어_있는지_비교한다() {
    final var actual = "Hello, world!";
    final var expected = "world";

    // TODO: AssertJ의 contains 메서드를 사용하여 actual에 expected가 포함되어 있는지 비교해보세요.
    assertTrue(actual.contains(expected));
    assertThat(actual).contains(expected);
}

 

-.startsWith(expected)

-.endsWith(expected)

: 특정 문자열로 시작/끝나는지 비교

@Test
@DisplayName("startsWith 메서드로 문자열이 특정 문자열로 시작하는지 비교한다")
void startsWith_메서드로_문자열이_특정_문자열로_시작하는지_비교한다() {
    final var actual = "Hello, world!";
    final var expected = "Hello";

    // TODO: AssertJ의 startsWith 메서드를 사용하여 actual이 expected로 시작하는지 비교해보세요.
    assertTrue(actual.startsWith(expected));
    assertThat(actual).startsWith(expected);
}
@Test
@DisplayName("문자열이 특정 문자열로 끝나는지 비교한다")
void 문자열이_특정_문자열로_끝나는지_비교한다() {
    final var actual = "Hello, world!";
    final var expected = "world!";

    // TODO: AssertJ의 기능을 활용하여 문자열이 특정 문자열로 끝나는지 비교해보세요.
    assertThat(actual).endsWith(expected);
}

 

-.matches(String regex)

: 정규 표현식과 일치하는지 비교

@Test
@DisplayName("문자열이 정규 표현식과 일치하는지 비교한다")
void matches_메서드로_문자열이_정규_표현식과_일치하는지_비교한다() {
    final var actual = "Hello, world!";
    final var expected = "Hello, [a-z]+!";

    // TODO: AssertJ의 기능을 활용하여 문자열이 정규 표현식과 일치하는지 검증해보세요.
    assertThat(actual).matches(expected);
}

>>Collection 관련 메서드 (assertThat(Collection c))

 

- .hasSize(int expected)

: 컬렉션의 사이즈를 체크

@Test
@DisplayName("Collection의 크기를 비교한다")
void Collection의_크기를_비교한다() {
    final var actual = List.of(1, 2, 3);
    final var expected = 3;

    // TODO: AssertJ의 기능을 활용하여 Collection의 크기를 비교해보세요.
    assertEquals(actual.size(), expected);
    assertThat(actual).hasSize(expected);
}

 

- .contains(Object o)

: Collection에 특정 객체가 포함되어 있는지 비교

@Test
@DisplayName("Collection에 특정 객체가 포함되어 있는지 비교한다")
void Collection에_특정_객체가_포함되어_있는지_비교한다() {
    final var actual = List.of(1, 2, 3);
    final var expected = 1;

    // TODO: AssertJ의 기능을 활용하여 Collection에 특정 객체가 포함되어 있는지 비교해보세요.
    assertTrue(actual.contains(expected));
    assertThat(actual).contains(expected);
}

 

- contains와 관한 다양한 메서드들

List<String> list = List.of("1", "1", "2", "3");

// 포함되어 있니
assertThat(list).contains("1", "2");
// 중복된 값도 반영됨
assertThat(list).containsOnly("2","1","3");
// 순서 까지 정확해야함
assertThat(list).containsExactly("1", "1", "2", "3");
// 순서 정확하지 않아도됨
assertThat(list).containsExactlyInAnyOrder("2", "3", "1", "1");
assertThat(list).contains("1").contains("1").containsSequence("2", "3");
// 오직 한번만 있는 값들
assertThat(list).containsOnlyOnce("2", "3");
assertThat(list).containsAnyOf("2");

 

-. extracting(필드이름)

: Collection에 포함된 객체들 중 특정 필드를 추출할 때 사용

- getter가 없이도 Reflection을 사용하여 추출가능

@Test
@DisplayName("extracting 메서드로 Collection에 포함된 객체들 중 특정 필드를 추출한다")
void extracting_메서드로_Collection에_포함된_객체들_중_특정_필드를_추출한다() {
    class User {
        private final String username;
        private final String password;

        User(final String username, final String password) {
            this.username = username;
            this.password = password;
        }

        public String getUsername() {
            return username;
        }
    }

    final var actual = List.of(
            new User("user1", "password1"),
            new User("user2", "password2"),
            new User("user3", "password3")
    );
    final var expected = List.of("user1", "user2", "user3");

    // TODO: AssertJ의 extracting 메서드를 사용하여 actual에 포함된 User 객체들 중 username 필드를 추출하여 expected와 비교해보세요.
    for (int i = 0, end = actual.size(); i < end; i++) {
        assertEquals(actual.get(i).getUsername(), expected.get(i));
    }

   
    assertThat(actual)
            .extracting("username")
            .containsExactlyElementsOf(expected);
}

추가

 

추가적으로 assertj 공식문서에서 괜찮은 기능 몇가지를 정리해보았다.

 

먼저 예제 클래스는 name과 age를 가지는 Hobbit 클래스이다.

public class Hobbit {

  public String name;
  public String age;

  @Override
  public String toString() {
    return format("Hobbit [name=%s, age=%s]", name, age);
  }
}

Satisfy

allSatisfy : 모두 만족하는지 확인

anySatisfy : 하나라도 조건을 만족하는지

noneSatisfy : 만족하는 것이 하나도 없는지

 

public SELF allSatisfy(Consumer<? super ELEMENT> requirements)
=> 매개변수는 있지만 반환값은 없는 람다식 사용 -> ThrowingConsumer를 통해 오류발생 여부 탐색

 

List<TolkienCharacter> hobbits = list(frodo, sam, pippin);

// allSatisfy : 조건을 모두 만족하는지 확인한다
// 조건1. 모든 hobbit들의 종족이 HOBBIT
// 조건2. 이름은 사우론이 아니어야 한다.
assertThat(hobbits).allSatisfy(character -> {
  assertThat(character.getRace()).isEqualTo(HOBBIT);
  assertThat(character.getName()).isNotEqualTo("Sauron");
});

//anySatisfy: 하나라도 만족하는지 확인한다
// 조건1. 종족이 HOBBIT
// 조건2. 이름이 Sam
assertThat(hobbits).anySatisfy(character -> {
  assertThat(character.getRace()).isEqualTo(HOBBIT);
  assertThat(character.getName()).isEqualTo("Sam");
});

//noneSatisfy : 만족하는 것이 하나도 없는지 확인한다.
// 조건1. 종족이 ELF
assertThat(hobbits).noneSatisfy(character -> 
	assertThat(character.getRace()).isEqualTo(ELF));

 

# 만약 allSatisfy가 실패한다면, 실패한 assertion이 출력된다.


Match

allMatch : 조건을 모두 만족하는지 확인한다.

anyMatch : 조건을 하나라도 만족하는지 확인한다.

noneMatch : 조건을 모두 만족하지 않는지 확인한다.

 

List<TolkienCharacter> hobbits = list(frodo, sam, pippin);

//allMatch : 모두 만족해야 할 사안 
//anyMatch : 하나라도 만족해야 할 사안
//noneMatch : 하나도 만족하지 못해야 하는 사안
assertThat(hobbits).allMatch(character -> character.getRace() == HOBBIT, "hobbits") // 뒤에 있는 hobbits는 PredicateDescription
                   .anyMatch(character -> character.getName().contains("pp"))
                   .noneMatch(character -> character.getRace() == ORC);

singleElement() : 오직 하나의 element를 가지는지 확인

 

Iterable<String> babySimpsons = list("Maggie");

// only object assertions available
assertThat(babySimpsons).singleElement()
                        .isEqualTo("Maggie");

 


Filtering

- filteredOn(조건식)

 

- private field를 읽는 것은 default로 가능하다.

- 그러나 불가능하게 만드는 설정도 가능하다

Assertions.setAllowExtractingPrivateFields(false)

 

filter는 기본적으로 조건에 부합하는 요소들을 걸러준다.

assertThat(fellowshipOfTheRing).filteredOn( character -> character.getName().contains("o") )
                               .containsOnly(aragorn, frodo, legolas, boromir);

 

property를 비교할 때 not, in notin등을 사용할 수 있다.

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);

// 내부 클래스 속성도 지원한다
// race.name이 Man인 것들을 필터링
assertThat(fellowshipOfTheRing).filteredOn("race.name", "Man")
                               .containsOnly(aragorn, boromir);

// race가 [HOBBIT, MAN]에 속하지 않는 것
assertThat(fellowshipOfTheRing).filteredOn("race", notIn(HOBBIT, MAN))
                               .containsOnly(gandalf, gimli, legolas);

// race가 [MAIA, MAN]에 속하는 것
assertThat(fellowshipOfTheRing).filteredOn("race", in(MAIA, MAN))
                               .containsOnly(gandalf, boromir, aragorn);

// race가 HOBBIT이 아닌 것
assertThat(fellowshipOfTheRing).filteredOn("race", not(HOBBIT))
                               .containsOnly(gandalf, boromir, aragorn, gimli, legolas);

//여러가지 기준을 Chaining 할 수 있다
assertThat(fellowshipOfTheRing).filteredOn("race", MAN)
                               .filteredOn("name", not("Boromir"))
                               .containsOnly(aragorn);

 

 

filteredOnNull : 지정된 property가 null인 객체를 반환한다.

TolkienCharacter pippin = new TolkienCharacter("Pippin", 28, HOBBIT);
TolkienCharacter frodo = new TolkienCharacter("Frodo", 33, HOBBIT);
TolkienCharacter merry = new TolkienCharacter("Merry", 36, HOBBIT);
TolkienCharacter mysteriousHobbit = new TolkienCharacter(null, 38, HOBBIT);

List<TolkienCharacter> hobbits = list(frodo, mysteriousHobbit, merry, pippin);

assertThat(hobbits).filteredOnNull("name")) //name이 null인 mysteriousHobbit 반환
                   .singleElement()
                   .isEqualTo(mysteriousHobbit);

 

Condition이라는 확장된 AssertJ 기능을 활용하여 filteredOn 안에 넣어두어도 된다.

여러 테스트가 공통의 Condition을 공유할 때 좋다

import org.assertj.core.api.Condition;

Condition<Player> mvpStats= new Condition<Player>(player -> {
    return player.pointsPerGame() > 20 && (player.assistsPerGame() >= 8 || player.reboundsPerGame() >= 8);
  }, "mvp");

List<Player> players;
players.add(rose); // Derrick Rose: 25 ppg - 8 assists - 5 rebounds
players.add(lebron); // Lebron James: 27 ppg - 6 assists - 9 rebounds
players.add(noah); // Joachim Noah: 8 ppg - 5 assists - 11 rebounds

// noah does not have more than 20 ppg
assertThat(players).filteredOn(mvpStats)
                   .containsOnly(rose, lebron);

 


extracting

-만약 TolkeinCharacter List에서 name 필드를 검증하는 경우를 살펴보자

- stream을 통해 하나하나 getter를 하는 과정은 tedious하다

- 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");

// specifying nested field/property is supported
assertThat(fellowshipOfTheRing).extracting("race.name")
                               .contains("Man", "Maia", "Hobbit", "Elf");

// same thing with a lambda which is type safe and refactoring friendly:
assertThat(fellowshipOfTheRing).extracting(TolkienCharacter::getName)
                               .contains("Boromir", "Gandalf", "Frodo", "Legolas");

// same thing map an alias of extracting:
assertThat(fellowshipOfTheRing).map(TolkienCharacter::getName)
                               .contains("Boromir", "Gandalf", "Frodo", "Legolas")

 

 

extracting에 두번째 매개변수로 type을 주어 strongly typed하게 만들 수 있다.

// String.class를 주어 강하게 타입화
assertThat(fellowshipOfTheRing).extracting("name", String.class)
                               .contains("Boromir", "Gandalf", "Frodo", "Legolas")
                               .doesNotContain("Sauron", "Elrond");

 

extracting으로 여러값을 추출하면 tuple로 반환된다

// when checking several properties/fields you have to use tuples:
import static org.assertj.core.api.Assertions.tuple;

// extracting name, age and race.name nested property
assertThat(fellowshipOfTheRing).extracting("name", "age", "race.name") //여러값 추출
                               .contains(tuple("Boromir", 37, "Man"), // tuple로 반환됨
                                         tuple("Sam", 38, "Hobbit"),
                                         tuple("Legolas", 1000, "Elf"));

// same assertion with functions for type safety:
assertThat(fellowshipOfTheRing).extracting(TolkienCharacter::getName,
                                            tolkienCharacter -> tolkienCharacter.age,
                                            tolkienCharacter -> tolkienCharacter.getRace().getName())
                                .contains(tuple("Boromir", 37, "Man"),
                                          tuple("Sam", 38, "Hobbit"),
                                          tuple("Legolas", 1000, "Elf"));

 

flatExtracting을 통해 flatMap 처럼 객체 깊이 있는 속성들을 일자화할 수 있다

Player jordan = ... // initialized with Pippen and Kukoc team mates
Player magic = ... // initialized with Jabbar and Worthy team mates
List<Player> reallyGoodPlayers = list(jordan, magic);

// check all team mates by specifying the teamMates property (Player has a getTeamMates() method):
assertThat(reallyGoodPlayers).flatExtracting("teamMates")
                             .contains(pippen, kukoc, jabbar, worthy);

// alternatively, you can use a Function for type safety:
assertThat(reallyGoodPlayers).flatExtracting(BasketBallPlayer::getTeamMates)
                             .contains(pippen, kukoc, jabbar, worthy);

// flatMap is an alias of flatExtracting:
assertThat(reallyGoodPlayers).flatMap(BasketBallPlayer::getTeamMates)
                             .contains(pippen, kukoc, jabbar, worthy);

// if you use extracting instead of flatExtracting the result would be a list of list of players so the assertion becomes:
assertThat(reallyGoodPlayers).extracting("teamMates")
                             .contains(list(pippen, kukoc), list(jabbar, worthy));

 

예외 처리

 

정해진 Exception을 잡는 테스트

  • catchException
  • catchIllegalArgumentException
  • catchIllegalStateException
  • catchIndexOutOfBoundsException
  • catchIOException
  • catchNullPointerException
  • catchReflectiveOperationException
  • catchRuntimeException

assertThatThrownBy(ThrowingCallable) : 예외 발생

assertThatThrownBy(() -> { throw new Exception("boom!"); }).isInstanceOf(Exception.class)
                                                           .hasMessageContaining("boom");

 

assertThatExceptionOfType(Exception.class).isThrownBy(ThrowingCallable) 

assertThatExceptionOfType(IOException.class).isThrownBy(() -> { throw new IOException("boom!"); })
                                            .withMessage("%s!", "boom")
                                            .withMessageContaining("boom")
                                            .withNoCause();

 

더 자연스러운 문법들도 있다.

  • assertThatNullPointerException
  • assertThatIllegalArgumentException
  • assertThatIllegalStateException
  • assertThatIOException
assertThatIOException().isThrownBy(() -> { throw new IOException("boom!"); })
                       .withMessage("%s!", "boom")
                       .withMessageContaining("boom")
                       .withNoCause();

 

 

reference)

https://umanking.github.io/2021/06/26/assertj-iteration/

 

AssertJ 자주 사용하는 것들

AssertJ에서 자주 사용하는 기본 문법들에 대해서 알아보자.

umanking.github.io

 

https://assertj.github.io/doc/

 

AssertJ - fluent assertions java library

Thanks to all the contributors of this release: Erhard Pointl, Stefano Cordio, BJ Hargrave, Jeremy Landis, Ashley Scopes, Roland Weisleder , Benedikt Bogason , Andreas Kutschera , Matthew , Chris HeZean , Leo0506 , Zhou Yicheng , Saria , Chunhao Liao , max

assertj.github.io