Spring Rest Docs VS Swagger

더기·2022년 4월 13일
0

스프링

목록 보기
2/4
post-thumbnail

서론


팀원들과 Rest Docs와 Swagger 중 무엇을 고민할지 상의했다.
결과적으로는 나의 생각과는 다르게 Rest Docs를 선택했다.
Swagger도 튜토리얼 정도만 해봤기 때문에 둘의 장단점을 명확하게 공감하지는 못한다.
그래서 이번에는 팀원들의 의견을 따라 Rest Docs를 사용하며, 장단점을 느껴보자.

Spring Rest Docs VS Swagger


내가 Swagger를 원했던 이유

  • Rest Docs는 문서를 만들기 위한 테스트 코드를 작성해야 한다. 이 부분이 불편하다고 느꼈다.
    차라리 컨트롤러에 의존되고 조금 코드가 지저분해지는 것이 좋지 않을까? 라는 생각이었다.
  • Rest Docs는 테스트를 강제화 해서 문서를 만든다는 것이다. 여기서 테스트는 컨트롤러 테스트를 의미한다.
    하지만 우리는 인수 테스트를 컨트롤러 테스트로 하기 때문에 Rest Docs의 테스트가 의미가 없다.
  • Swagger는 테스트 코드가 없기 때문에 컨트롤러에 바로 작성할 수 있다.
    그래서 테스트를 관리할 필요가 없어서 좋다.
  • Swagger가 코드가 지저분해 진다면, @ApiModel등을 사용하여 추상화하면 된다.
    더 많이 추상화를 할 수 있는 기능은 아직 보지 않았지만, @ApiModel만 해도 일단 지저분한 코드는 분리할 수 있다.
    즉, 코드가 지저분해진다는 것은 공감하지 못한다.

하지만 팀원 분은 프로덕션 코드에 의존되는 것 자체가 가독성이 좋지 않다고 했고,
@ApiModel을 사용해서 추상화를 한다고 해도 결국 추상화를 위한 클래스가 필요하기 때문에 별도로 클래스를 관리하는 것이
개발할 때 찾아가기 불편하다고 했다. 하지만 생각해보니 이 부분은 Rest Docs도 똑같은 것 같다.
결국 Rest Docs도 깔끔하게 하려면 추상화가 필요하다. 그래서 아직까지는 Swagger가 좋지 않을까 생각하고 있다.

MockMvc VS Rest Assured


AsciiDoc VS Markdown


우리 프로젝트는 문서화 도구로 Markdown이 아닌 AsciiDoc을 사용했다.
ATDD 교육 과정에서 사용했었는데 이유는 include의 여부이다.
Markdown은 문법이 굉장이 편하다. 그리고 많이 사용하는 문법이다.
반면 AsciiDoc은 문법은 조금 불편하지만 include가 가능하기 때문에 html을 작성하는 것처럼
재활용이 가능하다. 그래서 AsciiDoc을 채택하게 되었다.

Junit에서 Rest Docs 셋팅


테스트에서 공통으로 사용될 셋팅을 만든다.

import io.restassured.RestAssured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.RestDocumentationExtension;
import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.restdocs.request.ParameterDescriptor;
import org.springframework.test.context.ActiveProfiles;

import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
import static org.springframework.restdocs.payload.PayloadDocumentation.relaxedResponseFields;
import static org.springframework.restdocs.request.RequestDocumentation.requestParameters;
import static org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.document;
import static org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.documentationConfiguration;

@ActiveProfiles("test")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ExtendWith(RestDocumentationExtension.class)
public class Documentation {
    @LocalServerPort
    int port;

    protected RequestSpecification spec;

    @BeforeEach
    public void setUp(RestDocumentationContextProvider restDocumentation) {
        RestAssured.port = port;

        this.spec = new RequestSpecBuilder()
                .addFilter(documentationConfiguration(restDocumentation))
                .build();
    }

    RequestSpecification given(String identifier, ParameterDescriptor[] parameterDescriptors, FieldDescriptor[] fieldDescriptors) {
        return RestAssured.given(this.spec).log().all()
                .filter(document(identifier, preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()),
                        requestParameters(parameterDescriptors),
                        relaxedResponseFields(fieldDescriptors)
                        )
                );
    }
}
  • protected RequestSpecification spec
    • MVC 테스트로 Rest Assured를 사용하기 때문에 사용한다.
      Rest Assuredgiven() 메서드의 리턴 타입이 RequestSpecification이다.
  • RestDocumentationContextProvider, RestDocumentationExtension.class
  • document
    • identifier : 문서화 식별자(디렉토리명)
    • preprocessRequest : 요청 전 무슨 처리를 할지
    • preprocessResponse : 응답 전 무슨 처리를 할지
    • prettyPrint : 포맷팅
    • requestParameters : 요청 파라미터 설명 데이터
    • relaxedResponseFields : 응답 필드 설명 데이터
  • @ExtendWith(RestDocumentationExtension.class)
    • rest docs를 확장하여 조금 더 편리하게 사용하기 위해 테스트에서 공통으로 사용할 기능을 지원한다.
      mockito로 예를 들어보자면 아래와 같다.

      공통으로 사용하도록 @Mock을 사용하여 공통화할 수 있다.

      @ExtendWith(MockitoExtension.class)
      public class MockitoExtensionTest {
         @Mock
         private LineRepository lineRepository;
         @Mock
         private StationService stationService;
      
      		
         @Test
         void findAllLines() {
      		 // given
           when(lineRepository.findAll()).thenReturn(Lists.newArrayList(new Line()));
           LineService lineService = new LineService(lineRepository, stationService);
      
      	   ...
      	}

      공통화를 못하고 각 테스트마다 사용해야 한다.

      @DisplayName("단위 테스트 - mockito를 활용한 가짜 협력 객체 사용")
      public class MockitoTest {
         @Test
         void findAllLines() {
             // given
             LineRepository lineRepository = mock(LineRepository.class);
             StationService stationService = mock(StationService.class);
      
             when(lineRepository.findAll()).thenReturn(Lists.newArrayList(new Line()));
             LineService lineService = new LineService(lineRepository, stationService);
      
             ...
         }

      build.gradle 셋팅

      plugins {
      	id 'org.springframework.boot' version '2.6.5'
      	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
      	id "org.asciidoctor.jvm.convert" version "3.3.2"
      	id 'java'
      }
      
      configurations {
      	asciidoctorExt
      }
      
      group = 'project'
      version = '0.0.1-SNAPSHOT'
      sourceCompatibility = '11'
      
      repositories {
      	mavenCentral()
      }
      
      dependencies {
      	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
      	implementation 'org.springframework.boot:spring-boot-starter-web'
      	runtimeOnly 'com.h2database:h2'
      	runtimeOnly 'mysql:mysql-connector-java'
      	testImplementation 'org.springframework.boot:spring-boot-starter-test'
      
      	// handlebars
      	implementation 'pl.allegro.tech.boot:handlebars-spring-boot-starter:0.3.0'
      
      	// rest docs
      	asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor:2.0.5.RELEASE'
      	testImplementation 'org.springframework.restdocs:spring-restdocs-restassured:2.0.5.RELEASE'
      	testImplementation 'io.rest-assured:rest-assured:3.3.0'
      }
      
      // snippets 파일이 저장될 경로 변수 설정
      ext {
      	snippetsDir = file('build/generated-snippets')
      }
      
      // 테스트 실행 시 snippets를 저장할 경로로 snippetsDir 사용
      test {
      	useJUnitPlatform()
      }
      
      task testDocument(type: Test) {
      	useJUnitPlatform()
      	filter {
      		includeTestsMatching "*.documentation.*"
      	}
      }
      
      // API 문서 생성
      asciidoctor {
      	inputs.dir snippetsDir
      	configurations 'asciidoctorExt'
      	dependsOn testDocument
      }
      
      bootJar {
      	dependsOn asciidoctor
      	from ("${asciidoctor.outputDir}/html5") {
      		into 'static/docs'
      	}
      }
      
      task copyDocument(type: Copy) {
      	dependsOn asciidoctor
      
      	from file("build/docs/asciidoc")
      	into file("src/main/resources/static/docs")
      }
    • task : gradle을 통해 실행되는 단위

    • ext : 전역 변수 셋팅

    • asciidoctor : testDocument를 의존하여 테스트를 실행하고, snippetsDir에서 snippets을 참조하여
      문서를 생성한다.

    • bootJar : jar 빌드 시 asciidoctor를 참조하여 문서를 생성하고, snippetsDir에 있는 html5파일을
      static/docs로 복사한다. 복사하는 이유는 api 요청으로 문서 접근을 위함

    • copyDocument : from 디렉토리에 있는 API 문서 파일을 into로 복사한다.
      복사하는 이유는 api 요청으로 문서 접근을 위함. 테스트 시에는 이걸 쓰고, 배포 시에는 bootjar를 씀.

    • testDocument : restdocs 전용 테스트 실행 task로 **.documnetation.*에 해당하는 테스트만 실행한다.*

    • includeTestMatching : 실행할 테스트 패턴 정의
      - 우리는 Documentation이라는 RestDocs 전용 부모 클래스를 상속받아 사용하기 때문에
      **.documnetation.*로 지정*
      - https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/testing/TestFilter.html

      Rest Docs 실행 방법


    • 테스트(testDocument)를 수행시켜 snippet을 생성한다.

      • build.gradle에서 설정한 snippetsDir에 생성 됨.
    • gradle로 asciidoctor task를 수행시켜 문서 파일을 생성

      • 그 전에 src/docs/asciidoc/index.adoc 있어야 함.
      • asciidoctortestDocument를 의존하기 때문에 asciidoctor를 바로 실행해도 됨.
    • build > asciidoc > html5 > index.html에 문서가 생성된 것을 오픈하여 잘 만들어졌는지 확인한다.

    • task copyDocument를 실행하여 배포할 디렉토리도 복사하기

profile
wwqew11

0개의 댓글