Spring Boot/스프링 부트 핵심 가이드

[SpringBoot] 7.1-4 테스트 코드

nayonsoso 2023. 9. 22. 20:51

📌 7.1 테스트코드를 작성하는 이유


  • 개발 과정에서 문제를 미리 발견할 수 있음
  • 사이드 이펙트를 테스트할 수 있음 (내가 코드를 수정함으로써 다른 코드에 영향이 가는지)
  • 애플리케이션을 가동해서 하는 테스트보다 빠른 테스트를 할 수 있음
  • 협업을 할 때 명세 문서로서의 기능을 수행함
  • 코드가 작성된 목적을 명확하게 표현할 수 있으며, 불필요한 내용이 추가되는 것을 방지할 수 있음
  • 오류가 발생할 수 있는 상황을 만들어서 오류를 테스트할 수 있음

cf. 테스트 코드를 작성하는 것과 TDD는 엄연히 다르다. 
TDD는 테스트코드를 먼저 짜고 그것을 명세서 삼아 코드를 작성하는 것

📌 7.2 단위 테스트와 통합 테스트


단위 테스트(Unit Test)

  • 개별 모듈을 독립적으로 테스트하는 것
  • 일반적으로 메서드 단위로 테스트하여 메서드의 결과가 의도한 값이 나오는지를 확인
  • 네트워크나 데이터베이스 같은 외부요인을 제외하고 진행

통합 테스트 (Integration Test)

  • 다양한 모듈을 결합해서 테스트하는 것
  • 여러 모듈이 결합되었을 때 어플리케이션이 정상적인 로직 수행하는지를 확인
  • 네트워크나 데이터베이스 같은 외부요인을 포함하고 진행

📌 7.3.1 Given-When-Then 패턴


Given
- 테스트에 필요한 환경을 설정하는 단계
- 테스트에 필요한 변수를 정의하거나 Mock 객체를 통해 특정 상황에 대한 행동을 정의

When
- 테스트의 목적을 보여주는 단계
- 실제 테스트할 코드가 포함되며, 테스트를 통한 결과값을 가져오게 됨

Then
- 테스트의 결과를 검증하는 단계
- When 단계에서 나온 결과값을 검증하는 작업을 수행

📌 7.3.2 좋은 테스트를 작성하는 5가지 속성(F.I.R.S.T)


빠르게(Fast)
- 테스트는 빠르게 수행되어야 함
- 테스트가 느리면 코드를 개선하는 작업이 느려져 코드 품질이 떨어질 수 있음
- e.g. 목적을 단순하게 설정 / 외부 환경을 사용하지 않는 단위 테스트를 작성

고립된, 독립적(Isolated)
- 하나의 테스트 코드는 하나의 대상에 대해서만 수행되어야 함 
- 다른 테스트코드나 외부 요인으로 인해 테스트가 수행되지 않는 상황을 방지해야 함

반복 가능한(Repeatable)
- 테스트는 어떤 환경에서도 반복 가능하도록 작성해야 함
- 개발 환경의 변화나 네트워크의 연결 여부와 상관없이 수행되어야 함

자가 검증(Self-Validating)
- 테스트코드 자체만으로도 검증이 완료돼야 함
- 테스트 코드 안에 테스트가 성공했는지 실패했는지 확인할 수 있는 코드를 함께 작성해야 합니다.

적시에(Timely)
- 테스트 코드는 테스트하려는 애플리케이션 코드를 구현하기 전에 완성되어야 함 
- TDD 개발 원칙을 따르는 테스트 작성 규칙 / TDD 개발을 할 것이 아니라면 제외하고 진행하기도 함
- 너무 늦게 작성된 테스트 코드는 정상적인 역할을 수행하기 어려울 수 있으며, 테스트 코드로 인해 발견된 문제를 해결하기 위해 소모되는 개발 비용도 커지기 쉽기 때문

📌 7.4 JUnit을 활용한 테스트 코드 작성


JUnit 이란?

  • 자바 언어에서 사용되는 대표적인 테스트 프레임워크
  • 자바 애플리케이션의 단위 테스트를 위한 도구를 제공 (통합 테스트에서도 사용 가능)
  • 어노테이션 기반의 테스트 방식을 지원
  • assert문(단정문)을 통해 테스트 결과값을 검증할 수 있음
  • 현재는 JUnit5까지 나옴 

📌 7.4.1 JUnit의 세부 모듈


JUnit5 는 크게 Jupiter, Platform, Vintage의 세 모듈로 구성됨

JUnit Platform

  • JVM에서 테스트를 실행하기 위한 기초를 제공
  • 테스트를 발견하고 테스트 계획을 생성하는 테스트 엔진(TestEngine) API를 제공
  • 테스트 엔진은 테스트를 발견하고 테스트를 수행하며, 그 결과를 보고하는 역할
  • 또한 각종 IDE와의 연동을 보조하는 역할(IDE 콘솔 출력 등). 
  • Platform에는 TestEngine API, Console Launcher, JUnit 4 Based Runner 등이 포함됨

JUnit Jupiter

  • 테스트 엔진 API의 구현체를 포함하고 있음
  • JUnit 5에서 제공하는 Jupiter 기반의 테스트를 실행하기 위한 테스트 엔진을 가지고 있음
  • 그중 하나가 Jupiter Engine
  • Jupiter Engine은 Jupiter API를 활용해서 작성한 테스트 코드를 발견하고 실행하는 역할을 수행

JUnit Vintage

  • 하위 호환성을 위해 JUnit3, 4를 기반으로 돌아가는 플랫폼에 Vintage Engine을 제공

출처 : JUnit 5 Tutorial (howtodoinjava.com)

이처럼 JUnit은 하나의 Platform 모듈을 기반으로 Jupiter와 Vintage 모듈이 구현체의 역할을 수행함
자세한 내용 참고 (왕추천) : https://donghyeon.dev/junit/2021/04/11/JUnit5-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C/

📌 7.4.3 스프링 부트의 테스트 설정


spring-boot-starter-test에서 제공하는 라이브러리

  • JUnit 5 : 자바 애플리케이션의 단위 테스트를 지원
    단위 테스트만을 위한 것은 아니고, JUnit 에서 제공하는 도구를 통합 테스트에서도 사용할 수 있음
  • Spring Test & Spring Boot Test : 스프링 부트 애플리케이션에 대한 유틸리티와 통합 테스트를 지원
  • AssertJ : 다양한 단정문(assert)을 지원하는 라이브러리
  • Hamcrest : Matcher를 지원하는 라이브러리 e.g. greaterThan()
  • Mockito : 자바 Mock 객체를 지원하는 프레임워크
  • JSONassert : JSON용 단정문(assert) 라이브러리
  • JsonPath : JSON XPath를 지원

 

cf. Spring Initailizer를 통해서 프로젝트를 생성하면 spring-boot-starter-test dependency가 자동으로 추가됨

📌 7.4.4 JUnit5 의 생명주기


JUnit의 동작방식을 알기 위해서는 생명주기를 이해할 필요가 있고, 
이를 위해서는 생명주기에 관여하는 어노테이션을 알아둬야함
복습 ) JUnit은 어노테이션 기반의 테스트 방식을 지원한다.

  • @Test : 테스트 코드를 포함한 메서드를 정의
  • @BeforeAll : 테스트를 시작하기 전에 호출되는 메서드를 정의
  • @BeforeEach : 각 테스트 메서드가 실행되기 전에 동작하는 메서드를 정의
  • @AfterAll : 테스트를 종료하면서 호출되는 메서드를 정의
  • @AfterEach : 각 테스트 메서드가 종료되면서 호출되는 메서드를 정의
  • cf. All은 전체 테스트가 대상이고, Each는 테스트 메서드 각각이 대상이다.

https://www.arhohuttunen.com/junit-5-test-lifecycle/