loop-study
개발 공부할래?
loop-study
전체 방문자
오늘
어제
  • 분류 전체보기 (186)
    • 목표 및 회고 (25)
    • 세미나 & 워크샵 (1)
    • 교육 및 인강 (67)
      • TDD, Clean Code with Java (5)
      • ATDD, 클린 코드 with Spring (6)
      • DDD 세레나데 (3)
      • 인프라 공방 (6)
      • 이규원의 현실 세상의 TDD (19)
      • 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 (18)
      • 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 (0)
      • 모든 개발자를 위한 HTTP 웹 기본 지식 - 김영한 (8)
      • 코딩으로 학습하는 GoF의 디자인 패턴 (1)
      • 스프링 시큐리티 완전정복 6.x (1)
    • 서적 (62)
      • 객체지향의 사실과 오해 (1)
      • 객체지향과 디자인패턴 (7)
      • 만들면서 배우는 클린 아키텍처 (3)
      • 테스트 주도 개발로 배우는 객체 지향 설계와 실천 (1)
      • 오브젝트: 코드로 이해하는 객체지향 설계 (17)
      • 리팩토링 : 코드 구조를 체계적으로 개선하여 효율적인 리팩터링 구현하기 (0)
      • 토비의 스프링 (3)
      • 엔터프라이즈 애플리케이션 아키텍처 패턴 (9)
      • 개발자의 글쓰기 (1)
      • 소프트웨어 장인 (17)
      • Real MySQL 8.0 (2)
      • JVM 밑바닥까지 파헤치기 (0)
    • 개발 & 방법론 (29)
      • Java (13)
      • TDD (5)
      • ATDD (3)
      • DDD (6)
      • 인프라 (2)
      • SQL (0)
    • 개인이야기 (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

  • 백엔드 로드맵

인기 글

태그

  • 모든 개발자를 위한 HTTP 웹 기본 지식
  • 넥스트스탭
  • TDD
  • nextstep
  • Test Driven Development
  • 스터디
  • 소프트웨어 장인
  • 김영한
  • 추상화
  • 객체지향
  • 백기선
  • JUnit
  • 자바
  • 인프라공방
  • ATDD
  • DDD 세레나데
  • 현실세상의 TDD
  • 조영호
  • 오브젝트
  • 인프런
  • 스프링
  • 장인정신
  • fastcampus
  • java
  • 이규원
  • 엔터프라이즈 애플리케이션 아키텍처 패턴
  • study
  • Patterns of Enterprise Application Architecture
  • 테스트 주도 개발
  • 마틴 파울러

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
loop-study

개발 공부할래?

AssertJ 알아보기 (부제 : Jupiter, Hamcrest 맛보기 )
개발 & 방법론/TDD

AssertJ 알아보기 (부제 : Jupiter, Hamcrest 맛보기 )

2021. 11. 21. 21:22

많은 곳에서 JUnit에서 제공하는 공식 기능인 Jupiter가 아닌 AssertJ를 사용하고 있다.

여러 교육과정이나 인강에서도 AssertJ를 권장하는데 그 이유는 무엇일까? 간단히 알아보자.


AssertJ 이란?

테스트에 관련된 많은 기능을 제공하고 메서드 체이닝으로 가독성 높은 테스트 코드 작성을 지원하는 오픈 라이브러리다.

 

AssertJ Import

AssertJ의 Assertions를 사용 할 때 주의점은 것은 기존 JUnit의 Assertions와 같은 클래스명을 가진다.

import 경로는 org.assertj.core.api 다. 

Jupiter와 겹치는 클래스명

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
    '개발 & 방법론/TDD' 카테고리의 다른 글
    • Mockito 알아보기 (부제 : BDD)
    • JUnit5 알아보기
    • 테스트 코드를 작성하는 이유
    • 테스트코드 첫걸음
    loop-study
    loop-study
    오늘도 공부하자

    티스토리툴바