๐ ์ด ๊ธ์ ์ด๋ฐ ๋ถ๊ป ์ถ์ฒํฉ๋๋ค
- Python๊ณผ requests๋ก GET ์์ฒญ ํ ์คํธ๋ฅผ ์์ฑํ๊ณ ์ถ์ ๋ถ
- ์ํ ์ฝ๋์ JSON ์๋ต์ ๊ฒ์ฆํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ฐ๊ณ ์ถ์ ๋ถ
- pytest๋ก API ํ ์คํธ๋ฅผ ์๋ํํ๊ณ ์ถ์ QA ์์ง๋์ด
- ์๋ต์ ์คํค๋ง ๊ฒ์ฆ๊น์ง ๊ตฌํํ๊ณ ์ถ์ ๋ถ
โ ์ด ๊ธ์ ์ฝ์ผ๋ฉด ์ ์ ์๋ ๊ฒ
- pytest์ requests๋ก GET ์์ฒญ API ํ ์คํธ๋ฅผ ์์ฑํ๋ ๊ธฐ๋ณธ ํจํด
- ์ํ ์ฝ๋ใป์๋ต ๋ฐ๋ใปํค๋ ๊ฒ์ฆ ๋ฐฉ๋ฒ
- JSON ์คํค๋ง๏ผํ์ ใปํ๋๏ผ๋ฅผ ๊ฒ์ฆํ๋ ๊ตฌํ ๋ฐฉ๋ฒ
- ์ ์๊ณใป์ด์๊ณ API ํ ์คํธ ์ผ์ด์ค ์ค๊ณ ํจํด
Python๊ณผ pytest๋ก API ํ ์คํธ๋ฅผ ์์ฑํ ๋ ์ฒ์์ผ๋ก ๊ตฌํํ๋ ๊ฒ์ด GET ์์ฒญ ํ ์คํธ์ ๋๋ค. pytest์ requests๋ฅผ ์ฌ์ฉํด์ ใ์ํ ์ฝ๋๋ 200์ธ๊ฐ๏ผใใJSON ์๋ต์ ํ์ํ ํ๋๊ฐ ํฌํจ๋์ด ์๋๊ฐ๏ผใ๋ฅผ ์๋์ผ๋ก ๊ฒ์ฆํ๋ ๋ฐฉ๋ฒ์ ํด์คํฉ๋๋ค.
์ด ๊ธ์์๋ ๊ธฐ๋ณธ์ ์ธ ์ํ ์ฝ๋ ํ์ธ๋ถํฐ ์๋ต ๋ฐ๋์ ์คํค๋ง ๊ฒ์ฆ๊น์ง๋ฅผ ์ค๋ฌด์์ ์ฌ์ฉํ ์ ์๋ ์ฝ๋์ ํจ๊ป ์๊ฐํฉ๋๋ค.
- 00. API ํ ์คํธ์ GET ์์ฒญ์ ๊ธฐ๋ณธ
- 01. pytest + requests์ API ํ ์คํธ ํ๊ฒฝ
- 02. Python์ผ๋ก API GET ํ ์คํธ๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ
- 03. API ํ ์คํธ ์คํค๋ง๏ผํ์ ๏ผ๊ฒ์ฆ
- 04. API ํ ์คํธ ์ด์๊ณ ํจํด
- 05. API ํ ์คํธ ์๋ต ์๊ฐ ๊ฒ์ฆ
- 06. GET ํ ์คํธ ์ ์ฒด ์ฝ๋
- 07. ์์ฃผ ๊ฒช๋ ๋ฌธ์ & ํด๊ฒฐ๋ฒ
- โ response.json()์์ KeyError๊ฐ ๋ฐ์ํ๋ค
- โก ์ํ ์ฝ๋๊ฐ 200์ธ๋ฐ ํ ์คํธ๊ฐ ์คํจํ๋ค
- โข isinstance()๋ก ์ค์ฒฉ๋ JSON ํ์ ์ฒดํฌ์ ์คํจํ๋ค
- โฃ ์๋ต ์๊ฐ์ด ํ๊ฒฝ์ ๋ฐ๋ผ ๋ค์ฅ๋ ์ฅํ๋ค
- โค ๋ชฉ๋ก ์ทจ๋์์ ๋ฐ์ดํฐ ๊ฑด์๊ฐ ๋ฐ๋์ด ํ ์คํธ๊ฐ ๊นจ์ง๋ค
- 08. ์์ฃผ ๋ฌป๋ ์ง๋ฌธ๏ผFAQ๏ผ
- 09. ์ ๋ฆฌ
00. API ํ ์คํธ์ GET ์์ฒญ์ ๊ธฐ๋ณธ
API ํ ์คํธ๋, ๋ธ๋ผ์ฐ์ ๋ฅผ ์ฌ์ฉํ์ง ์๊ณ HTTP ์์ฒญ์ ์ง์ ์ ์กํ์ฌ ์๋ต์ ๊ฒ์ฆํ๋ ํ ์คํธ์ ๋๋ค. GET ์์ฒญ์ API์์ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ์กฐ์์ผ๋ก ใ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ใ ์ญํ ์ ๋ด๋นํฉ๋๋ค.
GET ํ ์คํธ์์๋ ์ฃผ๋ก ๋ค์ 3๊ฐ์ง๋ฅผ ๊ฒ์ฆํฉ๋๋ค.
| ํ์ธ ํญ๋ชฉ | ๋ด์ฉ | ์ |
|---|---|---|
| ์ํ ์ฝ๋ | ์์ํ HTTP ์ํ๊ฐ ๋ฐํ๋๋๊ฐ | 200, 404, 401 ๋ฑ |
| ์๋ต ๋ฐ๋ | JSON ๊ฐ์ด ์ฌ๋ฐ๋ฅธ๊ฐ | idใปnameใปemail ๊ฐ |
| ์คํค๋ง ๊ฒ์ฆ | JSON ํ๋ใปํ์ ์ด ์ฌ๋ฐ๋ฅธ๊ฐ | id๋ intํ, name์ strํ |
๐ก ํฌ์ธํธ๏ผ์ค๋ฌด์์๋ ์ํ ์ฝ๋๋ฟ๋ง ์๋๋ผ ์๋ต ๋ด์ฉ๊น์ง ๊ฒ์ฆํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. ์ํ๊ฐ 200์ด์ด๋ ์๋ต ๊ฐ์ด ํ๋ฆฌ๋ฉด ๋ฒ๊ทธ์ ๋๋ค.
01. pytest + requests์ API ํ ์คํธ ํ๊ฒฝ
์์ง ํ๊ฒฝ ๊ตฌ์ถ์ด ์ ๋ ๋ถ์ ๋จผ์ ์ค์นํด์ฃผ์ธ์.
pip install requests pytest pytest-html์ด๋ฒ ํ
์คํธ ๋์์ ๋ฌด๋ฃ ๋ชฉ๏ผmock๏ผ API JSONPlaceholder ์
๋๋ค.
GET https://jsonplaceholder.typicode.com/users/1์๋ต ์ํ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz",
"phone": "1-770-736-8031 x56442",
"website": "hildegard.org"
}๐ก ํฌ์ธํธ๏ผJSONPlaceholder๋ ๋ฐ์ดํฐ ๋ณ๊ฒฝใป์ญ์ ๊ฐ ์ค์ ๋ก๋ ์ด๋ฃจ์ด์ง์ง ์๋ ๋ชฉ API์ ๋๋ค. ํ์ต์ฉ์ผ๋ก ์์ฌํ๊ณ ์ฌ์ฉํ ์ ์์ต๋๋ค.
02. Python์ผ๋ก API GET ํ ์คํธ๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ
์ํ ์ฝ๋ ๊ฒ์ฆ
๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ํ ์คํธ์ ๋๋ค. ์์ฒญ์ ๋ณด๋ด์ 200์ด ๋ฐํ๋๋ ๊ฒ์ ํ์ธํฉ๋๋ค.
import requests
BASE_URL = "https://jsonplaceholder.typicode.com"
def test_get_user_status_code():
"""TC01: ์ํ ์ฝ๋๊ฐ 200์ด์ด์ผ ํ๋ค"""
response = requests.get(f"{BASE_URL}/users/1")
assert response.status_code == 200, \
f"๊ธฐ๋๊ฐ: 200, ์ค์ ๊ฐ: {response.status_code}"
print(f"\nโ
TC01 PASS | ์ํ ์ฝ๋: {response.status_code}")raise_for_status()๋ก 4xx/5xx๋ฅผ ํ์คํ ๊ฒ์ง
์ค๋ฌด์์ ์์ฃผ ์ฌ์ฉ๋๋ ์์ฑ๋ฒ์
๋๋ค. raise_for_status() ๋ฅผ ์ถ๊ฐํ๋ ๊ฒ๋ง์ผ๋ก 4xx / 5xx ์ค๋ฅ๊ฐ ๋ฐํ๋์ ๋ ์๋์ผ๋ก ์์ธ๋ฅผ ๋ฐ์์์ผ ํ
์คํธ๋ฅผ ์คํจ์ํฌ ์ ์์ต๋๋ค.
def test_get_user_raise_for_status():
"""TC02: 4xx/5xx ์ค๋ฅ ์ ์์ธ๊ฐ ๋ฐ์ํด์ผ ํ๋ค"""
response = requests.get(f"{BASE_URL}/users/1")
# 4xx/5xx ๋ ์๋์ผ๋ก HTTPError ๋ฐ์
response.raise_for_status()
assert response.status_code == 200
print(f"\nโ
TC02 PASS | raise_for_status() ์ ์ ํต๊ณผ")๐ก ํฌ์ธํธ๏ผresponse.raise_for_status() ๋ ์ค๋ฌด์์ ํ์ 1ํ์
๋๋ค. assert๋ก ์ํ ์ฝ๋๋ฅผ ์ฒดํฌํ๊ธฐ ์ ์ ๋ฃ์ด๋๋ฉด ์๊ธฐ์น ๋ชปํ ์ค๋ฅ ์๋ต์ ํ์คํ ๊ฒ์งํ ์ ์์ต๋๋ค.
ํค๋ ๊ฒ์ฆ
์๋ต์ Content-Type์ด ์ฌ๋ฐ๋ฅธ์ง๋ ํ์ธํฉ๋๋ค. JSON์ ๊ธฐ๋ํ๊ณ ์๋๋ฐ HTML์ด ๋ฐํ๋๋ ๊ฒฝ์ฐ๋ฅผ ๊ฒ์งํ ์ ์์ต๋๋ค.
def test_get_user_header():
"""TC03: Content-Type์ด JSON์ด์ด์ผ ํ๋ค"""
response = requests.get(f"{BASE_URL}/users/1")
response.raise_for_status()
content_type = response.headers.get("Content-Type", "")
assert "application/json" in content_type, \
f"์์ ๋ฐ์ Content-Type: {content_type}"
print(f"\nโ
TC03 PASS | Content-Type: {content_type}")๐ก ํฌ์ธํธ๏ผ์ค์ Content-Type ํค๋๋ application/json; charset=utf-8 ํํ๋ก ๋ฐํ๋ฉ๋๋ค. == ์์ ์ผ์น๊ฐ ์๋ in ์ผ๋ก ๋ถ๋ถ ์ผ์น๋ฅผ ์ฌ์ฉํ์ธ์.
์๋ต JSON ๊ฒ์ฆ
์ํ ์ฝ๋๋ฟ๋ง ์๋๋ผ ์๋ต ๊ฐ์ด ์ฌ๋ฐ๋ฅธ์ง๋ ํ์ธํฉ๋๋ค.
def test_get_user_response_body():
"""TC04: ์๋ต ๋ฐ๋์ ๊ฐ์ด ์ฌ๋ฐ๋ฅด๋ค"""
response = requests.get(f"{BASE_URL}/users/1")
response.raise_for_status()
body = response.json()
assert body["id"] == 1, f"id ๋ถ์ผ์น: {body['id']}"
assert body["name"] == "Leanne Graham", f"name ๋ถ์ผ์น: {body['name']}"
assert body["email"] == "Sincere@april.biz", f"email ๋ถ์ผ์น: {body['email']}"
print(f"\nโ
TC04 PASS | ์ฌ์ฉ์ ์ด๋ฆ: {body['name']}")ํ์ ํ๋ ์กด์ฌ ํ์ธ
ํ์ํ ํ๋๊ฐ ์ ๋ถ ํฌํจ๋์ด ์๋์ง ์ฒดํฌํฉ๋๋ค.
def test_get_user_fields_exist():
"""TC05: ํ์ ํ๋๊ฐ ์กด์ฌํด์ผ ํ๋ค"""
response = requests.get(f"{BASE_URL}/users/1")
response.raise_for_status()
body = response.json()
required_fields = ["id", "name", "username", "email", "phone", "website"]
for field in required_fields:
assert field in body, f"ํ์ ํ๋ '{field}' ๊ฐ ์กด์ฌํ์ง ์์ต๋๋ค"
print(f"\nโ
TC05 PASS | ๋ชจ๋ ํ์ ํ๋ ํ์ธ ์๋ฃ")๐ก ํฌ์ธํธ๏ผํ๋๋ฅผ ๋ฆฌ์คํธ๋ก ๊ด๋ฆฌํ๋ฉด ํ์ ํญ๋ชฉ์ด ๋์ด๋๋ ์ฝ๋ ๋ณ๊ฒฝ์ด ์ต์ํ๋ฉ๋๋ค. ์ค๋ฌด์์ ์์ฃผ ์ฌ์ฉ๋๋ ์์ฑ๋ฒ์ ๋๋ค.
03. API ํ ์คํธ ์คํค๋ง๏ผํ์ ๏ผ๊ฒ์ฆ
์๋ต ๊ฐ๋ฟ๋ง ์๋๋ผ ํ์ ๏ผint/str/dict๏ผ์ด ์ฌ๋ฐ๋ฅธ์ง๋ ํ ์คํธํฉ๋๋ค. ํ์ ์ด ๋ณํ๋ฉด ํ๋ก ํธ์๋๊ฐ ๋ง๊ฐ์ง ์ ์๊ธฐ ๋๋ฌธ์ ์ค๋ฌด์์ ์ค์ํ ๊ฒ์ฆ์ ๋๋ค.
def test_get_user_schema():
"""TC06: ์๋ต ์คํค๋ง๏ผํ์
๏ผ๊ฐ ์ฌ๋ฐ๋ฅด๋ค"""
response = requests.get(f"{BASE_URL}/users/1")
response.raise_for_status()
body = response.json()
assert isinstance(body["id"], int), f"id๋ int์ฌ์ผ ํจ: {type(body['id'])}"
assert isinstance(body["name"], str), f"name์ str์ฌ์ผ ํจ: {type(body['name'])}"
assert isinstance(body["username"], str), f"username์ str์ฌ์ผ ํจ: {type(body['username'])}"
assert isinstance(body["email"], str), f"email์ str์ฌ์ผ ํจ: {type(body['email'])}"
assert isinstance(body["address"], dict), f"address๋ dict์ฌ์ผ ํจ: {type(body['address'])}"
assert isinstance(body["company"], dict), f"company๋ dict์ฌ์ผ ํจ: {type(body['company'])}"
print(f"\nโ
TC06 PASS | ์คํค๋ง ๊ฒ์ฆ ์๋ฃ")๐ก ํฌ์ธํธ๏ผisinstance() ๋ฅผ ์ฌ์ฉํ๋ฉด ํ์
์ฒดํฌ๋ฅผ ๊ฐ๋จํ๊ฒ ํ ์ ์์ต๋๋ค. API ์๋ต ํ์
์ด ๋ณ๊ฒฝ๋์ ๋ ์ฆ์ ๊ฒ์งํ ์ ์์ด ์ค๋ฌด์์๋ ํ์ ๊ฒ์ฆ์
๋๋ค.
04. API ํ ์คํธ ์ด์๊ณ ํจํด
์กด์ฌํ์ง ์๋ ๋ฆฌ์์ค์ ์ ๊ทผ๏ผ404 ํ์ธ๏ผ
def test_get_nonexistent_user():
"""TC07: ์กด์ฌํ์ง ์๋ ์ฌ์ฉ์์ ์ ๊ทผํ๋ฉด 404๊ฐ ๋ฐํ๋๋ค"""
response = requests.get(f"{BASE_URL}/users/99999")
assert response.status_code == 404, \
f"์กด์ฌํ์ง ์๋ ๋ฆฌ์์ค์ ๊ธฐ๋๊ฐ์ 404: {response.status_code}"
print(f"\nโ
TC07 PASS | 404 ์ ์ ํ์ธ: {response.status_code}")๋ชฉ๋ก ์ทจ๋ ๋ฐ ๊ฑด์ ํ์ธ
def test_get_all_users():
"""TC08: ์ ์ฒด ์ฌ์ฉ์ ๋ชฉ๋ก์ด 10๊ฑด ์ทจ๋๋๋ค"""
response = requests.get(f"{BASE_URL}/users")
response.raise_for_status()
body = response.json()
assert isinstance(body, list), "์๋ต์ ๋ฆฌ์คํธ ํ์์ด์ด์ผ ํ๋ค"
assert len(body) == 10, f"์ฌ์ฉ์ ์๋ 10๊ฑด์ด ๊ธฐ๋๊ฐ: {len(body)}๊ฑด"
print(f"\nโ
TC08 PASS | ์ฌ์ฉ์ ๋ชฉ๋ก ์ทจ๋ ์ฑ๊ณต: {len(body)}๊ฑด")โ ๏ธ ์ฃผ์๏ผ์ด์๊ณ ํ ์คํธ๋ ์ ์๊ณ์ ๋ง์ฐฌ๊ฐ์ง๋ก ์ค์ํฉ๋๋ค. ์กด์ฌํ์ง ์๋ ๋ฆฌ์์ค์ ๋ํด ์ฌ๋ฐ๋ฅด๊ฒ 404๊ฐ ๋ฐํ๋๋์ง ํ์ธํจ์ผ๋ก์จ API์ ์๋ฌ ํธ๋ค๋ง์ด ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ๋์ด ์๋์ง ๊ฒ์ฆํ ์ ์์ต๋๋ค.
05. API ํ ์คํธ ์๋ต ์๊ฐ ๊ฒ์ฆ
์ฑ๋ฅ ๊ด์ ์์ ์๋ต์ด ์ผ์ ์๊ฐ ๋ด์ ๋ฐํ๋๋์ง๋ ํ์ธํฉ์๋ค.
import time
def test_get_user_response_time():
"""TC09: ์๋ต ์๊ฐ์ด 2000ms ์ด๋ด์ฌ์ผ ํ๋ค"""
start = time.time()
response = requests.get(f"{BASE_URL}/users/1")
elapsed_ms = (time.time() - start) * 1000
assert response.status_code == 200
assert elapsed_ms < 2000, \
f"์๋ต์ด ๋๋ฌด ๋๋ฆฝ๋๋ค: {elapsed_ms:.0f}ms๏ผ์ํ: 2000ms๏ผ"
print(f"\nโ
TC09 PASS | ์๋ต ์๊ฐ: {elapsed_ms:.0f}ms")๐ก ํฌ์ธํธ๏ผ์๋ต ์๊ฐ์ ์๊ณ๊ฐ์ ์์คํ ์๊ฑด์ ๋ง๊ฒ ์กฐ์ ํ์ธ์. CI/CD์ ํตํฉํ๋ฉด ์ฑ๋ฅ ์ ํ๋ฅผ ์๋์ผ๋ก ๊ฒ์งํ ์ ์์ต๋๋ค.
06. GET ํ ์คํธ ์ ์ฒด ์ฝ๋
"""
GET API Test
Target: JSONPlaceholder (https://jsonplaceholder.typicode.com)
Framework: Python + requests + pytest
"""
import time
import requests
BASE_URL = "https://jsonplaceholder.typicode.com"
def test_get_user_status_code():
"""TC01: ์ํ ์ฝ๋๊ฐ 200์ด์ด์ผ ํ๋ค"""
response = requests.get(f"{BASE_URL}/users/1")
assert response.status_code == 200, \
f"๊ธฐ๋๊ฐ: 200, ์ค์ ๊ฐ: {response.status_code}"
print(f"\nโ
TC01 PASS | status: {response.status_code}")
def test_get_user_raise_for_status():
"""TC02: 4xx/5xx ์ค๋ฅ ์ ์์ธ๊ฐ ๋ฐ์ํด์ผ ํ๋ค"""
response = requests.get(f"{BASE_URL}/users/1")
response.raise_for_status()
assert response.status_code == 200
print(f"\nโ
TC02 PASS | raise_for_status() ์ ์ ํต๊ณผ")
def test_get_user_header():
"""TC03: Content-Type์ด JSON์ด์ด์ผ ํ๋ค"""
response = requests.get(f"{BASE_URL}/users/1")
response.raise_for_status()
content_type = response.headers.get("Content-Type", "")
assert "application/json" in content_type, \
f"์์ ๋ฐ์ Content-Type: {content_type}"
print(f"\nโ
TC03 PASS | Content-Type: {content_type}")
def test_get_user_response_body():
"""TC04: ์๋ต ๋ฐ๋์ ๊ฐ์ด ์ฌ๋ฐ๋ฅด๋ค"""
response = requests.get(f"{BASE_URL}/users/1")
response.raise_for_status()
body = response.json()
assert body["id"] == 1
assert body["name"] == "Leanne Graham"
assert body["email"] == "Sincere@april.biz"
print(f"\nโ
TC04 PASS | name: {body['name']}")
def test_get_user_fields_exist():
"""TC05: ํ์ ํ๋๊ฐ ์กด์ฌํด์ผ ํ๋ค"""
response = requests.get(f"{BASE_URL}/users/1")
response.raise_for_status()
body = response.json()
for field in ["id", "name", "username", "email", "phone", "website"]:
assert field in body, f"ํ์ ํ๋ '{field}' ๊ฐ ์กด์ฌํ์ง ์์ต๋๋ค"
print(f"\nโ
TC05 PASS | ๋ชจ๋ ํ์ ํ๋ ํ์ธ ์๋ฃ")
def test_get_user_schema():
"""TC06: ์คํค๋ง๏ผํ์
๏ผ๊ฐ ์ฌ๋ฐ๋ฅด๋ค"""
response = requests.get(f"{BASE_URL}/users/1")
response.raise_for_status()
body = response.json()
assert isinstance(body["id"], int)
assert isinstance(body["name"], str)
assert isinstance(body["email"], str)
assert isinstance(body["address"], dict)
assert isinstance(body["company"], dict)
print(f"\nโ
TC06 PASS | ์คํค๋ง ๊ฒ์ฆ ์๋ฃ")
def test_get_nonexistent_user():
"""TC07: ์กด์ฌํ์ง ์๋ ์ฌ์ฉ์๋ 404๋ฅผ ๋ฐํํด์ผ ํ๋ค"""
response = requests.get(f"{BASE_URL}/users/99999")
assert response.status_code == 404
print(f"\nโ
TC07 PASS | 404 ํ์ธ: {response.status_code}")
def test_get_all_users():
"""TC08: ์ ์ฒด ์ฌ์ฉ์ ๋ชฉ๋ก์ด 10๊ฑด ์ทจ๋๋๋ค"""
response = requests.get(f"{BASE_URL}/users")
response.raise_for_status()
body = response.json()
assert isinstance(body, list)
assert len(body) == 10
print(f"\nโ
TC08 PASS | ์ฌ์ฉ์ ์: {len(body)}๊ฑด")
def test_get_user_response_time():
"""TC09: ์๋ต ์๊ฐ์ด 2000ms ์ด๋ด์ฌ์ผ ํ๋ค"""
start = time.time()
response = requests.get(f"{BASE_URL}/users/1")
elapsed_ms = (time.time() - start) * 1000
assert response.status_code == 200
assert elapsed_ms < 2000, f"{elapsed_ms:.0f}ms๏ผ์ํ: 2000ms๏ผ"
print(f"\nโ
TC09 PASS | ์๋ต ์๊ฐ: {elapsed_ms:.0f}ms")์คํ ์ปค๋งจ๋
pytest test_get_api.py -v -s์คํ ๊ฒฐ๊ณผ ์ํ
test_get_api.py::test_get_user_status_code PASSED
test_get_api.py::test_get_user_raise_for_status PASSED
test_get_api.py::test_get_user_header PASSED
test_get_api.py::test_get_user_response_body PASSED
test_get_api.py::test_get_user_fields_exist PASSED
test_get_api.py::test_get_user_schema PASSED
test_get_api.py::test_get_nonexistent_user PASSED
test_get_api.py::test_get_all_users PASSED
test_get_api.py::test_get_user_response_time PASSED
9 passed in 3.45s โ
07. ์์ฃผ ๊ฒช๋ ๋ฌธ์ & ํด๊ฒฐ๋ฒ
๊ตฌํ ์ค ์ค์ ๋ก ๊ฒช์๋ ๋ฌธ์ ๋ค์ ์ ๋ฆฌํ์ต๋๋ค. ๊ฐ์ ๊ณณ์์ ๋งํ๋ ๋ถ๋ค๊ป ๋์์ด ๋๋ฉด ์ข๊ฒ ์ต๋๋ค.
โ response.json()์์ KeyError๊ฐ ๋ฐ์ํ๋ค
JSON์์ ๊ฐ์ ๊บผ๋ผ ๋ ํค ์ด๋ฆ ์คํ๋ ๋์๋ฌธ์ ์ฐจ์ด๋ก KeyError ๊ฐ ๋ฐ์ํ์ต๋๋ค.
# โ ํค ์ด๋ฆ ์คํ
assert body["Name"] == "Leanne Graham" # KeyError๏ผ๏ผN์ด ๋๋ฌธ์๏ผ
# โ
๋จผ์ print()๋ก ํค ์ด๋ฆ ํ์ธ
print(body.keys())
# dict_keys(['id', 'name', 'username', 'email', ...])
# โ
get()์ ์ฌ์ฉํ๋ฉด KeyError๋ฅผ ๋ฐฉ์งํ ์ ์๋ค
name = body.get("name", "") # ํค๊ฐ ์์ผ๋ฉด ๋น ๋ฌธ์์ด ๋ฐํ๐ก ํฌ์ธํธ๏ผ์ฒ์ ํ
์คํธํ๋ API๋ ๋ฐ๋์ print(response.json()) ๋ก ์๋ต ๊ตฌ์กฐ๋ฅผ ํ์ธํ๊ณ ๋์ ํ
์คํธ ์ฝ๋๋ฅผ ์์ฑํฉ์๋ค.
โก ์ํ ์ฝ๋๊ฐ 200์ธ๋ฐ ํ ์คํธ๊ฐ ์คํจํ๋ค
์ํ ์ฝ๋๊ฐ 200์ธ๋ฐ assert๊ฐ ์คํจํ๋ ๊ฒฝ์ฐ๊ฐ ์์์ต๋๋ค. ์์ธ์ ์๋ต ๋ฐ๋๊ฐ ๋น JSON์ด๋ ์์ ์ธ์ ๊ตฌ์กฐ๋ก ๋ฐํ๋๊ณ ์์๊ธฐ ๋๋ฌธ์ ๋๋ค.
# โ ์ํ ์ฝ๋๋ง ํ์ธํ๊ณ ์์ฌํด๋ฒ๋ฆฐ๋ค
assert response.status_code == 200 # ํต๊ณผํด๋ ๋ด์ฉ์ด ๋น์ด์์ ๊ฐ๋ฅ์ฑ
# โ
์๋ต ๋ฐ๋๋ ํจ๊ป ํ์ธํ๋ค
assert response.status_code == 200
assert response.json() is not None
assert len(response.json()) > 0๐ก ํฌ์ธํธ๏ผใ200์ด ๋ฐํ๋๋ค = ์ฑ๊ณตใ์ด ์๋๋๋ค. ์๋ต ๋ด์ฉ๊น์ง ๊ฒ์ฆํ๋ ๊ฒ์ด API ํ ์คํธ์ ๋ณธ์ง์ ๋๋ค.
โข isinstance()๋ก ์ค์ฒฉ๋ JSON ํ์ ์ฒดํฌ์ ์คํจํ๋ค
์ค์ฒฉ๋ JSON ํ๋๏ผaddress.city ๋ฑ๏ผ๋ฅผ ๊ฒ์ฆํ๋ ค๊ณ ํ์ ๋ ์ฌ๋ฐ๋ฅด๊ฒ ์ ๊ทผํ์ง ๋ชปํด ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.
# โ ์ค์ฒฉ๋ ํ๋์ ์ง์ ์ ๊ทผํ๋ ค๊ณ ํด์ ์คํจ
assert isinstance(body["address.city"], str) # KeyError๏ผ
# โ
์ฌ๋ฐ๋ฅธ ์ค์ฒฉ ์ ๊ทผ ๋ฐฉ๋ฒ
assert isinstance(body["address"]["city"], str)
assert isinstance(body["address"]["zipcode"], str)๐ก ํฌ์ธํธ๏ผ์ค์ฒฉ๋ JSON์ body["address"]["city"] ์ฒ๋ผ ๋จ๊ณ์ ์ผ๋ก ์ ๊ทผํฉ๋๋ค. ๋จผ์ print(body["address"]) ๋ก ๊ตฌ์กฐ๋ฅผ ํ์ธํ๊ณ ๋์ ์์ฑํ๋ฉด ์์ฌ์
๋๋ค.
โฃ ์๋ต ์๊ฐ์ด ํ๊ฒฝ์ ๋ฐ๋ผ ๋ค์ฅ๋ ์ฅํ๋ค
๋ก์ปฌ์์๋ 1000ms ์ด๋ด์ธ๋ฐ CI ํ๊ฒฝ์์๋ 2000ms๋ฅผ ์ด๊ณผํด์ ํ ์คํธ๊ฐ ์คํจํ๋ ๊ฒฝ์ฐ๊ฐ ์์์ต๋๋ค.
# โ ๋๋ฌด ์๊ฒฉํ ์๊ณ๊ฐ
assert elapsed_ms < 500 # CI์์๋ ์คํจํ ์ ์๋ค
# โ
ํ๊ฒฝ์ ๊ณ ๋ คํ ์ฌ์ ์๋ ์๊ณ๊ฐ์ผ๋ก ์ค์
assert elapsed_ms < 3000 # 3์ด ์ด๋ด๋ฉด ์ถฉ๋ถโ ๏ธ ์ฃผ์๏ผ์๋ต ์๊ฐ์ ๋คํธ์ํฌ ํ๊ฒฝ๊ณผ ์๋ฒ ๋ถํ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋๋ค. ์ฌ์ ์๋ ์๊ณ๊ฐ์ ์ค์ ํด๋์ธ์.
โค ๋ชฉ๋ก ์ทจ๋์์ ๋ฐ์ดํฐ ๊ฑด์๊ฐ ๋ฐ๋์ด ํ ์คํธ๊ฐ ๊นจ์ง๋ค
ํ ์คํธ ํ๊ฒฝ์ ๋ฐ์ดํฐ๊ฐ ๋ฐ๋๋ฉด ๊ฑด์๋ ๋ฐ๋์ด ํ ์คํธ๊ฐ ๊นจ์ก์ต๋๋ค. ์ค์ ํ๋ก์ ํธ์์๋ ๊ณ ์ ๊ฐ๋ณด๋ค ์กฐ๊ฑด ๊ฒ์ฆ์ด ๋ ๊ฒฌ๊ณ ํฉ๋๋ค.
# โ ๊ฑด์๋ฅผ ๊ณ ์ ๊ฐ์ผ๋ก ๊ฒ์ฆํ๋ฉด ๊นจ์ง๊ธฐ ์ฝ๋ค
assert len(body) == 10 # ๋ฐ์ดํฐ๊ฐ ์ฆ๊ฐํ๋ฉด ์คํจ
# โ
๊ฑด์๋ณด๋ค ใ1๊ฑด ์ด์ ์กด์ฌํ ๊ฒใ์ ๊ฒ์ฆํ๋ ํธ์ด ๊ฒฌ๊ณ ํ๋ค
assert len(body) >= 1, "์ฌ์ฉ์๊ฐ 1๊ฑด๋ ์กด์ฌํ์ง ์์ต๋๋ค"
assert isinstance(body, list), "์๋ต์ ๋ฆฌ์คํธ ํ์์ด์ด์ผ ํ๋ค"๐ก ํฌ์ธํธ๏ผJSONPlaceholder ๊ฐ์ ๊ณ ์ ๋ชฉ API๋ ๊ณ ์ ๊ฐ๋ OK์ ๋๋ค. ์ค์ ํ๋ก์ ํธ์์๋ ๋ฐ์ดํฐ๊ฐ ๋ฐ๋์ด๋ ๊นจ์ง์ง ์๋ ์ค๊ณ๋ฅผ ์์ํฉ์๋ค.
08. ์์ฃผ ๋ฌป๋ ์ง๋ฌธ๏ผFAQ๏ผ
Q. API ํ
์คํธ์ GET ํ
์คํธ์์ ์ต์ํ ํ์ธํด์ผ ํ ๊ฒ์ ๋ฌด์์ธ๊ฐ์๏ผ
A. ์ต์ํ ใ์ํ ์ฝ๋๊ฐ 200์ธ ๊ฒใ๊ณผ ใํ์ ํ๋๊ฐ ์กด์ฌํ๋ ๊ฒใ 2๊ฐ์ง์
๋๋ค. ์ฌ์ ๊ฐ ์๋ค๋ฉด ํ์
์ฒดํฌใป๊ฐ ๊ฒ์ฆใป์๋ต ์๊ฐ๋ ์ถ๊ฐํ๋ฉด ์ค๋ฌด ์์ค์ API ํ
์คํธ๊ฐ ๋ฉ๋๋ค.
Q. requests.get()๊ณผ requests.head()์ ์ฐจ์ด๋ ๋ฌด์์ธ๊ฐ์๏ผ
A. get() ์ ์๋ต ๋ฐ๋๋ ์ทจ๋ํฉ๋๋ค. head() ๋ ํค๋ ์ ๋ณด๋ง ์ทจ๋ํ๊ณ ๋ฐ๋๋ฅผ ์ทจ๋ํ์ง ์๊ธฐ ๋๋ฌธ์ ๋น ๋ฆ
๋๋ค. ์ํ ์ฝ๋๋ง ํ์ธํ๊ณ ์ถ์ ๊ฒฝ์ฐ๋ head() ๊ฐ ํจ์จ์ ์ด์ง๋ง, ์๋ฒ์ ๋ฐ๋ผ์๋ head() ๋ฅผ ๊ฑฐ๋ถํ๋ ๊ฒฝ์ฐ๋ ์์ต๋๋ค.
Q. assert์ pytest.raises๋ ์ด๋ป๊ฒ ๊ตฌ๋ถํด์ ์ฌ์ฉํ๋์๏ผ
A. ์ ์๊ณ ์๋ต ๊ฒ์ฆ์๋ assert ๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์์ธ๏ผ์ค๋ฅ๏ผ๊ฐ ๋ฐ์ํ๋ ๊ฒ์ ํ์ธํ๊ณ ์ถ์ ๊ฒฝ์ฐ๋ pytest.raises ๋ฅผ ์ฌ์ฉํฉ๋๋ค. API ํ
์คํธ์์๋ ๊ธฐ๋ณธ์ ์ผ๋ก assert ๋ก ์ถฉ๋ถํฉ๋๋ค.
Q. GET ํ
์คํธ์ ํ
์คํธ ์ผ์ด์ค๋ ์ผ๋ง๋ ์์ฑํ๋ฉด ์ข๋์๏ผ
A. ์ต์ํ ใ์ ์๊ณ 1๊ฑดใป์ด์๊ณ 1๊ฑดใ๋ถํฐ ์์ํฉ์๋ค. ์ค๋ฌด์์๋ ใ์ํ ํ์ธใป๋ฐ๋ ๊ฒ์ฆใป์คํค๋ง ๊ฒ์ฆใป404 ํ์ธใป์๋ต ์๊ฐใ์ 5ใ7๊ฑด์ด ๊ธฐ์ค์
๋๋ค. ์ด๋ฒ TC01ใTC09๊ฐ ๊ทธ๋๋ก ์ค๋ฌด ํ์ค ํจํด์ด ๋ฉ๋๋ค.
Q. JSONPlaceholder ์ธ์ API ํ
์คํธ์ฉ ์ฌ์ดํธ๊ฐ ์๋์๏ผ
A. ์. ์ธ์ฆ ํ๋ก์ฐ ํ
์คํธ์๋ Restful Booker, ๋ ๋ณธ๊ฒฉ์ ์ธ API์๋ reqres.in ์ด๋ httpbin.org ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ํฌํธํด๋ฆฌ์ค์ฉ์ผ๋ก๋ JSONPlaceholder๏ผCRUD๏ผ์ Restful Booker๏ผ์ธ์ฆ๏ผ์ ์กฐํฉ์ด ์ถ์ฒ์
๋๋ค.
09. ์ ๋ฆฌ
Python์ผ๋ก API GET ํ ์คํธ๋ฅผ ๊ตฌํํ๋ ๊ฒฝ์ฐ, pytest์ requests๋ฅผ ์ฌ์ฉํด์ ์ํ ์ฝ๋ใป์๋ต ๋ฐ๋ใป์คํค๋ง์ 3๊ฐ์ง๋ฅผ ๊ฒ์ฆํ๋ ๊ฒ์ด ์ค๋ฌด์ ๊ธฐ๋ณธ์ ๋๋ค. ๊ฐ๋จํ ์ฝ๋๋ก ๋ง๋ผ์ ์ธ ํ ์คํธ๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
์ด ๊ธ์์๋ pytest์ requests๋ฅผ ์ฌ์ฉํ API GET ํ ์คํธ ๊ตฌํ ๋ฐฉ๋ฒ์ ํด์คํ์ต๋๋ค.
| ํ ์คํธ ์ผ์ด์ค | ๋ด์ฉ |
|---|---|
| TC01 | ์ํ ์ฝ๋๊ฐ 200์ด์ด์ผ ํ๋ค |
| TC02 | raise_for_status()๋ก 4xx/5xx๋ฅผ ๊ฒ์ง |
| TC03 | Content-Type์ด JSON์ด์ด์ผ ํ๋ค |
| TC04 | ์๋ต ๋ฐ๋์ ๊ฐ์ด ์ฌ๋ฐ๋ฅด๋ค |
| TC05 | ํ์ ํ๋๊ฐ ์กด์ฌํด์ผ ํ๋ค |
| TC06 | ์คํค๋ง๏ผํ์ ๏ผ๊ฐ ์ฌ๋ฐ๋ฅด๋ค |
| TC07 | ์กด์ฌํ์ง ์๋ ๋ฆฌ์์ค๋ 404๋ฅผ ๋ฐํํด์ผ ํ๋ค |
| TC08 | ๋ชฉ๋ก ์ทจ๋์์ ๊ฑด์๊ฐ ์ฌ๋ฐ๋ฅด๋ค |
| TC09 | ์๋ต ์๊ฐ์ด 2000ms ์ด๋ด์ฌ์ผ ํ๋ค |
๋ค์ ๊ธ์์๋ POST ์์ฒญ์ API ํ ์คํธ๏ผ๋ฐ์ดํฐ ์์ฑใป์ ํจ์ฑ ๊ฒ์ฌ ํ์ธ๏ผ๋ฅผ ํด์คํฉ๋๋ค.

