본문 바로가기

스웨거로 테스트하기

1. API 테스트 도구

인증까지 붙고 나면 확인해야 할 경우의 수가 갑자기 늘어납니다. 앞 절에서 만든 블로그만 해도 아래를 매번 확인해야 합니다.

  • 회원가입, 로그인이 되는가
  • 로그인 없이 글을 쓰면 401이 나오는가
  • 남의 글을 수정하면 403이 나오는가
  • 내 글은 수정되는가
  • 없는 글을 조회하면 404가 나오는가

코드를 한 줄 고칠 때마다 이 다섯 가지를 브라우저에서 손으로 확인하는 것은 현실적이지 않습니다. 그래서 요청을 미리 적어두고 순서대로 실행하는 방식이 필요합니다. Postman 같은 API 클라이언트 도구는 실무에서도 많이 사용되며, 이를 이용하면 API를 테스트하거나 문서화할 수 있습니다. 이 책에서는 VS Code 안에서 바로 쓸 수 있는 REST Client의 .http 파일과 FastAPI가 만들어주는 Swagger UI를 사용합니다.

방법특징이 책에서의 위치
.http 파일요청을 파일로 적어두고 클릭해서 실행지금 이 절
Swagger UI브라우저에서 눌러보며 확인지금 이 절
pytest 자동 테스트명령어 하나로 전부 검증7장

.http 파일과 Swagger UI는 사람이 눈으로 확인하는 방식입니다. 7장에서 배울 자동 테스트는 결과까지 기계가 확인합니다. 순서대로 익히면 됩니다.

2. .http 파일로 시나리오 테스트하기

2.1 시나리오 파일 만들기

프로젝트 폴더에 api.http 파일을 만들고, 앞 절에서 작성한 요청들을 시나리오 순서대로 정리합니다. 이렇게 만들어두면 위에서부터 차례로 실행하는 것만으로 API 전체를 점검할 수 있습니다.

@baseUrl = http://127.0.0.1:8000

# ==================================================
# 1단계: 계정 준비
# ==================================================

### 1-1. licat 회원가입
POST {{baseUrl}}/signup
Content-Type: application/json

{
    "email": "licat@weniv.co.kr",
    "password": "test1234"
}

### 1-2. mura 회원가입
POST {{baseUrl}}/signup
Content-Type: application/json

{
    "email": "mura@weniv.co.kr",
    "password": "test1234"
}

### 1-3. licat 로그인
# @name login1
POST {{baseUrl}}/token
Content-Type: application/x-www-form-urlencoded

username=licat@weniv.co.kr&password=test1234

### 1-4. mura 로그인
# @name login2
POST {{baseUrl}}/token
Content-Type: application/x-www-form-urlencoded

username=mura@weniv.co.kr&password=test1234

# ==================================================
# 2단계: 정상 동작 확인
# ==================================================

### 2-1. licat이 글 작성 (201)
# @name createBlog
POST {{baseUrl}}/blogs
Content-Type: application/json
Authorization: Bearer {{login1.response.body.access_token}}

{
    "title": "licat의 첫 글",
    "content": "안녕하세요"
}

### 2-2. 목록 조회 (200, 로그인 불필요)
GET {{baseUrl}}/blogs

### 2-3. 상세 조회 (200, 로그인 불필요)
GET {{baseUrl}}/blogs/{{createBlog.response.body.id}}

### 2-4. licat이 본인 글 수정 (200)
PUT {{baseUrl}}/blogs/{{createBlog.response.body.id}}
Content-Type: application/json
Authorization: Bearer {{login1.response.body.access_token}}

{
    "title": "고친 제목",
    "content": "고친 내용"
}

# ==================================================
# 3단계: 막혀야 하는 것들
# ==================================================

### 3-1. 로그인 없이 글 작성 (401)
POST {{baseUrl}}/blogs
Content-Type: application/json

{
    "title": "몰래 쓰기",
    "content": "안 될 겁니다"
}

### 3-2. 잘못된 토큰으로 글 작성 (401)
POST {{baseUrl}}/blogs
Content-Type: application/json
Authorization: Bearer this.is.not.a.valid.token

{
    "title": "가짜 토큰",
    "content": "안 될 겁니다"
}

### 3-3. mura가 licat의 글 수정 (403)
PUT {{baseUrl}}/blogs/{{createBlog.response.body.id}}
Content-Type: application/json
Authorization: Bearer {{login2.response.body.access_token}}

{
    "title": "남의 글 고치기",
    "content": "안 될 겁니다"
}

### 3-4. mura가 licat의 글 삭제 (403)
DELETE {{baseUrl}}/blogs/{{createBlog.response.body.id}}
Authorization: Bearer {{login2.response.body.access_token}}

### 3-5. 없는 글 조회 (404)
GET {{baseUrl}}/blogs/99999

### 3-6. 빈 제목으로 글 작성 (422)
POST {{baseUrl}}/blogs
Content-Type: application/json
Authorization: Bearer {{login1.response.body.access_token}}

{
    "title": "",
    "content": "제목이 비었습니다"
}

### 3-7. 잘못된 형식의 이메일로 회원가입 (422)
POST {{baseUrl}}/signup
Content-Type: application/json

{
    "email": "notanemail",
    "password": "test1234"
}

### 3-8. 짧은 비밀번호로 회원가입 (422)
POST {{baseUrl}}/signup
Content-Type: application/json

{
    "email": "gary@weniv.co.kr",
    "password": "123"
}

# ==================================================
# 4단계: 정리
# ==================================================

### 4-1. licat이 본인 글 삭제 (204)
DELETE {{baseUrl}}/blogs/{{createBlog.response.body.id}}
Authorization: Bearer {{login1.response.body.access_token}}

### 4-2. 삭제 확인 (404)
GET {{baseUrl}}/blogs/{{createBlog.response.body.id}}

2.2 응답을 변수로 이어 쓰기

이 파일에서 가장 유용한 기능은 앞선 응답을 다음 요청에서 꺼내 쓰는 것입니다.

### 1-3. licat 로그인
# @name login1
POST {{baseUrl}}/token
...

### 2-1. licat이 글 작성
POST {{baseUrl}}/blogs
Authorization: Bearer {{login1.response.body.access_token}}

요청 위에 # @name 이름을 적어두면, 이후 요청에서 {{이름.response.body.필드}} 형태로 응답값을 참조할 수 있습니다.

표현의미
{{login1.response.body.access_token}}login1 응답 본문의 access_token
{{createBlog.response.body.id}}createBlog 응답 본문의 id
{{createBlog.response.headers.location}}응답 헤더의 값

이 기능 덕분에 토큰이나 방금 만든 글의 ID를 복사해서 붙여넣는 작업이 사라집니다. 토큰이 만료되면 로그인 요청만 다시 실행하면 됩니다.

변수를 쓰려면 순서를 지켜야 합니다

{{login1.response...}}는 login1 요청을 이미 한 번 실행했을 때만 값을 갖습니다. 파일을 열자마자 3-3번을 실행하면 토큰이 비어 있어 401이 나옵니다.

위에서부터 순서대로 실행하는 습관을 들이시거나, 막히면 로그인 요청부터 다시 눌러보세요.

2.3 확인해야 할 것

위 파일을 순서대로 실행하면서 아래 표대로 나오는지 확인합니다.

요청기대하는 상태 코드
1-1, 1-2 회원가입201
1-3, 1-4 로그인200
2-1 글 작성201
2-2, 2-3 조회200
2-4 본인 글 수정200
3-1, 3-2 인증 없음401
3-3, 3-4 남의 글403
3-5 없는 글404
3-6, 3-7, 3-8 잘못된 입력422
4-1 본인 글 삭제204
4-2 삭제 확인404

3단계의 여덟 개가 이 시나리오의 핵심입니다. 잘 되는 것을 확인하는 것보다, 막혀야 할 것이 제대로 막히는지 확인하는 것이 훨씬 중요합니다. 인증 관련 사고는 대부분 "잘 되는 것만 확인하고 넘어갔을 때" 생깁니다.

2.4 파일을 나눠 관리하기

요청이 많아지면 파일 하나가 길어집니다. 기능별로 나누는 것이 좋습니다.

05_blog
┣━ 📁http/
┃   ┣━ 📄auth.http      # 회원가입, 로그인
┃   ┣━ 📄blogs.http     # 글 CRUD
┃   ┗━ 📄errors.http    # 막혀야 하는 요청들
┗━ 📄main.py

이 파일들은 그냥 텍스트이므로 Git에 함께 커밋할 수 있습니다. 새로 합류한 팀원에게 "이 폴더 열어서 위에서부터 눌러보세요"라고 하면 API 설명이 대부분 끝납니다.

토큰이 파일에 남지 않게 하세요

# @name으로 변수를 쓰면 토큰이 파일에 적히지 않습니다. 반대로 토큰 문자열을 직접 복사해서 붙여넣고 그대로 커밋하면, 그 토큰이 저장소 기록에 영구히 남습니다.

실습용 토큰이라 문제가 없어 보이지만, 실무에서는 이런 식으로 인증 정보가 저장소에 남는 사고가 자주 발생합니다. 처음부터 변수를 쓰는 습관을 들이시는 편이 좋습니다.

3. 스웨거로 테스트하기

.http 파일이 반복 실행에 좋다면, Swagger UI는 처음 만져볼 때와 다른 사람에게 보여줄 때 좋습니다. 스웨거로 테스트를 하고 싶은 경우 인증에 있어 몇 가지 알아둘 것이 있습니다.

http://127.0.0.1:8000/docs에 접속합니다.

3.1 자물쇠 표시 읽기

엔드포인트 목록을 보면 일부에만 자물쇠 아이콘이 붙어 있습니다.

엔드포인트자물쇠
GET /blogs없음
GET /blogs/{blog_id}없음
POST /blogs있음
PUT /blogs/{blog_id}있음
DELETE /blogs/{blog_id}있음
GET /me있음

이 표시는 우리가 따로 설정한 것이 아닙니다. 함수에 current_user: CurrentUser가 붙어 있는지를 FastAPI가 보고 자동으로 판단한 결과입니다.

문서와 코드가 어긋날 수 없다는 것이 이 방식의 장점입니다. 인증을 실수로 빼먹으면 문서에서도 자물쇠가 사라지므로, 문서만 봐도 알아챌 수 있습니다.

3.2 로그인하고 테스트하기

예를 들어, 우리가 만든 블로그에 글을 생성하고 싶다면 오른쪽 상단의 자물쇠 모양을 클릭하여 인증을 해야 합니다.

  1. 먼저 POST /signup을 펼쳐 Try it out을 누르고 계정을 만듭니다.
  2. 화면 오른쪽 위의 Authorize 버튼을 클릭합니다.
  3. username 칸에 이메일을, password 칸에 비밀번호를 입력합니다.
  4. Authorize를 누르면 Swagger UI가 대신 /token을 호출해 토큰을 받아 보관합니다.
  5. Close를 누르고 나오면 자물쇠가 잠긴 모양으로 바뀝니다.
  6. 이제 POST /blogs의 Try it out을 실행하면 토큰이 자동으로 붙어 나갑니다.

토큰 문자열을 직접 다루지 않아도 되는 것은 OAuth2PasswordBearer를 표준대로 썼기 때문입니다. 인증 방식을 직접 만들었다면 이 버튼은 동작하지 않습니다.

로그아웃하려면

Authorize 버튼을 다시 누르고 Logout을 선택하면 보관된 토큰이 지워집니다. 401 응답을 확인해보고 싶을 때 사용하세요.

3.3 Swagger UI의 한계

Swagger UI로는 아래와 같은 것을 하기 어렵습니다.

  • 여러 요청을 순서대로 한 번에 실행하기
  • 두 계정을 동시에 로그인해서 서로의 글에 접근해보기
  • 앞선 응답의 값을 다음 요청에 자동으로 넣기

특히 두 번째가 중요합니다. 이번 절의 403 확인은 계정이 두 개여야 하는데, Swagger UI는 한 번에 하나만 로그인할 수 있습니다. 그래서 권한 테스트에는 .http 파일이 더 적합합니다. 스웨거로는 이렇게 한 번에 테스트를 할 수 없기 때문에 가능하면 .http 파일이나 자동화 테스트 도구를 함께 사용하시는 것을 권합니다.

아래와 같이 나눠 쓰시면 됩니다.

상황도구
API를 처음 살펴볼 때Swagger UI
프론트엔드 개발자에게 설명할 때Swagger UI 또는 ReDoc
같은 요청을 반복할 때.http 파일
권한이나 에러 상황을 확인할 때.http 파일
코드를 고칠 때마다 전체를 검증할 때자동 테스트 (7장)

4. 손으로 하는 테스트의 한계

지금 방식에는 두 가지 문제가 있습니다.

사람이 확인해야 합니다. 3-3번 요청을 실행하고 403이 나왔는지 눈으로 봐야 합니다. 스무 개쯤 되면 하나쯤 대충 넘어가게 됩니다.

그리고 데이터가 쌓입니다. 시나리오를 실행할 때마다 계정과 글이 데이터베이스에 남습니다. 두 번째 실행하면 1-1번 회원가입이 400을 반환합니다. 이미 있는 이메일이기 때문입니다. 그때마다 blogs.db를 지우고 다시 시작해야 합니다.

7장에서는 이 두 가지를 해결합니다. 기대하는 상태 코드를 코드로 적어두면 기계가 확인해주고, 테스트마다 깨끗한 데이터베이스에서 시작하게 만들 수 있습니다.

# 7장에서 만들게 될 코드의 모습입니다
def test_남의_글은_수정할_수_없다(client, licat_token, mura_token):
    blog_id = create_blog(client, licat_token)

    response = client.put(
        f"/blogs/{blog_id}",
        json={"title": "고치기", "content": "시도"},
        headers={"Authorization": f"Bearer {mura_token}"},
    )

    assert response.status_code == 403

.http 파일에 적어둔 3단계 여덟 개가 그대로 테스트 함수 여덟 개가 됩니다. 지금 만들어 둔 시나리오를 그대로 옮기면 됩니다.

연습문제

  1. api.http 파일을 auth.http, blogs.http, errors.http 세 개로 나눠보세요.
  2. 3단계에 새로운 항목을 추가해보세요. 예를 들어 만료된 토큰으로 요청하기, 본문 없이 POST 보내기 등이 있습니다.
  3. 4단계까지 실행한 뒤 파일 맨 위로 돌아가 다시 실행해보세요. 어떤 요청이 실패하는지 확인하고 왜 그런지 설명해보세요.
  4. Swagger UI에서 Authorize를 하지 않은 상태로 POST /blogs를 실행해보세요. 응답이 어떻게 나오는지 확인해보세요.
스웨거로 테스트하기 - FastAPI 베이스캠프 | 위니버시티