테스트 코드에 기반한 API 명세 - ttasjwi/board-system GitHub Wiki

개요

  • 보통 웹 서비스 개발 과정에선, 클라이언트(모바일, 프론트엔드)/백엔드 간의 원활한 협력을 위해 API 명세를 먼저 결정해야한다.
  • 그러나 애플리케이션 초기 개발 과정에서는 API 명세는 자주 변경하기 쉽다.
  • 막상 API 명세를 전달하더라도, 에상치 못 한 이유 등으로 명세는 자주 변경되어야한다.
    • 실제 API 사양과 API 명세 사이의 불일치가 발생하기 쉽다.

문제 상황

image

  • 예시로 github wiki 를 활용하여 API 명세를 작성한 경우이다.
    • 이 당시 회원가입 api의 엔드포인트는 "/api/v1/members" 였다.
  • 그러나 시간이 경과하면서 회원 가입 엔드포인트는 "/api/v1/users" 로 변경되었다.
  • 그러나 API 명세는 수정되지 않아서 그대로 "/api/v1/members" 로 기재되어있다.
  • API 클라이언트 분들에게는 "API 사양 바뀌었어요...." 라고 말을 전달했지만, 실제 명세를 펼쳐보면 사양이 바로 반영되지 않아 혼동의 여지가 클 수밖에 없다. 이런 혼동은 소통 비용을 증가시키고, 개발 속도에 방해가 된다.

API 명세 작성방법 선택지

  • github wiki, notion 등에 직접 기술하기

    • 개발자가 문서 구조나 서술 방식을 자유롭게 커스터마이징할 수 있어, 복잡한 요구사항이나 설명을 유연하게 담을 수 있다.
    • 하지만 실제 API 사양과 별개로 수동으로 문서를 작성하다 보니, 코드 변경 시 명세가 함께 업데이트되지 않는 경우가 많다.
      • API 문서와 실제 동작 사이의 불일치가 쉽게 발생한다.
  • swagger image

    • 애플리케이션 실행 시, Controller나 DTO에 붙은 애너테이션 (@RestController, @GetMapping, @Schema, @Parameter, 등)을 스캔해서 API 명세를 생성
      • 편하게 느껴질 수도 있겠지만, 반대로 생각해면 문서 생성을 위해 컨트롤러나 DTO에 많은 어노테이션이 필요해 애플리케이션 코드 침투 문제가 발생한다.
    • API를 브라우저에서 직접 테스트 가능하다.
    • 자동화된 UI 문서 제공 (Swagger UI). 아래에서 후술할 Spring Rest Docs 와 비교했을 때 UI가 깔끔한 편이다.
    • 테스트 코드와는 전혀 연동되지 않기 때문에, 실제 동작과 Swagger 문서 간의 불일치가 발생할 수 있다.
  • Spring Rest Docs

    • 테스트 코드 기반의 API 명세 작성
      • API 명세와 실제 동작이 반드시 일치하도록 강제할 수 있음.
      • 정확성 보장: 테스트 통과하지 않으면 문서가 생성되지 않음
    • 애플리케이션 코드에 문서화를 위한 어노테이션이 거의 필요 없음.
    • 문서 결과물은 HTML이나 Asciidoc 기반으로 직접 보기엔 Swagger UI보다 가독성이 낮을 수 있음.

결정

  • 우리 서비스에서는 Spring Rest Docs 방식으로 API 명세를 작성하기로 한다.
  • 가독성 등의 문제가 있지만, API 명세와 실제 동작이 반드시 일치하도록 강제할 수 있다는 점이 매우 큰 강점으로 다가왔다.

적용과정

  mockMvc
            .perform(
                request(HttpMethod.POST, urlPattern, articleId)
                    .header("Authorization", "Bearer ${accessToken.tokenValue}")
            )
            .andExpectAll(
                status().isCreated,
                jsonPath("$.articleLikeId").value(response.articleLikeId),
                jsonPath("$.articleId").value(response.articleId),
                jsonPath("$.userId").value(response.userId),
                jsonPath("$.createdAt").value("2025-01-01T00:18:00+09:00"),
            )
            .andDocument(
                identifier = "article-like-create-success",
                requestHeaders(
                    HttpHeaders.AUTHORIZATION
                            headerMeans "인증에 필요한 토큰('Bearer [액세스토큰]' 형태)"
                            example "Bearer 액세스토큰",
                ),
                pathParameters(
                    "articleId"
                            paramMeans "게시글 식별자"
                            constraint "실제 게시글이 존재해야함."
                ),
                responseBody(
                    "articleLikeId"
                            type STRING
                            means "게시글 좋아요 식별자(Id)",
                    "articleId"
                            type STRING
                            means "게시글 식별자(Id)",
  • 테스트코드 통과후, API 명세 작성 로직도 추가적으로 작성하면 된다. (spring-rest-docs 의존성 필요)
  • 명세를 위한 snippet 파일들이 이 과정에서 만들어진다.

image

  • 테스트코드 통과 후에 만들어지는 snippet 파일들을 adoc 파일에서 조합해서, API 명세 구성에 사용할 수 있다.
  • asciidoctor 플러그인을 활용해 adoc 파일을 HTML 파일로 변환한다. (gradle 에서 빌드 후에 기술하여, 자동 문서화 할 수 있도록 설정 가능)

성과

image

  • 테스트 코드 기반의 API 명세가 간단하게 만들어질 수 있었다.
  • 실제 API 사양과 API 명세 사이의 불일치를 해결할 수 있었다.