[eeos] 테스트가 필요해 _ 3부
api 로직과 컴포넌트에 어디까지, 어떻게 테스트를 작성할지 비용과 효용을 따져 eeos의 테스트 전략을 세워본 과정을 정리해보았습니다.
로딩중...
오래 걸리지 않기를 바라요..!
api 로직과 컴포넌트에 어디까지, 어떻게 테스트를 작성할지 비용과 효용을 따져 eeos의 테스트 전략을 세워본 과정을 정리해보았습니다.
우선 기존에 작성했듯 api 에는 문서화의 이점을 보기 위해서 테스트를 작성하였다. 다만, 이 과정에서 너무나도 많은 비용이 발생하게 되었다.
또 이렇게 들인 비용에 비해서 생각보다 큰 효과가 있을까에 대해서도 어느정도 회의적인 생각이 들었다. api 문서의 경우 이미 문서화가 되어 있으며( 물론 최신화의 문제가 있을 수 있으며, api 함수와는 다르게 엔드포인트를 기준으로 작성이 되므로 엄연히 다르다고 볼 수도 있을 것 같긴 하다. ) 스팩을 확인하는데 오래 걸리나..? 라는 생각이 들기도 했다. api 함수 내부에서 하는 일은 많지 않다. 결국 요청을 보내고 받은 데이터 값을 "뷰 모델"을 통해 데이터를 전처리 하는 것일 뿐이다. 로직만 보아도 이해가 될 정도로 간단하고, 그러기에 함수의 이름이 모든 것을 설명해주기도 한다. 이에 따라 api 함수 로직에 굳이 테스트를 이정도의 비용을 들일 필요가 있을까? 에 대해서 ''아니다" 라는 결론을 짓게 되었다.
뷰 모델(view model) a.k.a "DTO"
뷰 모델은 MVVM 패턴의 핵심 요소로, 뷰와 모델 사이에서 데이터를 변환하고 상태를 관리하며 비즈니스 로직을 처리하는 중재자 역할을 한다. 이는 UI와 비즈니스 로직을 분리하여 코드의 테스트 용이성, 재사용성, 유지보수성을 향상시키며, 효율적인 UI 개발을 가능하게 한다.
DTO는 클라이언트 요청에 포함된 데이터를 담아 서버 측에 전달하고, 서버 측의 응답 데이터를 담아 클라이언트에 전달하는 계층간 전달자 역할을 한다.
api 로직에 테스트를 작성할 때 어떻게 구조화를 할지에 대해서 고민이 들었다.
사실 정답은 없는 문제인 것 같았다. 전자의 경우 스팩을 조금 더 구분해서 볼 수 있다는 장점이 있다. 다만, 위에서 말한 대로 설명할만한 스팩 자체가 얼마 없다. 굳이 (request, response) 둘을 분리해서 적을 필요가 있을까? 게다가 response가 필요하지 않는 경우도 존재했다(post 요청 등). 이에 따라 나는 단순히 스팩을 나열하는 방식을 택하기로 했다.


로그인 페이지 한정으로 컴포넌트 테스트를 작성하게 되었다. 여기서 어느 범위까지 고립을 시켜야 할지에 대해서 고민이 들었다.
이에 아래의 글을 참고해서 어느정도 방향성을 잡게 되었다.
기존에 리팩토링을 하기 전, 컴포넌트 추상화 레벨에 대해서 이야기 한 부분이 있다.
https://github.com/JNU-econovation/EEOS-FE/issues/107
기존의 컴포넌트 구조를 따르나, components 폴더 내에서 feature와 common으로 나눈다.
이를 통해서, 내부 로직까지 가지고 있는(ui + 내부 로직 및 데이터)컴포넌트들은 외부 모듈을 전부 모킹해서 고립된 상태로 내부 로직 그 자체만 테스트 하도록 하였다. 또한 외부 로직이 있는 (혹은 도메인을 가지고 있는) 컴포넌트의 경우 내부 로직을 가진 공통 컴포넌트를 포함하고 있기에 컴포넌트를 전체적으로, 그리고 유동적인 모킹을 통해서 통합적인 테스트를 작성하도록 하였다.
컴포넌트 테스트의 모킹의 범위를 정하고 어떻게 테스트를 할지에 대해서 기준이 잡히긴 했지만, 현실적으로 모든 컴포넌트에 이를 적용할 수는 없다. 이에 따라 다시한번 어디에 테스트를 작성해야할지 고민이 생기기 시작했다.
통함 테스트를 생각할 때 항상 염두해야하는 부분이 있다. 바로 서버 요청 로직이다. 서버에 직접 요청을 하게 되면 테스트가 서버의 상태에 따라서 실패할 수도, 성공할 수도 있는 무작위 테스트가 되어버린다. 이는 테스트에 대한 신뢰도를 떨어뜨리는 원인이 되고(코드를 고치지 않아도 실패해버리는 문제 등) 디버깅 부분에서도 문제가 되므로(올바른 코드를 작성했어도 서버의 이상으로 실패해버리는 경우 등) 테스트의 효과를 반감시키는 문제가 있다.
이에 따라서 많은 개발자들이 서버의 반환값을 모킹해서 사용하는 경향이 있다. 모킹 또한 위에서 말한 대로 엄청난 비용이 들어간다. 그리고 이 비용을 줄이기 위해서 MSW와 같은 라이브러리를 사용하게 된다.
다만 EEOS는 next를 사용하므로 서버와 클라이언트 두 곳에서 렌더링을 하게 되므로 설정하는데 꽤나 비용이 들어갈 수 있을 것이라 판단하였다.
이에 더해 이미 모킹 로직을 잘 만들어 두었으므로, axios를 모킹하여 사용하도록 하였다. ( 사실 이러한 모킹 로직을 작성하지 않았다면 msw를 사용했을 것 같다. )
jest.mock("../instance");
const mockHttps = https as jest.MockedFunction<typeof https>;const mockReturnData = getResponse({
url: "endpoint",
method: "method",
});
beforeEach(() => {
mockHttps.mockReturnValue(mockReturnData);
});// ex)
it("팀의 질문 리스트를 반환한다", async () => {
// arrange
const programId = 1;
const teamId = 1;
// act
await getQuestionsByTeam(programId, teamId);
// assert
expect(mockHttps).toHaveBeenCalledWith({
url: "comments",
method: "GET",
params: { programId, teamId },
});
});전체 예시
jest.mock("../instance");
const mockHttps = https as jest.MockedFunction<typeof https>;
describe("getQuestionsByTeam", () => {
const mockReturnData = getResponse({
url: "/comments",
method: "GET",
});
beforeEach(() => {
mockHttps.mockReturnValue(mockReturnData);
});
afterEach(() => {
jest.clearAllMocks();
});
it("팀의 질문 리스트를 반환한다", async () => {
// arrange
const programId = 1;
const teamId = 1;
// act
await getQuestionsByTeam(programId, teamId);
// assert
expect(mockHttps).toHaveBeenCalledWith({
url: "comments",
method: "GET",
params: { programId, teamId },
});
});
});(getResponse 내용이 궁금하다면? [eeos] 테스트가 필요해 _ 2부)