Test Driven Development(TDD)
- 테스트 주도 개발 기법
- 프로그램의 설계와 구현, 사고의 흐름을 테스트 중심으로 생각하는 개발 방법
- 개발 순서의 변화
- as - is : 구현 -> 테스트
- to - be : 테스트를 만든다 -> 구현
- 익스트림 프로그래밍(XP), 애자일, 폭포수 모델, Test-First Programming
- 개발 사이클
- RED : 실패하는 테스트를 설계(요구사항의 명세)
- GREEN : 테스트를 성공시킨다(구현)
- REFACTOR : 구현 코드를 리팩토링
- 테스트 구조
- Given(Arrange) : 상태(state)의 정의(테스트를 수행 할 때 전제 조건)
- When(Act) : 동작(테스트 실행)
- Then(Assert) : 검증(동작의 결과(actual)과 예상값(expected)를 비교하여 검증)
- TDD를 하는 이유
- 개인의 입장 : 뭘 하려는지에 대한 사실을 스스로 지속적으로 확인하고 명확하게 하기 위해서
- 프로젝트 중 막막하거나 막히는 부분해 대해 개발이 지연되는 부분을 방지
- 팀의 입장 : 내가 하는 일 도는 동료가 하는 일에 대해 명확히 알기 위해서 동료와 소스 코드로 공유하고 소통(코드 리뷰) 해야한다.
- 개발 계획을 문서와 구현 코드를 공유하고 테스트 코드를 공유하며 무슨 부분을 진행하고 있는지, 구현의 진행도 및 척도에 대해 파악 할 수 있다.
- 개인의 입장 : 뭘 하려는지에 대한 사실을 스스로 지속적으로 확인하고 명확하게 하기 위해서
- 테스트 개발을 효율적으로 하기 위한 방법
- 테스트 설계 흐름에 익숙해야한다.
- 사람의 요구사항을 프로그램이 할 수 있는 기능으로 변환
- 기능을 단위 기능으로 세분화
- 기능의 관계와 상호작용을 설계
- 테스트 작성 기술을 숙련하는 과정 필요
- 테스트 설계 흐름에 익숙해야한다.
- 단계
- 메인 요구사항의 기본 목표 위주로만 우선 테스트 작성
- 메인 요구사항 기본 + 세부 목표를 테스트로 작성
- 메인 요구사항 기본 + 세부 + 더욱 구체적인 기능적 고려
@Getter // Lombok에서 getter 메서드를 자동으로 생성합니다.
@ToString // Lombok에서 toString 메서드를 자동으로 생성합니다.
@EqualsAndHashCode(callSuper = true) // 부모 클래스의 필드를 고려하여 equals 및 hashCode 메서드를 생성합니다.
public class ApiDataResponse extends ApiErrorResponse { // ApiErrorResponse를 확장한 ApiDataResponse 클래스 선언
private final Object data; // 응답 데이터를 저장하는 필드
private ApiDataResponse(boolean success, Integer errorCode, String message, Object data) {
// 부모 클래스의 생성자를 호출하여 상태 설정
super(true, ErrorCode.OK.getCode(), ErrorCode.OK.getMessage());
this.data = data; // 데이터를 설정합니다.
}
public static ApiDataResponse of(boolean success, Integer errorCode, String message, Object data) {
// 새로운 ApiDataResponse 객체를 생성하고 반환합니다.
return new ApiDataResponse(success, errorCode, message, data);
}
}
기존 Test Code
class ApiDataResponseTest { // ApiDataResponse 클래스를 테스트하는 테스트 클래스 선언
@Test // JUnit에게 이 메서드가 테스트 메서드임을 알립니다.
void test(){ // test 메서드 정의
// Given 테스트에 사용될 데이터를 설정합니다. 여기서는 "test data"를 설정했습니다.
String data = "test data"; // 테스트 데이터 설정
// When ApiDataResponse.of 메서드를 호출하여 응답 객체를 생성합니다.
ApiDataResponse response = ApiDataResponse.of(true, ErrorCode.OK.getCode(), ErrorCode.OK.getMessage(), data);
// ApiDataResponse의 of 메서드를 호출하여 응답 객체를 생성합니다.
// Then 생성된 응답 객체를 검증합니다. response 객체가 올바른지 확인하기 위해 success, errorCode, message, data 필드가 예상된 값과 일치하는지 확인합니다.
assertThat(response) // response 객체에 대한 검증 시작
.hasFieldOrPropertyWithValue("success", true) // success 필드가 true인지 확인
.hasFieldOrPropertyWithValue("errorCode", ErrorCode.OK.getCode()) // errorCode 필드가 OK 코드와 일치하는지 확인
.hasFieldOrPropertyWithValue("message", ErrorCode.OK.getMessage()) // message 필드가 OK 메시지와 일치하는지 확인
.hasFieldOrPropertyWithValue("data", data); // data 필드가 테스트 데이터와 일치하는지 확인
}
}
TDD Style Refactoring
@Getter // Lombok에서 getter 메서드를 자동으로 생성합니다.
@ToString // Lombok에서 toString 메서드를 자동으로 생성합니다.
@EqualsAndHashCode(callSuper = true) // 부모 클래스의 필드를 고려하여 equals 및 hashCode 메서드를 생성합니다.
public class ApiDataResponse<T> extends ApiErrorResponse { // ApiErrorResponse를 확장한 ApiDataResponse 클래스 선언
private final T data; // 응답 데이터를 저장하는 필드
private ApiDataResponse(T data) { // 생성자 정의
super(true, ErrorCode.OK.getCode(), ErrorCode.OK.getMessage()); // 상위 클래스의 생성자를 호출하여 상태 설정
this.data = data; // 데이터를 설정합니다.
}
public static <T> ApiDataResponse<T> of(T data) { // 데이터를 받아 새로운 ApiDataResponse 객체를 생성하여 반환하는 정적 메서드
return new ApiDataResponse<>(data); // 새로운 ApiDataResponse 객체를 생성하여 반환합니다.
}
public static <T> ApiDataResponse<T> empty() { // 빈 응답을 생성하여 반환하는 정적 메서드
return new ApiDataResponse<>(null); // 빈 응답을 생성하여 반환합니다.
}
}
class ApiDataResponseTest { // ApiDataResponse 클래스를 테스트하는 테스트 클래스 선언
@DisplayName("문자열 데이터가 주어지면, 표준 성공 응답을 생성한다.")
@Test // JUnit에게 이 메서드가 테스트 메서드임을 알립니다.
void givenStringData_whenCreatingResponse_thenReturnsSuccessfulResponse(){ // test 메서드 정의
// Given 테스트에 사용될 데이터를 설정합니다. 여기서는 "test data"를 설정했습니다.
String data = "test data"; // 테스트 데이터 설정
// When ApiDataResponse.of 메서드를 호출하여 응답 객체를 생성합니다.
ApiDataResponse<String> response = ApiDataResponse.of(data);
// ApiDataResponse의 of 메서드를 호출하여 응답 객체를 생성합니다.
// Then 생성된 응답 객체를 검증합니다. response 객체가 올바른지 확인하기 위해 success, errorCode, message, data 필드가 예상된 값과 일치하는지 확인합니다.
assertThat(response) // response 객체에 대한 검증 시작
.hasFieldOrPropertyWithValue("success", true) // success 필드가 true인지 확인
.hasFieldOrPropertyWithValue("errorCode", ErrorCode.OK.getCode()) // errorCode 필드가 OK 코드와 일치하는지 확인
.hasFieldOrPropertyWithValue("message", ErrorCode.OK.getMessage()) // message 필드가 OK 메시지와 일치하는지 확인
.hasFieldOrPropertyWithValue("data", data); // data 필드가 테스트 데이터와 일치하는지 확인
}
@DisplayName("데이터가 없을 때, 비어있는 표준 성공 응답을 생성한다.") // 테스트 케이스의 이름 설정
@Test // JUnit에게 이 메서드가 테스트 메서드임을 알립니다.
void givenNothing_whenCreatingResponse_thenReturnsEmptySuccessfulResponse() { // givenNothing_whenCreatingResponse_thenReturnsEmptySuccessfulResponse 메서드 정의
// Given
// 테스트에 사용될 데이터가 없으므로 아무 작업도 수행하지 않습니다.
// When
ApiDataResponse<String> response = ApiDataResponse.empty(); // ApiDataResponse 클래스의 empty 메서드를 호출하여 응답 객체를 생성합니다.
// Then
assertThat(response) // response 객체에 대한 검증 시작
.hasFieldOrPropertyWithValue("success", true) // success 필드가 true인지 확인
.hasFieldOrPropertyWithValue("errorCode", ErrorCode.OK.getCode()) // errorCode 필드가 OK 코드와 일치하는지 확인
.hasFieldOrPropertyWithValue("message", ErrorCode.OK.getMessage()) // message 필드가 OK 메시지와 일치하는지 확인
.hasFieldOrPropertyWithValue("data", null); // data 필드가 null인지 확인
}
}
컬렉션 팩토리 of
- Arrays 클래스는 List를 제외한 Set, Map 같은 다른 형태의 자료구조 팩토리 메서드를 지원하지 않는다는 단점이 있다.
- 해결하기 위해선 리스트를 인수로 받거나 스트림 API 등의 방법을 통해 해결해야 한다.
- 내부적으로 불필요한 객체 할당을 필요로 하는 단점이 있다.
- 자료구조 내부가 변환할 수 있다
- 자바 9부터는 컬렉션 API를 개선하여 of라는 팩토리 메서드를 제공해준다. List, Set, Map은 다음과 같이 of 팩토리 메서드를 이용해서 간단하게 생성할 수 있다.
List<Integer> list = Arrays.asList(1, 2, 3); Set<Integer> set = Set.of(1, 2, 3); Map<Integer, String> map = Map.of(1, "A", 2, "B", 3, "C");
- 가변 인수를 활용한 of() 메서드 하나만 있으면 해결할 수 있는데 왜 굳이 0~10개의 매개변수를 가지는 of() 메서드를 오버로딩하여 구현해놓은 것일까?
- 불필요한 비용을 제거하기 위해서이다.
- 사용하지 않을 추가적인 배열을 할당 및 초기화한 다음 가비지 컬렉션을 통해 제거해야 하는 비용이 추가적으로 필요하기 때문에 일반적으로 활용되는 0~10개의 매개변수를 가지는 of() 메서드를 미리 구현
'개발 > Spring' 카테고리의 다른 글
Validation (0) | 2024.02.19 |
---|---|
MVC 패턴(3) - 비즈니스 로직 구현 (0) | 2024.02.19 |
Controller Test (0) | 2024.02.18 |
ControllerAdvice (0) | 2024.02.17 |
MVC 패턴 - API 설계(2) (0) | 2024.02.17 |