TestCode Intro
최근에는 애플리케이션을 개발할 때 테스트 코드로 로직을 확인하는 과정이 점점 더 중요하게 여겨지고 있습니다.
테스트 코드를 작성하는 이유
- 개발 과정에서 문제를 미리 발견할 수 있습니다.
- 리팩토링의 리스크가 줄어듭니다.
- 애플리케이션을 가동해서 직접 테스트하는 것보다 테스트를 빠르게 진행할 수 있습니다.
- 하나의 명세 문서로서의 기능을 수행합니다.
- 몇 가지 프레임워크에 맞춰 테스트 코드를 작성하면 좋은 코드를 생산할 수 있습니다.
- 코드가 작성된 목적을 명확하게 표현할 수 있으며, 불필요한 내용이 추가되는 것을 방지 합니다.
단위 테스트와 통합 테스트
테스트 대상 범위를 기준으로 구분하면 크게 단위 테스트(Unit Test)와 통합 테스트(Integration Test)로 구분됩니다.
- 단위 테스트 : 애플리케이션의 개별 모듈을 독립적으로 테스트하는 방식입니다.
- 통합 테스트 : 애플리케이션을 구성하는 다양한 모듈을 결합해 전체적인 로직이 의도한 대로 동작하는지 테스트하는 방식입니다.
단위 테스트의 특징
테스트 대상의 범위를 기준으로 가장 작은 단위의 테스트 방식
일반적으로 메서드 단위로 테스트를 수행하게 되며, 메서드 호출을 통해 의도한 결괏값이 나오는지 확인하는 수준으로 테스트를 진행
- 비용이 적게든다.
- 피드백을 빠르게 받을 수 있다.
통합 테스트의 특징
모듈을 통합하는 과정에서의 호환성 등을 포함해 애플리케이션이 정상적으로 동작하는지 확인하기 위해 수행하는 테스트 방식
여러 모듈을 함께 테스트해서 정상적인 로직 수행이 가능한지를 확인한다.
통합 테스트는 외부 요인(데이터베이스나 네트워크)들을 포함하고 테스트를 진행하므로 애플리케이션이 온전히 동작하는지를 테스트하게 됩니다.
💡 테스트 비용이란?
테스트 비용이란 용어는 소프트웨어 공학에서 많이 사용됩니다. 여기서 마라는 비용은 금전적인 비용을 포함해서 시간, 인력과 같은 개발에 필요한 것들을 포괄합니다. 통계적으로 하나의 서비스를 개발할 때는 개발 과정에서 60%, 테스트 과정에서 40%의 비용이 든다고 알려져 있습니다.
테스트 코드를 작성하는 방법
많은 사람들이 사용하는 ‘Given-When-Then’ 패턴과 ‘F.I.R.S.T’ 전략 소개
Given-When-Then 패턴
Give
테스트를 수행하기 전에 테스트에 필요한 환경을 설정하는 단계입니다. 테스트에 필요한 변수를 정의하거나 Mock 객체를 통해 특정상황에 대한 행동을 정의합니다.
When
테스트의 목적을 보여주는 단계입니다. 실제 테스트 코드가 포함되며, 테스트를 통한 결괏값을 가져오게 됩니다.
Then
테스트의 결과를 검증하는 단계입니다. 일반적으로 When 단계에서 나온 결괏값을 검증하는 작업을 수행합니다. 결괏값이 아니더라도 이 테스트를 통해 나온 결과에서 검증해야 하느 부분이 있다면 이 단계에 포함합니다.
테스트 주도 개발에서 파생된 BDD(Bdhavior-Driven-Development; 행위 주도 개발)를 통해 탄생한 테스트 접근 방식입니다.
간단한 테스트로 여겨지는 단위 테스트에서 잘 사용하지 않습니다. 그 이유 중 하나는 불필요하게 코드가 길어진다는 것
하지만 이 패턴을 통해 테스트 코드를 작성한다면 명세 문서의 역할을 수행한다는 측면에서 많은 도움이 됩니다.
좋은 테스트를 작성하는 5가지 속성(F.I.R.S.T)
테스트 코드를 작성하는데 도움이 될 수 있는 5가지 규칙을 의미합니다. 이 규칙은 대체로 단위 테스트에 적용할 수 있는 규칙이며, 간단하게 설명하면 다음과 같습니다.
- 빠르게(Fast) 테스트는 빠르게 수행돼야 합니다. 테스트가 느리면 코드를 개선하는 작업이 느려져 코드 품질이 떨어질 수 있습니다. 테스트 속도에 절대적인 기준은 없지만 목적을 단순하게 설정해서 작성하거나 외부 환경을 사용하지 않는 단위 테스트를 작성하는 것 등을 빠른 테스트라고 할 수 있습니다.
- 고립된, 독립적(Isolated) 하나의 테스트 코드는 목적으로 여기는 하나의 대상에 대해서만 수행돼야 합니다. 만약 하나의 테스트가 다른 테스트 코드와 상호작용하거나 관리할 수 없는 외부 소스를 사용하게 되면 외부 요인으로 인해 테스트가 수행되지 않을 수 있습니다.
- 반복 가능한(Repeatable) 테스트는 어떤 환경에서도 반복 가능하도록 작성해야 합니다. 이 의미는 앞의 Isolated 규칙과 비슷한 의미를 갖고 있습니다. 테스트는 개발 환경의 변화나 네트워크의 연결 여부와 상관없이 수행돼야 합니다.
- 자가 검증(Self-Validating) 테스트는 그 자체만으로도 테스트의 검증이 완료돼야 합니다. 테스트가 성공했는지 실패했는지 확인할 수 있는 코드를 함계 작성해야 합니다. 만약 결괏값과 기댓값을 비교하는 작업을 코드가 아니라 개발자가 직접 확인하고 있다면 좋지 못한 테스트 코드 입니다.
- 적시에(Timely) 테스트 코드는 테스트하려는 애플리케이션 코드를 구현하기 전에 완성돼야 합니다. 너무 늦게 작성된 테스트 코드는 정상적인 역할을 수행하기 어려울 수 있습니다. 또한 테스트 코드로 인해 발견된 문제를 해결하기 위해 소모되는 개발 비용도 커지기 쉽습니다. 다만 이 개념은 테스트 주도 개발의 원칙을 따르는 테스트 작성 규칙으로, 테스트 주도 개발을 기반으로 애플리케이션을 개발하는 것이 아니라면 이 규칙은 제외하고 진행하기도 합니다
JUnit을 활용한 테스트 코드 작성
JUnit은 자바 언어에서 사용되는 대표적인 테스트 프레임워크로서 단위 테스트를 위한 도구를 제공합니다. 또한 단위 테스트뿐만 아니라 통합 테스트를 할 수 있는 기능도 제공합니다.
가장 큰 특징으로는 어노테이션 기반으로 테스트 방식을 지원한다는 것
JUnit을 활용하면 단정문(assert)을 통해 테스트 케이스의 기댓값이 정상적으로 도출됐는지 검토할 수 있다는 장점이 있습니다.
JUnit의 세부 모튤
JUnit 5는 크게 Jupiter, PlatForm, Vintage의 세 모듈로 구성됩니다.
- JUnit Platform JUnit Platformdms JVM에서 테스트를 시작하기 위한 빼대 역할을 합니다. 테스트를 발견하고 테스트 계획을 생성한는 테스트 엔진(TestEngine)의 인터페이스를 가지고 있습니다. 테스트 엔진은 테스트를 발견하고 테스트를 수행하며, 그 결과를 보고하는 역할을 수행합니다. 또한 각종 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 JUnit 3,4 대한 테스트 엔진 API를 구현하고 있습니다. 기존에 작성된 JUnit 3,4 버전의 테스트 코드를 실행할 때 사용되며 Vintage Engine을 포함하고 잇습니다.
- Junit 5의 모튤 구조

[참조]
- 스프링 부트 핵심 가이드 [책] -장정우 지음