이번 5회차에서는 다양하지만 전부 중요한 개념들을 학습하고 실습해보았습니다.
어떻게 하면 더 좋은 코드를 작성할 수 있을까에 초점을 두어 학습을 진행하였고, 아직도 모르는 것이 많지만 차근차근 진행해보겠습니다.
각 기능마다 동작하는지 확인하는 작업으로써, 개발자에게 꼭 필요한 검증 과정입니다.
테스트 코드를 작성함으로써 기능이 정상적으로 작동하는지 빠르게 확인하여 수정할 수 있습니다.
여기서는 단위 테스트를 적용시켜보았습니다.
Junit5
는 자바 진영의 테스트 프레임워크입니다. 테스트에 필요한 검증 메서드 등을 제공합니다.
Mockito
는 실제 테스트할 기능만 테스트 하기 위해, Service 로직이나 DB 로직 의존성 대신 가짜 객체를 만들어 미리 해당 로직에 대한 결과를 만들고 이를 사용합니다.
계층형 구조에 따라 역할이 나눠져 있으므로 테스트도 계층마다 따로 해야 합니다.
controller는 Service를 가짜 객체로 만들고 spring mvc
를 MockMvc
로 대체하여 사용합니다.
MockMvc
는 실제 서버를 키지 않고도 요청값을 설정하면 그에 맞게 Servlet
이 돌아갈 수 있도록 구현한 mvc입니다.
GWT도 직관적이지만, 주석으로 처리한다는 단점이 있어 DCI 패턴으로 구현해보았습니다.
DCI는 계층으로 이루어져 있어 보기 훨씬 직관적입니다.
이를 사용하여 게시글 만들기 테스트를 진행해보았습니다.
@DisplayName("Describe: Board Controller 클래스")
@WebMvcTest(BoardController.class)
public class BoardControllerTest {
@MockBean
public BoardService boardService;
@Autowired
private MockMvc mockMvc;
@Nested
@DisplayName("Describe: createBoard 메서드는")
class Describe_createBoard {
@Nested
@DisplayName("Context: 제목과 내용이 주어지면")
class Context_with_title_content {
@Test
@DisplayName("It: 게시물을 리턴한다.")
void it_return_board() throws Exception {
BoardRequestDto boardRequestDto = BoardRequestDto.builder()
.title("제목")
.content("내용")
.build();
BoardResponseDto boardResponseDto = new BoardResponseDto.Builder()
.id((long)1)
.title("제목")
.content("내용")
.createAt(LocalDateTime.now())
.build();
doReturn(boardResponseDto).when(boardService)
.create(any(BoardRequestDto.class));
mockMvc.perform(
MockMvcRequestBuilders.post("/boards")
.contentType(MediaType.APPLICATION_JSON)
.content(new ObjectMapper().writeValueAsString(boardRequestDto))
).andDo(print())
.andExpect(status().is(201))
.andExpect(jsonPath("title", boardResponseDto.getTitle()).exists())
.andExpect(jsonPath("content", boardResponseDto.getContent()).exists());
}
}
}
}
CORS는 확인된 리소스만 접근할 수 있는 브라우저 정책입니다.
중요한 점만 요약하면,
- CORS는 응답값을 받을지 브라우저가 판단한다. 서버는 응답값을 정상적으로 보낸다.
- CORS는 백엔드 서버에서 설정해야 한다. 백엔드 서버에서 허용한 URL만이 접근할 수 있도록 한다.
- CORS를 하면 XSS를 막을 수 있다는 사실이 아니다. 해커가 만든 서버로 요청을 보내게 설정한 악성 스크립트를 심었다면, CORS가 의미가 없다.
Access-Control-Allow-Origin
헤더를 설정해야 한다.
여러가지 방법이 있지만 저는 SpringBoot에서 지원하는 WebMvcConfigurer
을 구현하였습니다.
@Configuration
public class WebCorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PATCH", "PUT", "DELETE")
.allowCredentials(true)
.maxAge(3000);
}
}
꼼꼼한 테스트를 진행하기 위해 보통 dev, stage, prod 3개의 환경을 두어 각각 개발합니다.
이들 환경은 각각 다르기 때문에 환경에 맞는 설정파일이 필요합니다.
DB설정이나 기타 웹 서버 설정, port번호 등이 다릅니다.
이를 위해 Spring에서 다음과 같은 설정을 제공합니다.
spring.profiles.active: 환경별 이름(지금같은 경우 dev, stage, prod)
이를 application.yml
에 넣은 후
이후 3개의 설정파일을 각각 만듭니다.
application-dev.yml
application-stage.yml
application-prod.yml
각각의 설정파일마다 설정을 다르게 한 후, 위의 active 설정에 원하는 환경 이름을 작성하면 됩니다.
2023-05-25 04:18:03.799 INFO 22648 --- [ main] c.e.s.SpringJsCodeApplication : The following 1 profile is active: "dev"
성공적으로 인식하여 설정을 적용했습니다!
Spring Exception Handling
기존에 작성했던 ExceptionAdvice
방식에서 더 효율적인 코드를 작성하고자, 다음 블로그를 참조하였습니다.
Exception들을 Enum으로 구현하여 예외를 미리 만든 후, 이를 ExceptionAdvice
에서 처리하는 방식입니다.
boardRepository.findById(id).orElseThrow(() -> new NoSuchElementException("게시판을 찾을 수 없습니다."));
기존 처리 방식에서
Board board = boardRepository.findById(id).orElseThrow(() -> new CustomException(ErrorCode.BOARD_NOT_FOUND));
로 바뀌게 되면서 훨씬 가독성이 좋아졌습니다.
이번 시간에는 매우 중요한 개념들을 배우고 실습하였습니다.
제일 중요한건 역할 및 책임의 분할이고 오늘 배운 개념들도 여기에서 파생되어 나왔다고 해도 과언이 아니라고 생각합니다.
앞으로도 level을 완수하는데 목표를 두어 열심히 나아가겠습니다.