회고/코드스쿼드 - Java 과정

[TIL] 코드스쿼드 2022 백엔드 58일차 회고(12주차)

jwKim96 2022. 3. 31. 01:30

오늘은 무슨 일이?

테스트 코드를 위한 리펙토링

4단계 로그인 로그아웃을 구현하고, 주요 로직들에 대해 루시드와 테스트 코드를 작성해보기로 했었는데요.
그래서 기존 소스를 보며 테스트 가능한 구조로 리펙토링하는 작업을 하며 느낀점을 기록합니다.

static 클래스는 최대한 지양하기

WAS 에서 동적 요청을 확인하기 위해, 아래와 같은 형태의 RequestMapping 이라는 클래스를 만들었습니다.

public class RequestMapping {
    private static final Map<URL, HttpServlet> servletMapping = new HashMap<>();

    static {
        servletMapping.put(new URL("/user/create"), new UserCreateServlet());
        servletMapping.put(new URL("/user/login"), new LoginServlet());
        servletMapping.put(new URL("/user/logout"), new LogoutServlet());
    }

    // ...
}

요구사항은 만족하는 클래스였지만, 테스트 코드를 작성하려니 문제가 있었습니다.

먼저, 위 클래스 내부의 static 블록에서 자신이 가질 상태가 정해지는것이 문제였습니다.
테스트를 위해서는 객체의 상태에 따라 수행한 결과의 정합성을 검증해야합니다.
그런데 RequestMapping 을 사용하는 클래스를 테스트 하려니, 이미 상태가 정해져있어 테스트가 불가했습니다.

두 번째로 위 클래스는 static 이기 때문에 다른 클래스에 주입해줄 수 없었습니다.
위 내용과 연관이 있는데, RequestMapping 을 사용하는 다른 클래스 테스트를 위해 상태를 변경하여 주입해주려니
static 클래스여서 인스턴스 생성이 불가하여 주입해줄 수 없었습니다.

그래서 이 클래스를 일반 객체로 만든 다음 RequestMappingFactory 에서 매핑을 생성하여 반환하도록 변경하였고,
테스트 코드에서는 RequestMapping 을 바로 생성하여 매핑(상태)을 임의로 주입하여 테스트를 작성할 수 있게 되었습니다.

메소드는 가능하면 순수함수로!

테스트를 작성하려고 보니, 제가 테스트하고싶은건 객체의 특정 메소드인데 내부에서 객체의 속성(상태)을 참조하면
객체를 생성하는 단계에서부터 주입을 해주어야 했습니다.

public abstract class HttpServlet {

    protected Request request;
    protected Response response;

    protected HttpServlet(Request request, Response response) {
        this.request = request;
        this.response = response;
    }

    public Response service() {
        switch (request.getHttpMethod()) {
            case GET:
                return doGet();
            case POST:
                return doPost();
        }

        throw new IllegalStateException("Unsupported http method");
    }

    public Response doGet() {
        // TODO : GET 요청 처리는 이 메소드를 오버라이드 해서 구현
        throw new IllegalStateException("Unimplemented get method");
    }

    public Response doPost() {
        // TODO : POST 요청 처리는 이 메소드를 오버라이드 해서 구현
        throw new IllegalStateException("Unimplemented get method");
    }
}

위는 요청을 처리하기위해 정의한 Servlet 추상클래스 입니다.
이 클래스를 상속한 Servlet 의 문제점은 매번 새로 생성해야 한다는 것 입니다.
객체의 속성(상태)으로 request, response 를 받는 형태이기 때문에, 새로운 요청이 오면 새로운 servlet 이 필요합니다.
이와 같은 맥락으로, servlet 을 테스트하려면 테스트 메소드마다 새로운 Servlet 을 생성해줘야 하는 문제점이 있었습니다.
그래서 객체의 생성시에 상태를 주입하지 않고, 메소드에 바로 주입하는 형태로 변경하였습니다.

package http;

public abstract class HttpServlet {

    public Response service(Request request, Response response) {
        switch (request.getHttpMethod()) {
            case GET:
                return doGet(request, response);
            case POST:
                return doPost(request, response);
        }

        throw new IllegalStateException("Unsupported http method");
    }

    public Response doGet(Request request, Response response) {
        // TODO : GET 요청 처리는 이 메소드를 오버라이드 해서 구현
        throw new IllegalStateException("Unimplemented get method");
    }

    public Response doPost(Request request, Response response) {
        // TODO : POST 요청 처리는 이 메소드를 오버라이드 해서 구현
        throw new IllegalStateException("Unimplemented get method");
    }
}

이렇게 되면 doGet, doPost 메소드는 순수함수가 되어 상태를 참조하지 않아도 되고
새로운 요청시에도, 테스트 코드를 작성할때도 Servlet 은 하나만 있어도 됩니다.

인상깊었던 것

  • 산토리의 기가막힌 Two-level Paging 설명👏
    • 강의 들을때는 감이 잘 안왔는데, 산토리 설명듣고 바로 이해됨!
  • 데이브의 깔끔한 Segmentation with Paging 정리👍
    • 중구난방 설명해서 정리가 힘들었던 내용을 깔끔하게 정리해주셔서 좋았음!

아쉬웠던 것

  • 공부 시간 배분을 잘 못해서, 목표했던 양을 다 못한 것 같네요
    • 선택과 집중~!!