TDD
test코드를 먼저 작성 후에 빨간 줄이 뜨는 메소드를 실행 전에 실제 코드에 작성해주면 되는 형식이 tdd
관련 글 : TDD란?
annotation
tdd를 작성하면서 새로 알게 된 부분들은 설명이 있고 원래 알고 있는 어노테이션은 간략하게 적었다.
test
■ @SpringBootTest
: 통합 테스트를 제공하는 기본적인 어노테이션
여러 단위 테스트를 하나의 통합된 테스트로 수행할 때 사용한다.
단, 어플리케이션의 설정을 모두 로드하기 때문에 어플리케이션의 규모가 클수록 느려진다.
■ @DataJpaTest
: SpringBoot에서 JPA를 테스트 할 수 있도록 제공하는 어노테이션
단위 테스트가 끝날 때 마다 자동으로 DB를 롤백시켜준다.
인메모리 DB를 사용한다.
실제 DB에서 테스트를 하고 싶다면 @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
어노테이션을 추가한다.
- 참고
Annotation | 설명 | Bean |
---|---|---|
@SpringBootTest | 통합 테스트, 전체 | Bean 전체 |
@WebMvcTest | 단위 테스트, Mvc 테스트 | MVC 관련된 Bean |
@DataJpaTest | 단위 테스트, Jpa 테스트 | JPA 관련 Bean |
@RestClientTest | 단위 테스트, Rest API 테스트 | 일부 Bean |
@JsonTest | 단위 테스트, Json 테스트 | 일부 Bean |
■ @Disabled
: 테스트를 실행하지 않게 설정하는 어노테이션
■ @AutoConfigureMockMvc
: 이 어노테이션을 통해 MockMvc를 Builder 없이 주입 받을 수 있다.
■ @ExtendWith(SpringExtension.class)
: JUnit5의 lifecycle에 test에서 사용할 기능을 확장할 때 사용된다.
Spring TestContext Framework와 Junit5와 통합하여 사용하게 된다.
■ @Import(BoardServiceImpl.class)
: BoardServiceImpl도 설정파일임을 명시
■ @MockBean
: @WebMvcTest를 이용한 테스트에서 사용할 수 있다.
@WebMvcTest는 Controller를 테스트할 때 주로 이용되며,
단일 클래스의 테스트를 진행하므로 @MockBean을 통해 가짜 객체를 만들어 준다.
→ Controller 객체까지만 생성되고 Service 객체는 생성하지 않는다.
@MockBean은 위와 같이 Bean 컨테이너에 객체(Service)가 있어야 다른 객체(Controller)와 협력할 수 있는데,
객체를 만들 수 없는 경우 @WebMvcTest
에 사용할 수 있다.
SpringBootTest에서 Mock 사용(테스트 더블)
mock이란 가짜 객체라고 불리며, 행위를 검증하기 위해 사용되는 객체이다.
테스트 더블 : 테스트를 진행하기 어려운 경우 테스트를 대신 진행할 수 있게 만드는 객체
*Mock 객체의 상위호환
SpringBootTest에서 테스트 더블 → Mockito
Mockito는 아래와 같은 동작들을 할 수 있다.
■ Mock 만들기(CreateMock)
■ Mock의 동작 지정 (Stub)
■ Mock의 사용(Exercise)
■ 검증(Verify)
@Mock
와 @MockBean
은 Mock 객체를 선언할 때 쓰이는 어노테이션이다.
Spring Boot Container가 테스트 시에 필요하고,
Bean이 Container에 존재한다면 @MockBean
을 사용하고 아닌 경우에는 @Mock
을 사용한다.
model
■ @entity
: JPA가 관리하는 클래스
■ @Data
: @ToString
, @EqualsAndHashCode
, @Getter
, @Setter
, @RequiredArgsConstructor
이 포함된 어노테이션
*개별 어노테이션의 설정 값을 기본 값이 아닌 값을 사용할 때는 @Data
대신 개별 어노테이션을 사용한다.
■ @Builder
@Builder
: 매개변수가 많아지면 코드를 읽기 어렵고 어떤 순서로 매개변수를 넣어야하는지 헷갈리는데
@Builder
를 활용하면 가독성이 좋아진다.
Registry registry = new Registry("hello", "hi", "uni", "etc")
Registry 클래스는 생성자 파라미터를 4개를 받지만 파라미터로 받아야하는 값이 많아지면
각 값들이 어떤 값을 의미하는지 이해하기 힘들다.
이를 @Builder
패턴으로 구현하면 무슨 값을 의미하는지 파악하기 쉽다.
만약 파라미터가 5개인 경우에 5개 파라미터를 모두 사용하는 경우에는 직접 설정하는 대신 @Builder
만 작성해도 된다.
5개 중에 한 두개만 사용하는 경우에는 직접 설정한다.
모델명.테이블 명(설정할 변수 명)
*모델명 생략
1
2
3
4
5
6
7
8
public Registry toEntity(){
return Registry.builder()
.id(registryId)
.title(registryTitle)
.main(registryMain)
.writer(registryWriter)
.build();
}
*생성자 체인
생성자 체인은 this 또는 super 키워드를 사용해서 생성자에서 다른 생성자를 호출하는 기술
Java에서 생성자의 이름을 직접 호출할 수 없으므로 this와 super 키워드를 사용한다.
this는 동일한 클래스의 생성자에서 다른 생성자를 호출할 때 사용한다.
super는 자식 클래스 생성자에서 부모 클래스 생성자를 호출할 때 사용한다.
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는 entity를 의미
dto를 이용해서 체인 기술을 사용했다.
Error
Controller를 tdd Post 코드를 작성 하면서 오류를 해결한 과정을 적어봤다.
☝🏻 DTO
처음에 내가 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번에서 말한 대로 수정한 후에 실행을 했더니 에러가 나타났다.
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는 들어오는 값을 쓴다라는 느낌
출처
[Java] Lombok @Data 어노테이션
빌더 패턴(Builder pattern)을 써야하는 이유, @Builder
Builder란?
[Java]생성자 체인(Constructor Chaining)
Spring Junit5 Test정리
[Spring Boot]스프링부트 사용하기 - Structuring Your Code / Configuration Classes(a.k.a Bean 설정) / Auto-configuration
[Mock과 Mocktio] @Mock @MockBean
JPA repository.save is Null (NullPointerException) - feat. @RunWith & @SpringBootTest & @DataJpaTest