[Project] 5회차 진행 - 테스트코드, CORS, 환경 분리, ErrorCode 적용

박상혁·2023년 5월 24일
0

Project

목록 보기
12/14

이번 5회차에서는 다양하지만 전부 중요한 개념들을 학습하고 실습해보았습니다.
어떻게 하면 더 좋은 코드를 작성할 수 있을까에 초점을 두어 학습을 진행하였고, 아직도 모르는 것이 많지만 차근차근 진행해보겠습니다.


테스트 코드

각 기능마다 동작하는지 확인하는 작업으로써, 개발자에게 꼭 필요한 검증 과정입니다.
테스트 코드를 작성함으로써 기능이 정상적으로 작동하는지 빠르게 확인하여 수정할 수 있습니다.

여기서는 단위 테스트를 적용시켜보았습니다.

Junit5 + Mockito

Junit5는 자바 진영의 테스트 프레임워크입니다. 테스트에 필요한 검증 메서드 등을 제공합니다.
Mockito는 실제 테스트할 기능만 테스트 하기 위해, Service 로직이나 DB 로직 의존성 대신 가짜 객체를 만들어 미리 해당 로직에 대한 결과를 만들고 이를 사용합니다.

controller 중 게시글 만들기 테스트

계층형 구조에 따라 역할이 나눠져 있으므로 테스트도 계층마다 따로 해야 합니다.
controller는 Service를 가짜 객체로 만들고 spring mvcMockMvc로 대체하여 사용합니다.

MockMvc는 실제 서버를 키지 않고도 요청값을 설정하면 그에 맞게 Servlet이 돌아갈 수 있도록 구현한 mvc입니다.

BDD 중 DCI 패턴 사용

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는 응답값을 받을지 브라우저가 판단한다. 서버는 응답값을 정상적으로 보낸다.
  • CORS는 백엔드 서버에서 설정해야 한다. 백엔드 서버에서 허용한 URL만이 접근할 수 있도록 한다.
  • CORS를 하면 XSS를 막을 수 있다는 사실이 아니다. 해커가 만든 서버로 요청을 보내게 설정한 악성 스크립트를 심었다면, CORS가 의미가 없다.
  • Access-Control-Allow-Origin 헤더를 설정해야 한다.

Spring에서의 CORS 처리 방법

여러가지 방법이 있지만 저는 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"

성공적으로 인식하여 설정을 적용했습니다!


Error Code 작성

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을 완수하는데 목표를 두어 열심히 나아가겠습니다.

profile
개발 노트

0개의 댓글