본문 바로가기

Backend/Spring

Kotlin에서 Rest Docs 문서화 코드 개선하기

기존에는 테스트 결과를 문서화 하기 위해, Rest Docs에서 제공하는 FieldDescriptor를 다음과 같이 생성하였다.

그러다 보니 Field가 많으면 엄청난 양의 코드를 작성해야 하고 이 부분의 가독성이 떨어져 개선하고자 한다.

 

❌ 문제의 코드

restDocMockMvc.post("/test") {
    jsonContent(request)
}.andExpect {
    status { isOk() }
}.andDo {
    handle(
        MockMvcRestDocumentation.document(
            "{class-name}/test-success",
            requestFields(
                fieldWithPath("name").description("이름").attributes(field(EXAMPLE, request.name))
            ),
            responseFields(
                fieldWithPath("hashValue").description("name의 hashCode 값"),
                fieldWithPath("originalName").description("trim을 적용한 name 값")
            )
        )
    )
}

🤦‍♂️ 해당 코드는 반복적이고, 코드가 잘 읽히지 않는다.

 

✔ 개선 코드

restDocMockMvc.post(targetUri) {
    jsonContent(request)
}.andExpect {
    status { isOk() }
}.andDo {
    createDocument(
        "test-success",
        requestBody("name" type STRING description "이름" example request.name),
        responseBody(
            "hashValue" type NUMBER description "name의 hashCode 값",
            "originalName" type STRING description "trim을 적용한 name 값"
        )
    )
}

방법

Kotlin에서는 infix function확장 함수를 제공한다. 해당 기능을 사용해서 코드를 개선할 생각이다.

 

💬 Infix Function

두 개의 객체 중간에 들어가게 되는 함수를 의미한다.

 

👀 예시

val monday: Pair<String, String> = "Monday" to "월요일"

◼ 위와 같이 to라는 infix function을 사용해서 Pair 객체를 생성할 수 있다.

 

💬 확장 함수

기존에 정의된 클래스에 함수를 추가하는 기능이다.

 

👀 예시

fun MutableList<Int>.swap(a: Int, b: Int){
	val tmp = this[a]
	this[a] = this[b]
	this[b] = tmp
}

◼ 위와 같이 MutableList 클래스에 swap이라는 함수 기능을 추가할 수 있다.

 

개선 과정

FieldDscriptor를 가지고 있는 class를 만든다.

class RestDocsField(
    val descriptor: FieldDescriptor
) {
    infix fun isOptional(value: Boolean): RestDocsField {
        if (value) descriptor.optional()
        return this
    }

    infix fun description(value: String): RestDocsField {
        descriptor.description(value)
        return this
    }

    infix fun example(value: String): RestDocsField {
        descriptor.attributes(field(EXAMPLE, value))
        return this
    }
}

◼ RestDocsField 클래스에서 FieldDescriptor를 받는다.

◼ infix 함수를 통해 FieldDescriptor에 해당 값을 추가한다.

 

String 타입에 확장 함수 추가

infix fun String.type(
    type: JsonFieldType
): RestDocsField = createField(this, type)
private fun createField(
    path: String,
    type: JsonFieldType
): RestDocsField = RestDocsField(PayloadDocumentation.fieldWithPath(path).type(type))

◼ String 클래스에 type이라는 infix 함수를 추가한다.

◼ JsonFieldType은 Rest Docs에서 제공하는 class로 필드의 type 지정할 수 있다.

 

 "code" type JsonFieldType.NUMBER

◼ 위와 같이 코드를 작성하면, type 이라는 infix 함수에 "code" 가 this로, JsonFieldType.NUMBER가 type 인자로 들어간다.

◼ createField 함수를 통해 path에 code를 type에는 JsonFieldType.NUMBER가 저장되어 FieldDescriptor 객체가 생성되고 해당 객체를 인자로 받는 RestDocsField 객체가 반환된다.

 

 "code" type JsonFieldType.NUMBER description "응답 코드"

◼ 위와 같이 코드를 작성하면, "code" type JsonFieldType.NUMBER 가 RestDocsField로 변환된다.

◼ 이후, 생성된 ResDocsField의 description infix 함수를 통해 FieldDescriptor 의 description 값으로 "응답 코드" 가 저장된다.

 

ResponseFieldsSnippet 만들기

fun responseBody(vararg fields: RestDocsField): ResponseFieldsSnippet =
PayloadDocumentation.responseFields(fields.map { it.descriptor })

◼ 이제 생성된 RestDocsField를 인자로 받아 responseFields 함수에 RestDocsField의 descriptor를 인자로 넘겨주면 완성된다.

 

💬 vararg 키워드

가변 인자를 사용하면 함수를 호출할 때, 인자의 개수를 유동적으로 지정할 수 있다.

 

MockMvcResultHandlersDsl에 확장 함수 추가

fun MockMvcResultHandlersDsl.createDocument(identifier: String, vararg snippets: Snippet) {
    return handle(MockMvcRestDocumentation.document("{class-name}/$identifier", *snippets))
}

◼ createDocument로 snippet을 저장할 폴더 이름과 만든 snippets을 인자로 받아 anDo 함수를 실행한다. 

 

결론

코틀린에서 제공하는 다양한 기능을 활용하여 기존 코드를 개선할 수 있었다. 반복적인 코드를 최소한으로 줄이고 가독성을 높여 작성하기 쉽고 이해하기 쉬운 코드를 작성할 수 있었다.

 

'Backend > Spring' 카테고리의 다른 글

Spring Event 사용하기  (0) 2023.06.29
kotlin + HttpInterface 알아보기  (0) 2023.06.22
Kotest를 통한 DCI 패턴 적용  (0) 2023.05.27
spring에서 flyway 사용하기  (0) 2023.05.26
[Spring] checkstyle 적용하기  (0) 2023.05.03