Post

Tdd 과정

Tdd 과정

TDD(Test Driven Development) 정리

1. TDD란?

TDD는 테스트 코드를 먼저 작성한 후 테스트를 통과시키기 위한 실제 코드를 구현하는 개발 방식이다.

1. 테스트 코드 작성
2. 컴파일 에러 / 실패 (빨간 줄)
3. 테스트를 통과시키는 실제 코드 작성
4. 리팩토링





2. Test 관련 Annotation

@SpringBootTest

  • Spring Boot 애플리케이션 전체를 통합 테스트할 때 사용

  • 실제 설정된 DB 사용 가능

  • 테스트 속도가 상대적으로 느림



@DataJpaTest

  • JPA 관련 컴포넌트만 빠르게 테스트할 때 사용

  • 인메모리 DB(H2 등) 사용


실제 DB를 사용하고 싶다면 아래 어노테이션을 추가한다.

1
2
3
@AutoConfigureTestDatabase(
    replace = AutoConfigureTestDatabase.Replace.NONE
)



@Disabled

  • 테스트 실행을 비활성화
  • 임시로 테스트를 제외하고 싶을 때 사용



@MockBean

  • @WebMvcTest에서 주로 사용
  • 가짜 객체(Mock)를 Spring Bean으로 등록

특징:

  • Controller까지만 생성
  • Service, Repository는 실제 객체 생성 ❌
  • 협력 객체는 @MockBean으로 대체
1
2
3
Controller O
Service     X → Mock
Repository  X





3. SpringBootTest에서 Mock 사용

Mock이란?

Mock은 가짜 객체로 객체의 행위를 검증하기 위해 사용된다.



테스트 더블(Test Double)

  • 테스트를 진행하기 어려운 객체를 대신하는 객체
  • Mock은 테스트 더블의 한 종류



SpringBootTest + Mockito

SpringBootTest 환경에서 테스트 더블을 사용할 때 주로 Mockito를 사용한다.

Mockito의 주요 기능은 다음과 같다.

1
2
3
4
1. Mock 생성 (Create Mock)
2. 동작 정의 (Stub)
3. 사용 (Exercise)
4. 검증 (Verify)



@Mock vs @MockBean

@Mock

  • Spring Container 필요 ❌
  • 단순 단위 테스트

@MockBean

  • Spring Container 필요 ⭕
  • Bean이 컨테이너에 존재해야 할 때 사용

Spring Context가 필요하고 해당 객체가 Bean이라면 → @MockBean, 그렇지 않다면 → @Mock





4. Model 관련 Annotation

@Entity

  • JPA가 관리하는 클래스
  • DB 테이블과 매핑되는 객체



@Data

Lombok 어노테이션을 모두 포함한다.

1
2
3
4
5
@ToString
@EqualsAndHashCode
@Getter
@Setter
@RequiredArgsConstructor

*개별 옵션을 세밀하게 제어해야 할 경우 @Data 대신 개별 어노테이션 사용 권장





5. @Builder 패턴

Builder를 사용하는 이유

생성자 파라미터가 많아질수록 가독성이 떨어지고 매개변수 순서를 헷갈리기 쉽다

1
2
Registry registry =
    new Registry("hello", "hi", "uni", "etc");

위 코드는 각 값이 어떤 의미인지 한눈에 파악하기 어렵다.



Builder 패턴 적용

1
2
3
4
5
6
7
8
public Registry toEntity() {
    return Registry.builder()
            .id(registryId)
            .title(registryTitle)
            .main(registryMain)
            .writer(registryWriter)
            .build();
}
  • 어떤 값이 어떤 필드에 들어가는지 명확
  • 가독성 향상
  • 선택적 파라미터 처리 용이





6. this를 이용한 객체 필드 초기화

1
2
3
4
5
6
public Registry(RegistryDto registryDto) {
    this.registryId = registryDto.getRegistryId();
    this.registryTitle = registryDto.getRegistryTitle();
    this.registryMain = registryDto.getRegistryMain();
    this.registryWriter = registryDto.getRegistryWriter();
}

this는 현재 객체를 가리키며

Dto로부터 전달받은 값을 Entity의 필드에 초기화하기 위해 사용된다.





Error

Controller를 tdd Post 코드를 작성 하면서 오류를 해결한 과정을 적어봤다.



☝🏻 DTO

image

처음에 내가 dto에 값을 담아준 것으로 생각하고 왜 에러가 떴는지 몰랐다.

알고보니 dto에 값을 담아준 것이 아니라 이 자리에 BoardDto가 들어와야 한다고 알려준 것이다.

그리고 dto로 설정에서 dto에 담아서 보내줘야 한다.

그래서 new BoardDto를 작성해준 것이다.



GET도 dto로 작성?


내가 작성한 get 코드는 아래처럼 dto를 사용하지 않았다.

1
2
3
given(boardService.getRegistry("23")).willReturn(
    new Board("23", "title", "main", "writer")                       
);

그 이유는 본 코드를 보면 알 수 있는데 파라미터를 post에서는 dto를 담고 있고

get에서는 String BoardId를 담고 있었기 때문에 dto가 아닌 값을 주어서 dto를 사용하지 않은 것이다.

무조건 dto를 쓰는 것이 아니라 어떻게 짜냐에 따라 쓸 수 있고 안쓸 수도 있다.




✌🏻 메모리 주소가 달라서 생기는 오류

1번에서 말한 대로 수정한 후에 실행을 했더니 에러가 나타났다.

image


ERROR : java.lang.AssertionError: No value at JSON path "$.boardId"

new Dto → any(Dto.class)로 해결

1
2
given(boardService.saveRegistry(new BoardDto("23","title", "main", "writer")))
    .willReturn(new Board("23","title", "main", "writer"));

⬇️

1
2
given(boardService.saveRegistry(any(BoardDto.class)))
    .willReturn(new Board("23","title", "main", "writer"));

new BoardDto(~~)any(BoardDto.class)로 변경했다.


RegistryDto 클래스 타입으로 들어오는 값을 사용한다는 의미로

new 로 새로운 객체를 만들어서 작성하지 않고 구현체 객체를 사용해 메모리 주소가 다르지 않아서 오류가 해결 된 것 같다.

본 코드처럼 dto는 들어오는 값을 쓴다라는 느낌





REFERENCE

This post is licensed under CC BY 4.0 by the author.