

분리하는 이유는 관심사의 분리
관심사를 분리해서 책임을 나누고 유지보수 하기 용이하게
테스트 하기 복잡해보인다?
테스트 하기 어려운 영역을 분리해서 테스트 하고자하는 영역에 집중한다.
명시적이고 이해할수 있는 문서 형태로 테스트를 깔끔하게 작성한다.
스프링은 프레임워크이다.
라이브러리
프레임워크
스프링은 프레임워크로써 이미 갖춰진 것들, 제공하고 있는 환경들이 있고 그걸 맞춰서 우리의 코드를 작성해서 끼워넣으면 원하는 대로 동작한다.
사용하는 어노테이션
테스트에 사용하는 어노테이션
@DataJpaTest는 jpa와 관련된 빈들만 주입해서 서버를 띄우기 때문에 @SpringBootTest보다 가볍다.
그러나 강사님은 @SpringBootTest 어노테이션 선호

하위 레이어들을 목킹 처리함
하위에 있는 두 레이어(Business, Presentation)를 가짜 객체로 대신하여 정상 동작한다는 가정하에 테스트 하고자하는 프레젠테이션 레이어에 집중하기 위하여
Mock(가짜) 객체를 사용해 스프링 MVC 동작을 재현할 수 있는 테스트 프레임워크이다. MockMvc를 사용해 컨트롤러의 엔드포인트를 HTTP 메서드(GET, POST, PUT, DELETE 등)를 사용해 실제 HTTP 요청처럼 호출하여 동작을 검증한다.
class ProductControllerTest {
@Autowired
private MockMvc mockMvc;
// .. 중략
}
컨트롤러 레이어를 독립적으로 테스트하려는 경우 MockMvc와 함께 사용한다. 컨트롤러와 관련된 빈만 로드하므로 테스트 속도가 빠르다. Service, Repository 등 다른 계층의 로직은 Mock으로 대체된다.
@WebMvcTest(controllers = ProductController.class)
class ProductControllerTest {
@Autowired
private MockMvc mockMvc;
// .. 중략
}
Mock 객체를 주입하기 위한 어노테이션이다. Mockito라는 라이브러리 사용하며, 컨테이너에 Mockito로 만든 Mock 객체를 주입하낟.
@WebMvcTest(controllers = ProductController.class)
class ProductControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private ProductService productService;
// .. 중략
}
JSON과 자바 객체간의 직렬화, 역직렬화를 처리한다. 테스트 시 요청 본문 또는 응답 데이터를 JSON 형태로 변환하거나 파싱할 때 유용하다.
@WebMvcTest(controllers = ProductController.class)
class ProductControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@MockBean
private ProductService productService;
// .. 중략
}
-> Auditing 이슈
@EnableJpaAuditing
@Configuration
public class JpaAuditingConfig {
}
@WebMvcTest(controllers = ProductController.class)
@MockBean(JpaMetamodelMappingContext.class)
class ProductControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@MockBean
private ProductService productService;
1번으로 config 분리를 했는데도 오류가 발생하여 2번 방법으로 해결했다.
입력 데이터를 검증(Validation)하기 위해 사용된다. DTO 객체나 메서드 매개변수에 붙여 데이터 유효성을 검사한다.
build.gradle에 아래 의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-validation'
Controller
public class ProductController {
// ..중략
public void createProduct(@Valid @RequestBody ProductCreateRequest request) {
// ..중략
}
@NotNull: 값이 null이면 안 됨@NotEmpty: 값이 null이거나 비어 있으면 안 됨@NotBlank: 공백 문자만으로 구성되어 있으면 안 됨@Size(min, max): 문자열 또는 컬렉션의 길이 제한@Email 유효한 이메일 형식이어야 함@Pattern: 정규 표현식에 맞는 값이어야 함@Min: 숫자의 최소값 지정@Max: 숫자의 최대값 지정DTO
// ..중략
public class ProductCreateRequest {
@NotNull(message = "상품 타입은 필수입니다.")
private ProductType type;
@NotBlank(message = "상품 이름은 필수입니다.")
private String name;
@Positive(message = "상품 가격은 양수여야 합니다.")
private int price;
// ..중략
}
@NotNull vs @NotEmpty vs @NotBlank
@NotNull:값이 null이면 안 됨. (빈 문자열이나 공백만 있는 문자열은 통과)@NotEmpty:값이 null이거나 비어 있으면 안 됨. (공백만 있는 문자열은 통과, 빈 문자열은 통과X)@NotBlank:공백 문자만으로 구성되어 있으면 안 됨. (빈 문자열, 공백만 있는 문자열 모두 통과 X. 문자열을 체크할 땐@NotBlank사용 권장)
@Transactional 어노테이션
@Transactional(readOnly = true)로 주면 읽기 전용이 되며, CRUD에서 CUD 작업이 동작하지 않고 오직 R만 가능하다.
JPA에선 @Transactional(readOnly = true)를 주면 CUD 스냇샵 저장, 변경감지 기능이 동작하지 않아 성능이 향상된다.
@Transactional을 통해 CQRS 패턴으로 동작 가능하다.
"CQRS - Command와 Query를 분리하자"
즉, CUD(쓰기)와 R의(읽기) 책임을 분리 하는 것이다.
Write DB와 Read DB의 엔드 포인트를 분리함으로써 장애 격리가 가능하다.
@Transactional 어노테이션 사용 시 클래스 상단에 @Transactional(readOnly = true)를 걸고
쓰기 작업이 필요한 메서드라면 메서드 단위로 @Transactional을 달자
출처 - 박우빈, Practical Testing: 실용적인 테스트 가이드
이 블로그에 포함된 모든 코드와 이미지는 원작자이신 박우빈 강사님의 저작권에 귀속됩니다.