[SpringBoot] 7.1-4 테스트 코드
📌 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은 하나의 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는 테스트 메서드 각각이 대상이다.