Python API GET ํ…Œ์ŠคํŠธ ์ž‘์„ฑ๋ฒ•๏ฝœpytestใƒปrequests๋กœ ์ƒํƒœ ์ฝ”๋“œใƒป์‘๋‹ต ๊ฒ€์ฆ

ํ…Œ์ŠคํŠธ ์ž๋™ํ™”

๐Ÿ“Œ ์ด ๊ธ€์€ ์ด๋Ÿฐ ๋ถ„๊ป˜ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค

  • Python๊ณผ requests๋กœ GET ์š”์ฒญ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์‹ถ์€ ๋ถ„
  • ์ƒํƒœ ์ฝ”๋“œ์™€ JSON ์‘๋‹ต์„ ๊ฒ€์ฆํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ฐฐ์šฐ๊ณ  ์‹ถ์€ ๋ถ„
  • pytest๋กœ API ํ…Œ์ŠคํŠธ๋ฅผ ์ž๋™ํ™”ํ•˜๊ณ  ์‹ถ์€ QA ์—”์ง€๋‹ˆ์–ด
  • ์‘๋‹ต์˜ ์Šคํ‚ค๋งˆ ๊ฒ€์ฆ๊นŒ์ง€ ๊ตฌํ˜„ํ•˜๊ณ  ์‹ถ์€ ๋ถ„

โœ… ์ด ๊ธ€์„ ์ฝ์œผ๋ฉด ์•Œ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ

  • pytest์™€ requests๋กœ GET ์š”์ฒญ API ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ธฐ๋ณธ ํŒจํ„ด
  • ์ƒํƒœ ์ฝ”๋“œใƒป์‘๋‹ต ๋ฐ”๋””ใƒปํ—ค๋” ๊ฒ€์ฆ ๋ฐฉ๋ฒ•
  • JSON ์Šคํ‚ค๋งˆ๏ผˆํƒ€์ž…ใƒปํ•„๋“œ๏ผ‰๋ฅผ ๊ฒ€์ฆํ•˜๋Š” ๊ตฌํ˜„ ๋ฐฉ๋ฒ•
  • ์ •์ƒ๊ณ„ใƒป์ด์ƒ๊ณ„ API ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ์„ค๊ณ„ ํŒจํ„ด

๐Ÿ‘จโ€๐Ÿ’ป ํ•„์ž ์†Œ๊ฐœ

QA ์—”์ง€๋‹ˆ์–ด๋กœ์„œ ์‹ค๋ฌด์—์„œ Pythonใƒปpytestใƒปrequests๋ฅผ ์‚ฌ์šฉํ•œ API ํ…Œ์ŠคํŠธ ์ž๋™ํ™”๋ฅผ ๋‹ด๋‹นํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ณธ ๊ธ€์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์ฝ”๋“œ๋Š” ๋ชจ๋‘ GitHub์— ๊ณต๊ฐœ๋˜์–ด ์žˆ์œผ๋ฉฐ, ์‹ค์ œ๋กœ ๋™์ž‘ ํ™•์ธ์ด ์™„๋ฃŒ๋œ ์ฝ”๋“œ๋ฅผ ๊ทธ๋Œ€๋กœ ํ•ด์„คํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. GitHub์—์„œ ์ฝ”๋“œ ๋ณด๊ธฐ โ†’

Python๊ณผ pytest๋กœ API ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ ์ฒ˜์Œ์œผ๋กœ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด GET ์š”์ฒญ ํ…Œ์ŠคํŠธ์ž…๋‹ˆ๋‹ค. pytest์™€ requests๋ฅผ ์‚ฌ์šฉํ•ด์„œ ใ€Œ์ƒํƒœ ์ฝ”๋“œ๋Š” 200์ธ๊ฐ€๏ผŸใ€ใ€ŒJSON ์‘๋‹ต์— ํ•„์š”ํ•œ ํ•„๋“œ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋Š”๊ฐ€๏ผŸใ€๋ฅผ ์ž๋™์œผ๋กœ ๊ฒ€์ฆํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ•ด์„คํ•ฉ๋‹ˆ๋‹ค.

์ด ๊ธ€์—์„œ๋Š” ๊ธฐ๋ณธ์ ์ธ ์ƒํƒœ ์ฝ”๋“œ ํ™•์ธ๋ถ€ํ„ฐ ์‘๋‹ต ๋ฐ”๋””์˜ ์Šคํ‚ค๋งˆ ๊ฒ€์ฆ๊นŒ์ง€๋ฅผ ์‹ค๋ฌด์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค.


  1. 00. API ํ…Œ์ŠคํŠธ์™€ GET ์š”์ฒญ์˜ ๊ธฐ๋ณธ
  2. 01. pytest + requests์˜ API ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ
  3. 02. Python์œผ๋กœ API GET ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•
    1. ์ƒํƒœ ์ฝ”๋“œ ๊ฒ€์ฆ
    2. raise_for_status()๋กœ 4xx/5xx๋ฅผ ํ™•์‹คํžˆ ๊ฒ€์ง€
    3. ํ—ค๋” ๊ฒ€์ฆ
    4. ์‘๋‹ต JSON ๊ฒ€์ฆ
    5. ํ•„์ˆ˜ ํ•„๋“œ ์กด์žฌ ํ™•์ธ
  4. 03. API ํ…Œ์ŠคํŠธ ์Šคํ‚ค๋งˆ๏ผˆํƒ€์ž…๏ผ‰๊ฒ€์ฆ
  5. 04. API ํ…Œ์ŠคํŠธ ์ด์ƒ๊ณ„ ํŒจํ„ด
    1. ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผ๏ผˆ404 ํ™•์ธ๏ผ‰
    2. ๋ชฉ๋ก ์ทจ๋“ ๋ฐ ๊ฑด์ˆ˜ ํ™•์ธ
  6. 05. API ํ…Œ์ŠคํŠธ ์‘๋‹ต ์‹œ๊ฐ„ ๊ฒ€์ฆ
  7. 06. GET ํ…Œ์ŠคํŠธ ์ „์ฒด ์ฝ”๋“œ
    1. ์‹คํ–‰ ์ปค๋งจ๋“œ
    2. ์‹คํ–‰ ๊ฒฐ๊ณผ ์ƒ˜ํ”Œ
  8. 07. ์ž์ฃผ ๊ฒช๋Š” ๋ฌธ์ œ & ํ•ด๊ฒฐ๋ฒ•
    1. โ‘  response.json()์—์„œ KeyError๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค
    2. โ‘ก ์ƒํƒœ ์ฝ”๋“œ๊ฐ€ 200์ธ๋ฐ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•œ๋‹ค
    3. โ‘ข isinstance()๋กœ ์ค‘์ฒฉ๋œ JSON ํƒ€์ž… ์ฒดํฌ์— ์‹คํŒจํ•œ๋‹ค
    4. โ‘ฃ ์‘๋‹ต ์‹œ๊ฐ„์ด ํ™˜๊ฒฝ์— ๋”ฐ๋ผ ๋“ค์‘ฅ๋‚ ์‘ฅํ•˜๋‹ค
    5. โ‘ค ๋ชฉ๋ก ์ทจ๋“์—์„œ ๋ฐ์ดํ„ฐ ๊ฑด์ˆ˜๊ฐ€ ๋ฐ”๋€Œ์–ด ํ…Œ์ŠคํŠธ๊ฐ€ ๊นจ์ง„๋‹ค
  9. 08. ์ž์ฃผ ๋ฌป๋Š” ์งˆ๋ฌธ๏ผˆFAQ๏ผ‰
  10. 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์ด์–ด์•ผ ํ•œ๋‹ค
TC02raise_for_status()๋กœ 4xx/5xx๋ฅผ ๊ฒ€์ง€
TC03Content-Type์ด JSON์ด์–ด์•ผ ํ•œ๋‹ค
TC04์‘๋‹ต ๋ฐ”๋””์˜ ๊ฐ’์ด ์˜ฌ๋ฐ”๋ฅด๋‹ค
TC05ํ•„์ˆ˜ ํ•„๋“œ๊ฐ€ ์กด์žฌํ•ด์•ผ ํ•œ๋‹ค
TC06์Šคํ‚ค๋งˆ๏ผˆํƒ€์ž…๏ผ‰๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๋‹ค
TC07์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฆฌ์†Œ์Šค๋Š” 404๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค
TC08๋ชฉ๋ก ์ทจ๋“์—์„œ ๊ฑด์ˆ˜๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๋‹ค
TC09์‘๋‹ต ์‹œ๊ฐ„์ด 2000ms ์ด๋‚ด์—ฌ์•ผ ํ•œ๋‹ค

๋‹ค์Œ ๊ธ€์—์„œ๋Š” POST ์š”์ฒญ์˜ API ํ…Œ์ŠคํŠธ๏ผˆ๋ฐ์ดํ„ฐ ์ƒ์„ฑใƒป์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ํ™•์ธ๏ผ‰๋ฅผ ํ•ด์„คํ•ฉ๋‹ˆ๋‹ค.

์ œ๋ชฉ๊ณผ URL์„ ๋ณต์‚ฌํ–ˆ์Šต๋‹ˆ๋‹ค