코알못
[TDD] SpringBoot 를 통한 TDD 손쉬운 작성 - 02) TDD 작성 해보기! 본문

TDD 를 예시를 통해 알아보자 !
우리는 회원관리 시스템을 만든다고 하자! 그 중 유저 생성 API 로 TDD 를 수행해보자!
1) 테스트 시나리오 작성
- 유저 생성 API 호출하여 정상 처리시 상태 코드 200을 리턴한다.
- 유저 생성 API 호출시 이름, ID, 패스워드 정보를 전달 받으며 이 중 하나라도 없다면 상태 코드 400을 리턴한다.
- 이름 파라미터는 세자리이며 맞지 않을 경우 상태코드 400을 리턴한다.
- ID 파라미터는 영어로만 이뤄지며 맞지 않을 경우 상태코드 400을 리턴한다.
- 패스워드 파라미터는 영문자 숫자로 이뤄지며 최소 4자리 이상이여야 하며 맞지 않을 경우 상태코드 400을 리턴한다.
2) API 설계 문서화
요청 정보
- 메서드 : POST
- 경로 : /user
- 헤더 : Content-Type: application/json
- 본문 :
User {
name: string,
id : string,
password: string
}
성공 응답
- 상태 코드 : 200 Ok
정책
- name 파라미터는 3자 이여야 한다.
- id 파라미터는 영어로만 이뤄져야 한다.
- password 파라미터는 영문 또는 숫자로 구성되며 4자리 이상이여야 한다.
테스트 항목
- 정상 요청 → 200 반환
- 파라미터 없음 → 400 반환
- 형식 오류 → 400 반환
3) 테스트 코드 작성
AAA 패턴
테스트 코드는 보통 Arrange - Act - Assert(AAA) 패턴으로 작성한다.
| 명칭 | 내용 |
| Arrange(구성) | 테스트용 데이터 준비 |
| Act(실행) | 테스트 대상 실행 |
| Assert(검증) | 결과 검증 |
우선 요청 파라미터를 담을 Recode 를 만든다!
public record User(
String name,
String id,
String password
) {
}
그리고 테스트 시나리오대로 테스트 케이스를 만든다.
우선 첫번째 시나리오인 성공시 200을 리턴하는 테스트 케이스를 작성 하였다.
// [1] SpringBootTest
@SpringBootTest(
classes = [테스트대상클래스명].class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
// [2] DisplayName
@DisplayName("POST /user")
public class POST_specs {
// [3] Test
@Test
void 성공시_200_리턴(
@Autowired TestRestTemplate client
) {
// [4] Arrange
User user = new User(
"corin",
"test",
"test"
);
// [5] Act
ResponseEntity<Void> response = client.postForEntity(
"/user",
user,
Void.class
);
// [6] Assert
assertThat(response.getStatusCode().value()).isEqualTo(200);
}
}
하나씩 보자!
[1] @SpringBootTest 어노테이션
해당 어노테이션은 Spring Boot 환경에서 테스트를 실행하도록 설정하는 부분이다.
- classes 옵션
미지정 시 전체 빈 스캔하게 되어 모든 컨텍스트를 스캔, 빈 생성 하기에 로딩 시간을 줄이고 싶다면
테스트 진행 대상만 클래스를 지정하여 필요한 빈만 로딩하여 테스트 속도를 향상 할 수 있다.
- webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT 옵션
테스트 실행시 랜덤 포트로 실행 되도록 하여 테스트 실행시 사용중인 포트 이외 포트를 사용하여 테스트 실행 오류를 방지 한다.
[2] @DisplayName 어노테이션
테스트 결과 화면에 표시되는 이름을 설정 하는 어노테이션으로 HTTP 메서드명, API 주소를 표기하여 어떤 테스트 인지 알 수 있도록 한다.
[3] @Test 어노테이션
메서드에 @Test 를 붙여 테스트 메서드로 동작하도록 한다.
[4] TestRestTemplate 활용
Spring Boot Test에서는 Http 요청 관련 테스트를 위한 TestRestTemplate을 제공한다. RestTemplate 가 아닌 Test 에서 제공하는 클래스를 이용하면 테스트에 용이하게 정의 되어있어 (예 : 500 에러 시 예외 대신 응답 반환) Http 관련 테스트 진행시 해당 클래스를 이용한다.
[5] Arrange 단계
정의 단계로, API 요청 파라미터인 User를 정의 한다.
[6] Act 단계
실행 단계로, TestRestTemplate 를 이용하여 /user API 로 Post 호출 하며 실행 결과를 얻어온다.
[7] Assert 단계
검증 단계로, 보통 SpringBootTest 에 포함 되어 있는 AssertJ 라이브러리를 사용하여 검증을 코드로 구현한다.
아래 코드는 http 상태코드를 받아 200이면 테스트 검증 통과 처리 하며 아니라면 실패 처리 한다.
assertThat(response.getStatusCode().value()).isEqualTo(200);
해당 테스트 실행 결과를 보자 !!
아래와 같이 왼쪽 박스 부분을 보면, DisplayName 에 정의한 내용과 메서드 명이 표기 되어 어떤 테스트인지 한눈에 알 수 있다.
그리고 테스트 결과는 '초록색'이 아닌 '노란색 X 박스'로 표기 되어 검증 실패임을 알 수 있고,
그 원인을 오른쪽 박스의 오류 로그를 보면,
테스트 코드로 작성한 '상태 코드 200' 이기대한 값이지만 404로 나와 검증 실패 되었음을 알 수 있다!
이렇게 켄트 백의 TDD 중 Red 부분 작성이 완료 되었다!

이제 Green 단계를 진행해보자!
해당 코드가 통과 할 수 있는 코드를 작성하면 된다. 아래 조건을 만족하면 200을 리턴하면 된다.
- name 파라미터는 3자 이여야 한다.
- id 파라미터는 영어로만 이뤄져야 한다.
- password 파라미터는 영문 또는 숫자로 구성되며 4자리 이상이여야 한다.
이제 200이 나올 수 있도록 작성을 해보자!
@PostMapping("/user")
public ResponseEntity<?> user(@RequestBody User user) {
if (user.name().length() == 3 && user.id().matches("^[a-zA-Z]*$") && user.password().matches("^[a-zA-Z0-9]{4,}$")) {
return ResponseEntity.ok().build();
}
return ResponseEntity.badRequest().build();
}
돌려보면 정상적으로 패스 한다.

여기까지 하면 Green 단계 한 사이클을 완료한 것이다.
이제 다음 케이스인 '파라미터 없음 → 400 반환'를 만들어 나머지 Red + Green 단계 진행 해보자!
@Test
void 파라미터없을시_400_리턴(@Autowired TestRestTemplate client) {
// Arrange
User user = new User(
null,
null,
null
);
// Act
ResponseEntity<Void> response = client.postForEntity(
"/user",
user,
Void.class
);
// Assert
assertThat(response.getStatusCode().value()).isEqualTo(400);
}
이렇게 해서 돌리게 되면 실패하게 되고

파라미터 값이 없을경우 400이 나오도록 코드를 추가한다.
@PostMapping("/user")
public ResponseEntity<?> user(@RequestBody User user) {
if(user.name() == null || user.id() == null || user.password() == null) {
return ResponseEntity.badRequest().build();
}
그리고 돌려보면 테스트 정상 통과 한다!

그런데 여기서 잘못된 파라미터가 넘어올시 400 리턴되는 케이스가 필요하다는 생각이 들었다.
이러한 테스트 케이스 추가하는 작업이 Green 단계에서 이뤄진다. 계속해서 체크하며 좋은 테스트 케이스를 만든다!
그럼 실습을 위해 id 파라미터에 대해서만 테스트 케이스 추가 작업 해보도록 한다.
id 파라미터는 영어만 받기로 정했기에 '숫자, 특수문자, 영어+숫자' 를 호출했을때 400이 나오는 케이스를 추가한다.
하나의 테스트 케이스에 특정 값만 변경해서 호출하고 싶으면 '@ParameterizedTest' 와 '@ValueSource' 어노테이션을 이용하여 반복 테스트 가능하다.
id 파라미터 예시로 보자!
@ParameterizedTest
@ValueSource(strings = {
"1243",
"*&^%",
"test1234"
})
void id_잘못된파라미터_400_리턴(String id, @Autowired TestRestTemplate client) {
// Arrange
User user = new User(
"코린이",
id,
"test"
);
// Act
ResponseEntity<Void> response = client.postForEntity("/user", user, Void.class);
// Assert
assertThat(response.getStatusCode().value()).isEqualTo(400);
}
@ValueSource 로 변경 하여 테스트 하고자 하는 값을 나열하고 테스트 매개변수 id 로 받으면 해당 값이 변경되며 호출 된다.
결과는 아래와 같으며 모든 케이스 통과 하였다!

이제 Refactor 단계를 진행해보자!
작성한 코드를 보면 보기 어렵게 되어있고 좋지 않은 코드이다.
이렇게 코드를 개선 하는것이 Refactor 과정이다! 아래 변경을 해보자!
@PostMapping("/user")
public ResponseEntity<?> user(@RequestBody User user) {
if(user.name() == null || user.id() == null || user.password() == null) {
return ResponseEntity.badRequest().build();
}
if (user.name().length() == 3 && user.id().matches("^[a-zA-Z]*$") && user.password().matches("^[a-zA-Z0-9]{4,}$")) {
return ResponseEntity.ok().build();
}
return ResponseEntity.badRequest().build();
}
코드를 가독성 좋게 기능을 메서드로 뺐다.
@PostMapping("/user")
public ResponseEntity<Void> user(@RequestBody User user) {
if (hasEmptyParam(user)) {
return ResponseEntity.badRequest().build();
}
if (!isValidUser(user)) {
return ResponseEntity.badRequest().build();
}
return ResponseEntity.ok().build();
}
/** null 체크 */
private boolean hasEmptyParam(User user) {
return user.name() == null
|| user.id() == null
|| user.password() == null;
}
/** 유효성 검사 */
private boolean isValidUser(User user) {
return isValidName(user.name())
&& isValidId(user.id())
&& isValidPassword(user.password());
}
private boolean isValidName(String name) {
return name.length() == 3;
}
private boolean isValidId(String id) {
return id.matches("^[a-zA-Z]+$");
}
private boolean isValidPassword(String password) {
return password.matches("^[a-zA-Z0-9]{4,}$");
}
이제 테스트 케이스 모두 돌려서 모든 케이스를 통과하는지 최종 점검 한다.

모두 통과 하였으며 TDD 마지막 단계까지 완료 하였다!
최종 정리 !
지금까지 TDD와 테스트 작성 방법을 정리해 보면 다음과 같다고 한다.
- TDD는 테스트 먼저 작성하고 기능 개발 하며 코드 개선하는 방식이다.
- Red → Green → Refactor 구조로 반복된다.
- 실무에서는 하이브리드 TDD를 많이 사용한다.
- 테스트 코드 작성시에는 AAA 패턴으로 작성 한다.
테스트는 귀찮은 작업이 아니라,
미래의 나를 지켜주는 보험이라고 한다.
꾸준히 연습하면서 TDD에 익숙해져 보자!
* 참고
# Java 에서 record란 무엇인가?
record는 Java 17부터 도입된 문법으로 불변 데이터를 정의하는 VO 와 같은 객체를 정의 할때 '보일러 플레이트 코드'를 줄여준다.
특징
- 불변 객체 (필드에 final 이 붙는다.)
- getter, 생성자(모든 변수 정의된 생성자), toString 자동 생성
보일러 플레이트란?
반복적으로 작성하는 코드를 의미한다. (같은 내용을 인쇄하는 변경 불가능한 판의 이름이 보일러 플레이트다.)
VO 같은 클래스에 자주 등장한다고 한다.
'JAVA' 카테고리의 다른 글
| [TDD] SpringBoot 를 통한 TDD 손쉬운 작성 - 01) TDD 란 ? (0) | 2026.02.16 |
|---|---|
| [Querydsl] 03. 기본 문법 (fetchJoin, case, 상수문자더하기) (0) | 2024.03.31 |
| [Querydsl] 02. 기본 문법 (Q-Type,검색조건쿼리,결과조회,정렬,페이징,집합) (0) | 2024.01.28 |
| [Querydsl] 01. 소개 및 프로젝트 설정 (0) | 2024.01.28 |
| [JPA] JPA 정복하기 - 05. 스프링 데이터 JPA 분석 (1) | 2024.01.21 |
