본문 바로가기
TestCode

Junit을 이용한 단위 테스트

by soeundid 2023. 8. 13.

목차

1. 테스트 코드를 작성해야 하는 이유

2. JUnit5 란?

3. Assertj

4. 수동테스트 VS 자동화된 케이스

5. 테스트 케이스 세분화 하기

 


테스트 코드란?

  • 테스트 코드는 소프트웨어의 기능과 동작을 테스트하는데 사용되는 코드이다.
  • 테스트 코드는 개발자가 작성한 코드를 실행하고 예상된 결과가 나오는지 확인하는데 사용된다.

1. 테스트 코드를 작성해야 하는 이유

  • 코드의 품질 향상
    • 테스트 코드를 통해 발생 가능성 있는 버그를 사전에 찾아내고 방지할 수 있으며, 이는 개발자가 신뢰할 수 있는 코드를 작성할 수 있게 도와준다.
  • 문서화
    • 테스트 코드는 개발자가 기능의 동작 방식을 이해하는데 도움이 되는 문서로 작용할 수 있다.
    • 테스트 코드를 통해 코드의 예상 동작을 명확하게 확인할 수 있으며, 개발자 간의 커뮤니케이션 향상에도 도움이 된다.
  • 리팩토링
    • 테스트 코드가 있는 경우 코드를 리팩토링할때 기존 기능이 여전히 올바르게 작동하는지 확인할 수 있다.
    • 변경 사항이 예상치 못한 부작용을 일으키지 않도록 하며, 코드의 동작이 바뀌지 않았는지 확인할 수 있다.

 

테스트 종류

단위 테스트(Unit Test)

단위 테스트는 응용 프로그램에서 테스트 가능한 가장 작은 소프트웨어를 실행하여 예상대로 동작하는지 확인하는 테스트이다. 테스트 대상 단위의 크기는 엄격하게 정해져 있지 않지만 일반적으로 클래스 또는 메서드 수준으로 정해진다.

통합 테스트(Integreation Test)

통합 테스트는 단위 테스트보다 더 큰 동작을 달성하기 위해 여러 모듈들을  모아 이들이 의도대로 협력하는지 확인하는 테스트이다. 단위 테스트와 달리 개발자가 변경할 수 없는 부분(외부 라이브러리)까지 묶어 검증할 때 사용한다. 이는 DB에 접근하거나 전체 코드와 다양한 환경이 제대로 작동하는지 확인하는데 필요한 모든 작업을 수행할 수 있다.

 

2.  JUnit5 란?

JUnit5 = JUnit Platform + JUnit Jupiter + Junit Vintage

JUnit5는 자바 프로그래밍 언어를 위한 테스트 프레임워크이다. JUnit은 소프트웨어의 단위 테스트를 작성하고 실행하는 데 사용되는 도구로서, 코드의 품질을 검증하고 버그를 감지하기 위해 개발자들에게 도움을 주는 역할을 한다. JUnit5는 이전 버전인 JUnit4의 다음 세데 버전으로 개발되었다. JUnit5는 자바 8 이상부터 사용 가능하다.

 

 

JUnit 5 User Guide

Although the JUnit Jupiter programming model and extension model do not support JUnit 4 features such as Rules and Runners natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and custo

junit.org

 

spring boot 에서 JUnit5 사용하기

build.gradle

    //test
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

Test Class 생성: 단축키 Crtl + Shift + T 

Create Test

 

3. AssertJ 

AssertJ는 자바 프로그래밍 언어를 위한 테스트 단언 라이브러리이다. assertion은 코드의 동작을 검증하고 예상한 결과와 실제 결과가 일치하는지를 확인하는데 사용한다.

AssertJ는 테스트 코드를 더 읽기 쉽고 유지보수하기 쉽도록 만들어주는 다양한 메서드와 기능을 제공한다.

 

 

AssertJ / Fluent assertions for java

AssertJ Fluent assertions for java

joel-costigliola.github.io

 

build.gradle

    //test
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

- 여기에 AssertJ도 포함되어 있다.

 

  • AssertJ에서 제공하는 assertion이 가독성이 더 높은것을 볼 수 있다.
    @Test
    void getName(){
        Americano americano = new Americano();

        assertEquals(americano.getName(), "아메리카노");//Junit API

        assertThat(americano.getName()).isEqualTo("아메리카노");//AssertJ

    }

 

테스트 코드 작성 시작

4. 수동 테스트 VS 자동화된 테스트

수동테스트

    @Test
    void add_manual_test(){//수동 테스트
        CafeKiosk cafeKiosk = new CafeKiosk();
        cafeKiosk.add(new Americano());

        System.out.println(">>> 담긴 음료 수: " + cafeKiosk.getBeverages().size());
        System.out.println(">>> 담긴 음료: " + cafeKiosk.getBeverages().get(0).getName());
    }

수동테스트 실행결과

  • 콘솔에 찍힌 결과를 보고 사람이 판단하는 테스트 코드이다.
  • 올바른 테스트 코드일까?
    • 문제점
      • 1. 최종 단계에서 사람이 개입해야 하는 테스트 코드이다.
      • 2. 다른 사람이 테스트 코드를 봤을때 어떤것을 검증해야 하는지 뭐가 맞는 상황인지 틀린 상황인지 알 수 없다.
      • 3. 무조건 성공하는 테스트 코드이다.

 

자동화된 테스트

    @Test
    void add(){
        CafeKiosk cafeKiosk = new CafeKiosk();
        cafeKiosk.add(new Americano());

        assertThat(cafeKiosk.getBeverages().size()).isEqualTo(1);
        assertThat(cafeKiosk.getBeverages().get(0).getName()).isEqualTo("아메리카노");
    }

자동화된 테스트 실행결과

  • 자동화된 테스트는 사람이 확인하지 않아도 된다.

 

5. 테스트 케이스 세분화하기

  • 해피 케이스
  • 예외 케이스

+ 경계값 테스트: 범위(이상, 이하, 초과, 미만), 구간 날짜 등

 

✏ 요구사항

가게 운영 시간(10:00 ~ 22:00) 외에는 주문을 생성할 수 없다.

 

위의 요구사항에서

해피케이스: 10:00 ~ 22:00 사이의 시간에 주문을 생성하는 경우

  • 해피케이스의 경우 경계값이 존재한다면 경계값을 테스트 하는것이 좋은 방법이다.
  • 경계값 테스트: 10:00 or 22:00에 대한 테스트

예외 케이스: 운영시간 외의 시간에 주문을 생성하는 경우

 

주문생성 method

    public Order createOrder(){//주문 생성
        LocalDateTime currentDateTime = LocalDateTime.now();
        LocalTime currentTime = currentDateTime.toLocalTime();

        if(currentTime.isBefore(SHOP_OPEN_TIME) || currentTime.isAfter(SHOP_CLOSE_TIME)){
            throw new IllegalArgumentException("주문 시간이 아닙니다. 관리자에게 문의하세요.");
        }

        return new Order(LocalDateTime.now(), beverages);
    }
  • createOrder 실행시 현재 시간을 받아와서 현재 시간이 가게 운영시간안에 있는 확인하고 주문을 생성하는 메서드

 

해피케이스

    @Test
    void createOrder(){
        CafeKiosk cafeKiosk = new CafeKiosk();
        Americano americano = new Americano();

        cafeKiosk.add(americano);

        Order order = cafeKiosk.createOrder();

        assertThat(order.getBeverages()).hasSize(1);
        assertThat(order.getBeverages().get(0).getName()).isEqualTo("아메리카노");

    }

 

!이 경우의 문제점!

  • createOrder 메소드가 현재 시간을 가지고 주문시간을 check해서 예외를 던지도록 구현되어 있기 때문에 가게 운영시간 안에서 수행할때만 성공하는 테스트가 되어버린다.

해결방법은?

    public Order createOrder(LocalDateTime currentDateTime){//주문 생성

        LocalTime currentTime = currentDateTime.toLocalTime();

        if(currentTime.isBefore(SHOP_OPEN_TIME) || currentTime.isAfter(SHOP_CLOSE_TIME)){
            throw new IllegalArgumentException("주문 시간이 아닙니다. 관리자에게 문의하세요.");
        }

        return new Order(LocalDateTime.now(), beverages);
    }
  • LocalDateTime은 실행할때마다 변경되는 값으로 이것이 문제가 되기 때문에 함수 외부에서 입력받도록 수정

수정한 method로 테스트 수행

 

경계값 테스트

    @Test
    void createOrderWithCurrentTime(){
        CafeKiosk cafeKiosk = new CafeKiosk();
        Americano americano = new Americano();

        cafeKiosk.add(americano);

        Order order = cafeKiosk.createOrder(LocalDateTime.of(2023, 8, 4, 10, 0));//경계값 테스트
        assertThat(order.getBeverages()).hasSize(1);
        assertThat(order.getBeverages().get(0).getName()).isEqualTo("아메리카노");

    }

 

예외 테스트

    @Test
    void createOrderOutsideOpenTime(){//예외 테스트
        CafeKiosk cafeKiosk = new CafeKiosk();
        Americano americano = new Americano();

        cafeKiosk.add(americano);

        assertThatThrownBy(()-> cafeKiosk.createOrder(LocalDateTime.of(2023, 8, 4, 9, 59)))
                .isInstanceOf(IllegalArgumentException.class)
                        .hasMessage("주문 시간이 아닙니다. 관리자에게 문의하세요.");

    }

 

실제로 사용할때는 현재시간을 넣어주는 방식으로 사용
테스트 할때는 원하는 시간을 넣어서 테스트 하는 방식으로 해결할 수 있다.

 

Order order = cafeKiosk.createOrder(LocalDateTime.now());