목차
TDD 소개
Why TDD?
How TDD?
JUnit5 사용법
기본 Annotation
Assertion
@ParameterizedTest
테스트를 바라보는 관점
What TDD?
단위 테스트, 통합 테스트, E2E 테스트
목차
테스트 코드 Tip
테스트 코드 작성시, 주의할 점들
ATDD 소개
ATDD?
Classist vs Mockist
최근 기술 공유
Test Container, Java Test Fixture …
Tip
테스트 코드는
직접 작성해보고 실행보는게
최고의 지름길입니다.
🐈
테스트 코드? Why?
코드가 잘 돌아가려나???
무엇을 테스트해야 하나?
해당 시스템이 어떤 기능과 서비스를 제공하는지 확인!
회귀 테스트
자동적으로 기본적인 기능이 정상 작동하는지 확인할 수 있다.
TDD란?
Test-Driven Development
테스트 주도 개발
테스트 코드를 먼저 작성한 후 실제 구현 코드를 개발하는 방식
빠른 피드백
테스트를 자동화하여 빠르게 코드의 유효성을 확인할 수 있음
지속적인 리팩토링
테스트 코드를 통해 안전하게 리팩토링할 수 있음
TDD Cycle
1
Red (실패)
먼저, 테스트 코드를 작성합니다.
(테스트는 당연히 실패합니다.)
어떤 기능을 만들지 생각하게 됩니다.
2
Green (성공)
테스트를 통과할 수 있는 구현 코드를 작성합니다.
3
Refactor (리팩터링)
중복을 제거하고 코드를 개선합니다.
테스트 통과를 유지하면서 구조를 개선합니다.
TDD 장점
1
코드 품질 향상
테스트 코드로 불필요한 버그를 사전에 막을 수 있습니다.
2
빠른 피드백
코드 작성 직후 테스트를 실행하여 오류를 즉시 발견하고 수정 가능합니다.
3
기능 요구사항 명확화
테스트 케이스가 명확한 기능 요구사항을 반영합니다.
4
리팩터링에 큰 도움
테스트 코드가 있어 안전하게 리팩토링할 수 있습니다.
테스트 코드~ 뭐가 좋아?
1
🔍 기능이 올바르게 작동하는지 확인
2
🤖 자동으로 테스트 가능
3
♻️ 리팩터링 시 유용
4
📝 기능을 명확하게 기록
5
정상 작동 하는지 빠르게 확인
6
💬 개발자에게 전반적인 기능 공유
애플리케이션의 기능과 동작을 검증하여 품질을 높일 수 있습니다.
테스트 코드? How?
JUnit, TestNG, Mockito
Kotest, Spek
testing 패키지, Testify, Ginkgo
Google Test, Boost.Test
Cargo Test, Mockall
xUnit, NUnit, MSTest
PHPUnit, Codeception, Behat
Jest, Mocha, Chai, Jasmine
unittest, pytest, nose2
테스트 관련 라이브러리
JUnit5
자바 플랫폼을 위한 단위 테스트 프레임워크
Mockito
자바 모킹 프레임워크, 외부 의존성 제어
AssertJ
유연하고 풍부한 assertion 기능 제공
Hamcrest
복잡한 assert 조건을 간단히 표현
JUnit5
Java 단위 테스트 프레임워크로,
최신 기능과 확장성을 제공합니다.
테스트 작성과 실행을 편리하게 해주는 다양한 어노테이션과 API를 제공합니다.
JUnit Platform
JVM에서 테스트 프레임워크를 실행하기 위한 기반 제공.
JUnit Jupiter
새로운 프로그래밍 모델 및 확장 모델로 테스트와 확장 작성.
🔌 JUnit Vintage
JUnit 3 및 JUnit 4 테스트의 호환성 제공.
JUnit5 환경 설정
Gradle (Kotlin)
Gradle (Groovy)
Maven
JUnit5 기본 Annotation
  • @Test
  • @BeforeAll
  • @BeforeEach
  • @AfterEach
  • @AfterAll
  • @TestInstance(Lifecycle.PER_CLASS)
Assertion이란?
테스트 대상의 기능을 테스트하고 검증하는 코드

주로 Assertj 혹은 JUnit에서 제공하는 Assertions 클래스 활용
Assertion 메서드
1
객체
  • isEqualTo(), isSameAs()
  • isNull()
2
숫자
  • isGreaterThan(), isLessThan()
  • isBetween()
3
문자열
  • isEmpty(), isBlank()
  • contains()
  • startsWith(), endsWith()
4
리스트
  • isEmpty()
  • contains(), containsAnyOf()
  • containsExactly(), containsExactlyInAnyOrder()
5
예외
  • assertThatTypeOfException()
  • hasMessage()
  • isInstanceOf()
@ParameterizedTest
하나의 테스트 메서드에서
여러 개의 테스트 입력 값과 함께 반복 실행할 수 있게 해줌
데이터 주입
다양한 데이터 세트로 테스트하기
데이터 관리
테스트 데이터를 체계적으로 관리하기
효율적 테스트
단일 테스트 코드로 여러 케이스 검증하기
@ValueSource

String, short, byte, int, long, float, double, char, boolean, Class 타입까지 지원합니다.
@EnumSource
@NullAndEmptySource, @NullSource, @EmptySource
@CsvSource
@MethodSource
@CsvFileSource, @ArgumentsSource
테스트 코드 범위
코드
내가 푼 알고리즘 문제가 100점으로 통과할까?
내가 만든 유틸성 함수는 버그는 없는걸까?
Class
Class 하나가 1만줄을 넘네...
이 클래스는 어떤 역할을 하는 걸까?
시스템
내가 담당한 레거시 시스템은 어떤 서비스를 제공하는 걸까?
고객
신규 고객 회원 가입로그인이 정상적으로 작동되는 걸까?
주문 취소 버튼을 눌렀을때, 정상적으로 결제 취소가 되는 걸까?
단위 테스트
소프트웨어의 개별 구성 요소(함수, 메소드, 클래스 등)가
정확하게 작동하는지 확인하는 테스트

레고 블록 하나씩 검사하기
통합 테스트
소프트웨어의 여러 구성 요소가
함께 모여서 제대로 작동하는지 확인하는 테스트

레고 블록이 잘 조립됬는지
E2E 테스트
사용자가 소프트웨어를 실제로 사용하는 것처럼
전체 시스템이 처음부터 끝까지 제대로 작동하는지 확인하는 테스트

레고 도시가 의도한대로 잘 작동되는지
단위 테스트, 통합 테스트, E2E 테스트
1
단위 테스트 (Unit Test)
메서드 단위로 독립적으로 검증
2
통합 테스트 (Integration Test)
모듈 간 상호작용 및 연결 검증
3
E2E 테스트 (End-to-End Test)
사용자 관점에서 전체 프로세스 검증
4
인수 테스트 (Acceptance Test)
고객 요구사항 충족 여부 확인
단위 테스트
가능하면 단위 테스트에서 단일 기능에 대해 다양한 상황을 다루고, (예외 상황 포함)
통합/E2E 테스트
통합 테스트, E2E 테스트는 주요 핵심 기능 상황에 초점을 맞추는 것이 좋습니다.
통합 테스트에서의 힘든점
외부 의존성
데이터베이스, API 등 외부 시스템과 연동하는 경우
인프라 의존성
메시징 시스템, 파일 저장소 등 인프라 요소에 의존할 경우
Test Double
테스트를 진행하기 어려운 경우,
이를 대신해 테스트를 진행할 수 있도록 만들어주는 객체

영화 촬영 시 위험한 역할을 대신하는 스턴트 더블에서 비롯되었다.
Test Double
1
Stub
구현을 테스트에 맞게 단순하게 대체하는 것
2
Fake
실제 동작하는 구현을 제공하지만, 제품에는 적합하지 않습니다.
(예시: DB 대신 메모리 사용하여 구현)
3
Spy
호출된 내역을 기록합니다.
4
Mock
Stub과 Spy의 기능을 모두 가지고 있으며,
기대한 대로 상호작용하는지 행위를 검증합니다.
Stub
테스트를 위해 메서드의 행동을 인위적으로 설정
Fake
테스트를 위한 실제 단순 구현체를 활용
Spy
객체의 행동을 감시
일부 메서드 동작에 대해서 인위적 설정 가능
Mock
Stub + Spy
참고: @Mock, @MockBean, @Spy, @SpyBean
  • Mock의 경우
  • 기본적으로 모든 동작을 stub
  • Spy의 경우
  • 기본적으로 모든 동작을 실제 객체 그대로 사용
Mockito 사용법 - Case 1
Mock 객체의 메서드가 특정 값을 리턴한다고 가정
Mockito 사용법 - Case 2
Mock 객체의 메서드에서 예외가 발생한다고 가정
Mockito 사용법 - Case 3
Mock 객체의 Void 메서드가 정상 작동한다고 가정
Mockito 사용법 - Case 4
Mock 객체의 Void 메서드에서 예외가 발생한다고 가정
ArgumentMatchers
Mocking 메서드의 인자로 들어가는 값에 따라 메서드 동작을 유연하게 지정
Verify
Mocking 객체의 특정 메서드가 호출되었는지 확인
ArguementCaptor
Mocking 객체의 특정 메서드가 호출될 때, 어떤 파라미터가 전달되었는지 확인
테스트 코드 Tip
변수나 필드를 사용해서 기댓값 표현하지 않기
과도하게 내부 구현 검증하지 않기
Mockito 사용에 대한 모순점
  • 모의 객체 메서드 호출 검증을 많이 하면,
  • 내부 구현을 조금만 변경해도 테스트가 깨질 가능성이 커진다.
  • 테스트 코드는 내부 구현보다 실행 결과를 검증해야 한다.
중복된 테스트 상황 설정을 @BeforEach에 넣지 않기
  • 각 테스트 메서드가 독립적으로 실행되어야 한다.
  • 각 테스트 코드는 독립적으로 given - when - then을 수행해야 한다.
  • @BeforeEach에 공통적인 상황 설정을 넣으면, 테스트 코드 간 의존성이 생길 수 있다.
  • 데이터 공유 주의하기
  • 모든 테스트가 같은 값을 사용하는 데이터 (공유 OK)
  • (예시) 코드값 데이터, 메타 데이터
  • 테스트 메서드에서만 필요한 데이터 (공유 NO!)
  • (예시) 중복 ID 검사를 위한 회원 데이터
실행 시점이 다르다고 해서 실패하지 않기
  • LocalDateTime.now() 등의 실행 시점에 따라 결과가 달라지는 코드는 테스트하기 어렵다.
  • 파라미터로 받거나, 별도의 시간 클래스를 만들어서 사용하는 것이 좋다.
랜덤하게 실패하지 않기
  • 랜덤하게 값을 생성하는 위임 객체를 만들어서 사용하면, mocking 테스트가 가능해진다.
ATDD란?
Acceptance Test Driven Development
인수테스트?
최종 사용자가 요구한 기능이 제대로 구현되었는지,
소프트웨어가 사용자의 기대에 부합하는지를 확인하는 테스트
ATDD Cycle
TDD vs ATDD
사용자 스토리로 검증하는 기능 테스트
사용자 스토리로 테스트할 시나리오를 지정
ATDD 구현 방법 1 - MockMVC
ATDD 구현 방법 2 - RestAssured
Classist vs Mockist
Classist
  • 실제 구성 요소나 객체를 사용하여 테스트를 수행
  • 코드의 진짜 의존성을 그대로 사용

진짜 재료로 요리하기
Mockist
  • 실제 구성 요소 대신 Test Double을 사용하여
    테스트를 수행
  • 테스트를 위해 의존성을 가짜로 대체

모형 재료로 요리하기
Classist 단위 테스트
테스트를 격리해야하는 대상은 코드가 아니라 또 다른 테스트
테스트 간의 공유하는 의존성이 아니라면 실제 객체를 사용
Mockist 단위 테스트
테스트 대상을 협력객체로 부터 격리하기 위해, 테스트 대상이 의존하는 모든 것을 가짜 객체로 대체
Classical TDD vs Mockist TDD
Outside In
  • 상위 레벨 테스트 부터 시작
  • 테스트 더블을 활용하여 테스트 대상이 의존하는 협력 객체의 예상 결과를 정의
  • 다음 사이클로 테스트 더블로 미리 정의한 협력 객체를 테스트 대상으로 함
Inside Out
  • 도메인 설계가 충분히 이루어진 다음 진행 가능
  • TDD 사이클을 이어나가기가 상대적으로 어려움
  • 프로덕션 코드에 덜 의존적인 테스트가 작성됨
테스트 코드가 가지는 의미
팀 상황과 목표에 맞게 적절하게 개선해 나가자!
테스트 자동화
테스트 코드를 통해 반복적인 테스트를 자동화할 수 있어, 효율성과 일관성을 높일 수 있습니다.
결함 발견
테스트 코드를 통해 프로그램의 오류와 예외 상황을 빠르게 발견하고 해결할 수 있습니다.
협업 증진
테스트 코드는 팀 내 협업을 촉진하고, 코드 품질 보증의 기준을 제공합니다.
테스트 관련 기술 공유
Gradle java-test-fixtures

Toss_service

테스트 의존성 관리로 높은 품질의 테스트 코드 유지하기

혹시 테스트 코드에서도 의존성을 관리해본 적이 있으실까요? 해당 포스트에서는 Gradle의 java-test-fixtures 플러그인을 사용하여 테스트 의존성 관리를 통해 높은 품질의 테스트 코드를 유지하는 방법을 알아봅니다.

TestContainers
테스트 수행 시 필요한 데이터베이스, 메시지 브로커, 웹 서버에 대한 테스트용 일회성 Docker 컨테이너를 제공
WireMock
개발 진행 중 Mocking Server를 제공
SpringBoot Docker-Compose Support
SpringBoot에서 local에서 서버 기동시,
Docker-Compose yaml 파일을 통해 개발 인프라 환경을 자동으로 구성해준다.
Ngrok
로컬에서 실행 중인 서버를 외부 인터넷을 통해 접근 가능
다른 개발자와 로컬에서 개발 중인 서버를 함께 테스트 할 때 유용
# Server 기동 python -m http.server 8080 # ngrok 토큰 활성화 ngrok config add-authtoken ${NGROK_AUTH_TOKEN} # ngrok을 통한 로컬 서버 외부 연결 ngrok http 8080
️ Intellij 사용 Tip - 테스트 클래스 생성
cmd + shift + T
→ 테스트 클래스 생성 or 테스트 클래스 찾기
Intellij 사용 Tip - @Tag 활용
@Tag 어노테이션 활용
Gradle Task 신규 등록
Intellij 사용 Tip - @Tag 활용
@Tag 별로 테스트 실행
@Tag별로 테스트 수행하는 Gradle Task
Intellij 사용 Tip - Live Template 기능
Editor → Live Templates
Reference

edu.nextstep.camp

ATDD, 클린 코드 with Spring

NEXTSTEP에서 개발자들을 위해 디자인된 강의를 수강해보세요.

추천 도서
Github Repository 공유

GitHub

GitHub - rolroralra/hello-tdd

Contribute to rolroralra/hello-tdd development by creating an account on GitHub.

Made with