시스템을 잘 설계하는 것과 그 시스템을 다른 사람이 쓸 수 있게 하는 것은 다른 일이다. 아무리 규약을 충실히 따라 설계해도, 그것을 쓰는 쪽이 무엇을 어떻게 요청해야 하는지 모르면 설계는 소용이 없다. 시스템의 경계가 밖으로 노출되는 순간, 그 경계는 문서로 설명되고 테스트로 검증되어야 비로소 완성된다. 마지막 편은 이 문서화와 테스트를 다룬다.


문서화와 테스트는 흔히 개발이 끝난 뒤에 하는 부수적인 일로 여겨진다. 그러나 이 둘은 시스템이 외부와 맺는 계약을 다루는 일이라, 설계만큼이나 중요하다. 문서가 계약의 내용을 밝히고, 테스트가 그 계약이 실제로 지켜지는지를 확인한다. 이번 편은 무엇을 문서로 남겨야 하는지, 규약이 문서를 어떻게 돕는지, 그리고 계약을 검증하는 테스트가 어떤 층위로 이루어지는지를 살펴본다.

설계는 문서로 완성된다

시스템의 경계는 다른 사람이 건너오는 지점이다. 그 경계 너머의 사람은 시스템의 내부를 볼 수 없고, 오직 밖으로 드러난 약속만 보고 요청을 보낸다. 그 약속이 어디에도 적혀 있지 않으면, 쓰는 쪽은 시행착오로 더듬어 가며 규칙을 짐작해야 한다. 문서는 이 짐작을 없애 준다.


문서가 없는 시스템은 설계자의 머릿속에만 존재하는 것과 같다. 설계자는 어떤 주소로 무엇을 요청해야 하는지, 어떤 응답이 돌아오는지를 알지만, 그 지식이 밖으로 나오지 않으면 다른 사람은 매번 설계자에게 물어야 한다. 이는 설계자를 병목으로 만들고 협업을 느리게 한다. 문서는 그 지식을 설계자 바깥으로 꺼내 놓는 일이다.


좋은 문서는 시스템을 쓰는 비용을 크게 낮춘다. 쓰는 쪽이 문서만 읽고도 요청을 정확히 구성하고 응답을 올바로 해석할 수 있다면, 두 편 사이의 소통 비용이 사라진다. 반대로 문서가 부실하면, 겉으로는 잘 설계된 시스템도 쓰기 어려운 시스템이 된다. 설계의 품질과 문서의 품질은 함께 가야 한다.


문서화를 설계와 분리해 나중으로 미루면 대개 부실해진다. 설계가 끝난 뒤 기억을 더듬어 문서를 쓰면, 빠뜨리는 부분이 생기고 실제 동작과 어긋나기도 한다. 그래서 설계를 진행하면서 문서를 함께 만들거나, 아예 문서를 먼저 정하고 그에 맞춰 구현하는 방식이 권장된다. 문서를 설계의 결과가 아니라 설계의 일부로 다루는 것이다.

무엇을 적어야 하는가

문서에 담아야 할 첫째는 어떤 자원이 있고 각각을 어떤 주소로 가리키는지다. 시스템이 다루는 자원의 목록과 그 주소 체계가 문서의 뼈대가 된다. 쓰는 쪽은 이 목록을 보고 자신이 다룰 대상이 무엇이고 어디에 있는지를 파악한다. 앞서 다룬 주소 설계가 잘 되어 있으면 이 부분의 문서도 간결해진다.


둘째는 각 자원에 어떤 조작이 가능한지다. 어떤 자원은 조회만 되고, 어떤 자원은 생성과 수정과 삭제까지 된다. 어떤 메서드로 어떤 조작을 하는지를 자원마다 밝혀야 한다. 쓰는 쪽은 이를 보고 자신이 할 수 있는 일과 할 수 없는 일을 구분한다. 조작의 범위가 곧 그 자원으로 할 수 있는 일의 경계다.


셋째는 요청과 응답의 구체적인 형태다. 요청에 어떤 값을 담아야 하는지, 그중 무엇이 필수이고 무엇이 선택인지, 응답으로 어떤 구조의 데이터가 돌아오는지를 적는다. 실제 요청과 응답의 예시를 함께 두면 이해가 빠르다. 추상적인 설명보다 구체적인 한 사례가 쓰는 쪽의 시행착오를 줄인다.


넷째는 일이 잘못되었을 때의 응답이다. 어떤 상황에서 어떤 상태 코드가 돌아오는지, 실패 응답의 본문이 어떤 형태로 원인을 알리는지를 밝혀야 한다. 앞서 에러 응답 설계에서 다룬 것처럼, 실패를 잘 정리해 두어야 쓰는 쪽이 오류에 대응할 수 있다. 성공만 문서화하고 실패를 빠뜨리면 문서의 절반이 비는 셈이다.

규약을 문서로 삼기

HTTP를 규약대로 충실히 따르면 문서화의 부담이 줄어든다. 규약 자체가 이미 많은 것을 정해 두었기 때문이다. 조회는 조회 메서드로, 생성은 생성 메서드로, 성공은 성공 코드로, 없음은 없음 코드로 표현된다는 공통의 약속을 쓰는 쪽도 알고 있다. 규약을 지키면 그 공통 약속만큼은 따로 설명하지 않아도 된다.


이것이 규약을 결대로 따르는 설계의 숨은 이점이다. 메서드의 의미를 제멋대로 쓰거나 상태 코드를 임의로 붙이면, 그 하나하나를 전부 문서로 설명해야 한다. 반면 규약의 표준 의미를 그대로 쓰면, 쓰는 쪽이 이미 아는 지식을 재사용할 수 있어 문서가 짧아진다. 표준을 따르는 것 자체가 일종의 문서화인 셈이다.


일관성도 문서를 줄인다. 모든 자원이 같은 규칙으로 다루어지면, 하나의 자원을 설명하는 방식이 다른 자원에도 그대로 통한다. 쓰는 쪽은 한 자원의 사용법을 익히면 나머지를 짐작할 수 있어, 모든 자원을 일일이 읽지 않아도 된다. 통일된 인터페이스가 사용 편의뿐 아니라 문서의 경제성까지 가져다준다.


정형화된 형식으로 시스템의 경계를 기술하는 방법도 널리 쓰인다. 자원과 주소, 조작과 요청과 응답을 정해진 틀에 맞춰 적으면, 그 기술로부터 사람이 읽는 문서와 시험용 도구를 자동으로 만들어 낼 수 있다. 손으로 쓴 문서가 실제 동작과 어긋나는 문제를 이 방식이 줄여 준다. 형식을 정해 두면 문서가 설계와 함께 유지되기 쉽다.

스스로 설명하는 응답

문서와 별개로, 응답 자체가 어느 정도 자신을 설명하게 만들 수 있다. 응답에 담긴 데이터의 구조가 명확하고 이름이 뜻을 잘 드러내면, 쓰는 쪽은 문서를 덜 뒤져도 응답을 해석할 수 있다. 값의 이름이 모호하거나 구조가 뒤엉켜 있으면, 아무리 문서가 자세해도 매번 확인해야 한다.


응답에 관련 자원의 주소를 함께 담는 방식도 응답을 스스로 설명하게 한다. 어떤 자원의 응답에 그와 이어지는 다른 자원의 주소가 들어 있으면, 쓰는 쪽은 다음에 어디로 갈 수 있는지를 응답만 보고 안다. 주소를 미리 다 문서에서 찾지 않아도, 응답을 따라가며 시스템을 탐색할 수 있다. 응답이 길잡이 역할을 겸하는 것이다.


다만 응답의 자기 설명에 지나치게 기대는 것도 곤란하다. 응답만으로 모든 것을 알 수 있게 하려면 응답이 무거워지고, 그럼에도 요청을 어떻게 구성해야 하는지까지는 응답이 알려 주지 못한다. 응답의 자기 설명은 문서를 보완하는 것이지 대체하는 것이 아니다. 둘을 적절히 나누어 쓰는 균형이 필요하다.


이름을 잘 짓는 일은 문서화의 가장 값싼 형태다. 자원과 값의 이름이 그 뜻을 정확히 담고 있으면, 그 이름 자체가 설명이 되어 별도의 해설을 덜어 준다. 이름을 대충 지어 놓고 그 뜻을 문서로 길게 설명하는 것보다, 처음부터 뜻이 드러나는 이름을 짓는 편이 낫다. 좋은 이름은 문서를 짧게 만든다.

요청을 검증하는 테스트

문서가 계약을 밝힌다면, 테스트는 그 계약이 실제로 지켜지는지를 확인한다. 시스템이 문서에 적힌 대로 동작하는지, 특정 요청에 약속된 응답이 돌아오는지를 실제로 요청을 보내 검증하는 것이다. 문서와 구현이 어긋나는 순간을 잡아내는 것이 이 테스트의 목적이다.


가장 기본적인 테스트는 정상적인 요청에 기대한 응답이 오는지 보는 것이다. 올바른 요청을 보냈을 때 약속된 상태 코드와 데이터가 돌아오는지를 확인한다. 이것이 통과해야 시스템이 최소한 정상 경로에서는 계약을 지킨다고 말할 수 있다. 정상 경로의 검증이 테스트의 출발점이다.


그러나 정상 경로만 검증하면 절반만 확인한 것이다. 잘못된 요청에 시스템이 어떻게 반응하는지도 함께 검증해야 한다. 필수 값을 빠뜨린 요청, 권한 없는 요청, 존재하지 않는 자원을 향한 요청에 약속된 실패 응답이 정확히 돌아오는지를 확인한다. 앞서 다룬 실패의 설계가 실제로 지켜지는지는 이 테스트로만 알 수 있다.


경계에 놓인 값도 테스트의 중요한 대상이다. 허용된 범위의 끝에 놓인 값, 비어 있는 값, 예상보다 큰 값처럼 애매한 입력에서 시스템이 무너지지 않고 정해진 대로 반응하는지를 본다. 문제는 대개 평범한 입력이 아니라 이런 경계에서 터진다. 경계를 찔러 보는 테스트가 숨은 결함을 드러낸다.


테스트는 한 번 만들어 두면 회귀를 막는 그물이 된다. 예전에 고쳤던 문제가 다른 변경에 밀려 다시 나타나는 일이 흔한데, 그 문제를 재현하는 테스트를 남겨 두면 같은 결함이 되살아나는 순간 바로 걸린다. 잡은 결함마다 그것을 검증하는 테스트를 하나씩 쌓아 가면, 시스템이 커질수록 그물이 촘촘해진다. 테스트는 지금의 동작을 확인하는 동시에 과거의 실수가 되풀이되지 않게 지키는 장치다.

계약을 지키는지 확인하기

테스트는 몇 개의 층으로 나누어 생각할 수 있다. 가장 좁은 층은 개별 처리 하나가 홀로 올바로 동작하는지 보는 것이고, 넓은 층은 여러 요청이 이어지는 흐름이 전체적으로 맞물리는지 보는 것이다. 좁은 테스트는 빠르고 원인을 짚기 쉽지만 부분만 보고, 넓은 테스트는 느리지만 전체를 본다. 두 층을 함께 갖추어야 한다.


시스템의 경계에서 이루어지는 테스트는 특히 중요하다. 실제 요청을 보내고 실제 응답을 받아 계약이 지켜지는지 확인하는 이 테스트는, 쓰는 쪽이 겪을 경험을 그대로 재현한다. 내부의 세부가 어떻든 경계에서 약속대로 동작하면 쓰는 쪽에는 문제가 없다. 경계의 테스트가 계약의 준수를 가장 직접적으로 보증한다.


이런 테스트를 자동으로 반복하게 만들어 두면, 시스템을 고칠 때마다 계약이 여전히 지켜지는지를 매번 확인할 수 있다. 내부를 바꾸다가 무심코 경계의 동작을 깨뜨리는 일은 흔한데, 자동화된 테스트가 그 순간을 즉시 잡아낸다. 손으로 매번 확인하는 것은 곧 지치고 빠뜨리게 되므로, 반복되는 검증은 자동으로 돌려야 한다.


테스트는 문서가 낡지 않게 지키는 역할도 한다. 테스트가 문서에 적힌 계약을 그대로 검증하고 있으면, 구현이 문서에서 멀어지는 순간 테스트가 실패한다. 이 실패가 문서를 고치거나 구현을 되돌리게 만든다. 문서와 테스트가 서로를 붙들어, 시스템에 대한 서술이 실제와 어긋나지 않게 유지된다.

살아 있는 문서

문서와 테스트가 부수적인 일로 취급되면 금세 낡는다. 시스템은 계속 바뀌는데 문서는 처음 상태에 멈춰 있으면, 얼마 지나지 않아 문서가 거짓말을 하게 된다. 실제와 다른 문서는 없는 것만 못하다. 쓰는 쪽이 그 문서를 믿고 요청을 구성했다가 어긋나는 경험을 하면, 이후로는 문서를 신뢰하지 않는다.


그래서 문서는 시스템과 함께 살아 움직여야 한다. 설계가 바뀌면 문서도 바뀌고, 문서가 바뀌면 테스트도 그에 맞춰 바뀌는 순환이 돌아야 한다. 이 순환이 끊기지 않게 하려면, 문서와 테스트를 설계 과정 안으로 끌어들여야 한다. 나중에 몰아서 하는 일이 아니라 매 변경과 함께 갱신되는 일로 다루는 것이다.


문서와 테스트에 드는 노력은 당장은 비용처럼 보인다. 그러나 그 비용은 시스템이 오래 쓰이고 여러 사람이 함께 다룰수록 회수된다. 잘 정리된 문서는 새로 합류하는 사람의 학습 비용을 줄이고, 촘촘한 테스트는 변경의 두려움을 줄인다. 이 둘이 갖추어진 시스템은 시간이 지나도 다루기 쉬운 상태로 남는다.


이번 편은 시스템의 경계를 문서로 밝히고 테스트로 검증하는 일을 다루었다. 규약을 충실히 따를수록 문서가 짧아지고, 계약을 촘촘히 시험할수록 그 문서가 살아 있게 된다는 점을 짚었다. 이로써 이 시리즈는 규약의 정의에서 출발해 요청과 응답, 메서드와 상태 코드, 헤더와 인증, 설계와 전송, 그리고 문서화와 검증까지 한 바퀴를 돌았다. 규약의 원리를 손에 쥐고 있으면, 새로운 도구와 프레임워크가 아무리 바뀌어도 그 아래에서 무슨 일이 벌어지는지를 스스로 읽어 낼 수 있다. 도구는 낡아도 규약의 원리는 낡지 않는다. 이 시리즈가 그 단단한 밑바탕을 세우는 데 보탬이 되었기를 바란다.