Python API PUTใƒปPATCH ํ…Œ์ŠคํŠธ ์ž‘์„ฑ๋ฒ•๏ฝœpytestร—requests๋กœ ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ ๊ฒ€์ฆ

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

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

  • Python๊ณผ requests๋กœ PUTใƒปPATCH ์š”์ฒญ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์‹ถ์€ ๋ถ„
  • API ํ…Œ์ŠคํŠธ์—์„œ ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ ๊ฒ€์ฆ ๋ฐฉ๋ฒ•์„ ๋ฐฐ์šฐ๊ณ  ์‹ถ์€ QA ์—”์ง€๋‹ˆ์–ด
  • PUT๊ณผ PATCH์˜ ์ฐจ์ด๋ฅผ ์ดํ•ดํ•˜๊ณ  ํ…Œ์ŠคํŠธ์— ํ™œ์šฉํ•˜๊ณ  ์‹ถ์€ ๋ถ„
  • pytest๋กœ CRUD ์กฐ์ž‘์˜ API ํ…Œ์ŠคํŠธ๋ฅผ ์™„์„ฑํ•˜๊ณ  ์‹ถ์€ ๋ถ„

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

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

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

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

Python๊ณผ pytest๋กœ API ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ PUT๊ณผ PATCH๋Š” ๋‘˜ ๋‹ค ใ€Œ๋ฐ์ดํ„ฐ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š”ใ€ ์š”์ฒญ์ž…๋‹ˆ๋‹ค. pytest์™€ requests๋ฅผ ์‚ฌ์šฉํ•ด์„œ ใ€Œ์ „์ฒด ์—…๋ฐ์ดํŠธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋™์ž‘ํ•˜๋Š”๊ฐ€๏ผŸใ€ใ€Œ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ์—์„œ ์ง€์ •ํ•œ ํ•„๋“œ๋งŒ ๋ฐ”๋€Œ๋Š”๊ฐ€๏ผŸใ€๋ฅผ ์ž๋™์œผ๋กœ ๊ฒ€์ฆํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ•ด์„คํ•ฉ๋‹ˆ๋‹ค.

์ด ๊ธ€์—์„œ๋Š” PUTใƒปPATCH์˜ ์ฐจ์ด๋ถ€ํ„ฐ ์‹ค๋ฌด์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊นŒ์ง€๋ฅผ ์ •๋ฆฌํ•ด์„œ ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค.


  1. 00. API ํ…Œ์ŠคํŠธ์—์„œ PUT๊ณผ PATCH์˜ ์ฐจ์ด
  2. 01. pytest + requests์˜ API ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ
  3. 02. Python์œผ๋กœ API PUT ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•
    1. ๊ธฐ๋ณธ์ ์ธ PUT ํ…Œ์ŠคํŠธ๏ผˆ200 ํ™•์ธ๏ผ‰
    2. raise_for_status()๋กœ 4xx/5xx๋ฅผ ํ™•์‹คํžˆ ๊ฒ€์ง€
    3. PUT ์‘๋‹ต ๋ฐ”๋”” ๊ฒ€์ฆ
    4. ํ—ค๋” ๊ฒ€์ฆ
  4. 03. Python์œผ๋กœ API PATCH ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•
    1. ๊ธฐ๋ณธ์ ์ธ PATCH ํ…Œ์ŠคํŠธ๏ผˆ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ ํ™•์ธ๏ผ‰
    2. PATCH ์‘๋‹ต ๋ฐ”๋”” ๊ฒ€์ฆ
    3. PATCH๋กœ ๋ณต์ˆ˜ ํ•„๋“œ๋ฅผ ์—…๋ฐ์ดํŠธ
  5. 04. API ํ…Œ์ŠคํŠธ PUTใƒปPATCH ์Šคํ‚ค๋งˆ ๊ฒ€์ฆ
  6. 05. API ํ…Œ์ŠคํŠธ PUTใƒปPATCH ์ด์ƒ๊ณ„ ํŒจํ„ด
    1. ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฆฌ์†Œ์Šค์— PUT๏ผˆ404 ํ™•์ธ๏ผ‰
  7. 06. PUTใƒปPATCH ํ…Œ์ŠคํŠธ ์ „์ฒด ์ฝ”๋“œ
    1. ์‹คํ–‰ ์ปค๋งจ๋“œ
    2. ์‹คํ–‰ ๊ฒฐ๊ณผ ์ƒ˜ํ”Œ
  8. 07. ์ž์ฃผ ๊ฒช๋Š” ๋ฌธ์ œ & ํ•ด๊ฒฐ๋ฒ•
    1. โ‘  PUT์—์„œ ์ƒ๋žตํ•œ ํ•„๋“œ๊ฐ€ null์ด ๋œ๋‹ค
    2. โ‘ก PATCH์—์„œ ์—…๋ฐ์ดํŠธ๋˜์ง€ ์•Š์€ ํ•„๋“œ ์ฒดํฌ๋ฅผ ์žŠ์–ด๋ฒ„๋ฆฐ๋‹ค
    3. โ‘ข PUT๊ณผ PATCH ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ˜ผ๋™ํ•œ๋‹ค
    4. โ‘ฃ ์—…๋ฐ์ดํŠธ ํ›„ ID๊ฐ€ ๋ณ€ํ•˜์ง€ ์•Š์•˜๋Š”์ง€ ํ™•์ธ์„ ์žŠ๋Š”๋‹ค
    5. โ‘ค ๋ชฉ API์—์„œ ๋ฉฑ๋“ฑ์„ฑ์„ ํ™•์ธํ•  ์ˆ˜ ์—†๋‹ค
  9. 08. ์ž์ฃผ ๋ฌป๋Š” ์งˆ๋ฌธ๏ผˆFAQ๏ผ‰
  10. 09. ์ •๋ฆฌ

00. API ํ…Œ์ŠคํŠธ์—์„œ PUT๊ณผ PATCH์˜ ์ฐจ์ด

PUT๊ณผ PATCH๋Š” ๋‘˜ ๋‹ค ใ€Œ์—…๋ฐ์ดํŠธใ€์ด์ง€๋งŒ, ์—…๋ฐ์ดํŠธ ๋ฒ”์œ„๊ฐ€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค.

PUTPATCH
์—…๋ฐ์ดํŠธ ๋ฒ”์œ„๋ฆฌ์†Œ์Šค ์ „์ฒด๋ฅผ ๊ต์ฒดํ•œ๋‹ค์ง€์ •ํ•œ ํ•„๋“œ๋งŒ ์—…๋ฐ์ดํŠธํ•œ๋‹ค
์ƒ๋žตํ•œ ํ•„๋“œnull์ด๋‚˜ ๊ธฐ๋ณธ๊ฐ’์ด ๋œ๋‹ค๋ณ€๊ฒฝ๋˜์ง€ ์•Š๊ณ  ์›๋ž˜ ๊ฐ’์ด ์œ ์ง€๋œ๋‹ค
๋ฉฑ๋“ฑ์„ฑ์žˆ์Œ๏ผˆ๋ช‡ ๋ฒˆ ๋ณด๋‚ด๋„ ๊ฐ™์€ ๊ฒฐ๊ณผ๏ผ‰๊ตฌํ˜„์— ๋”ฐ๋ผ ๋‹ค๋ฆ„
์šฉ๋„ํ”„๋กœํ•„ ์ „์ฒด ์—…๋ฐ์ดํŠธ ๋“ฑ์ด๋ฉ”์ผ ์ฃผ์†Œ๋งŒ ๋ณ€๊ฒฝ ๋“ฑ

๐Ÿ’ก ํฌ์ธํŠธ๏ผš์‹ค๋ฌด์—์„œ๋Š” PATCH๊ฐ€ ๋” ๋งŽ์ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ํ•˜๋‚˜์˜ ํ•„๋“œ๋งŒ ๋ณ€๊ฒฝํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์ „์ฒด ํ•„๋“œ๋ฅผ ์ „์†กํ•  ํ•„์š”๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.


01. pytest + requests์˜ API ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ

์•„์ง ํ™˜๊ฒฝ ๊ตฌ์ถ•์ด ์•ˆ ๋œ ๋ถ„์€ ๋จผ์ € ์„ค์น˜ํ•ด์ฃผ์„ธ์š”.

pip install requests pytest pytest-html

์ด๋ฒˆ ํ…Œ์ŠคํŠธ ๋Œ€์ƒ์€ ๋ฌด๋ฃŒ ๋ชฉ๏ผˆmock๏ผ‰ API JSONPlaceholder ์ž…๋‹ˆ๋‹ค.

# PUT ์š”์ฒญ๏ผˆ์ „์ฒด ์—…๋ฐ์ดํŠธ๏ผ‰
PUT https://jsonplaceholder.typicode.com/users/1

# PATCH ์š”์ฒญ๏ผˆ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ๏ผ‰
PATCH https://jsonplaceholder.typicode.com/users/1

๐Ÿ’ก ํฌ์ธํŠธ๏ผšJSONPlaceholder๋Š” ๋ชฉ API์ด๋ฏ€๋กœ ์‹ค์ œ๋กœ๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์‘๋‹ต์€ ์‹ค์ œ API์™€ ๋™์ผํ•œ ํ˜•์‹์œผ๋กœ ๋ฐ˜ํ™˜๋˜๋ฏ€๋กœ ํ…Œ์ŠคํŠธ ํ•™์Šต์— ์ตœ์ ์ž…๋‹ˆ๋‹ค.


02. Python์œผ๋กœ API PUT ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•

๊ธฐ๋ณธ์ ์ธ PUT ํ…Œ์ŠคํŠธ๏ผˆ200 ํ™•์ธ๏ผ‰

PUT ์š”์ฒญ์œผ๋กœ ์ „์ฒด ์—…๋ฐ์ดํŠธ๋ฅผ ํ•˜๊ณ  200์ด ๋ฐ˜ํ™˜๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

import requests

BASE_URL = "https://jsonplaceholder.typicode.com"

def test_put_user_status_code():
    """TC01: PUT์œผ๋กœ ์ „์ฒด ์—…๋ฐ์ดํŠธ ์‹œ ์ƒํƒœ ์ฝ”๋“œ 200์ด ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค"""
    updated_user = {
        "id": 1,
        "name": "Updated Yoshitsugu",
        "username": "yoshitsugu728",
        "email": "updated@example.com",
        "phone": "090-9999-9999",
        "website": "qa-auto-lab.com"
    }
    response = requests.put(f"{BASE_URL}/users/1", json=updated_user)

    assert response.status_code == 200, \
        f"๊ธฐ๋Œ“๊ฐ’: 200, ์‹ค์ œ ๊ฐ’: {response.status_code}"

    print(f"\nโœ… TC01 PASS | ์ƒํƒœ ์ฝ”๋“œ: {response.status_code}")

raise_for_status()๋กœ 4xx/5xx๋ฅผ ํ™•์‹คํžˆ ๊ฒ€์ง€

def test_put_user_raise_for_status():
    """TC02: 4xx/5xx ์˜ค๋ฅ˜ ์‹œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•ด์•ผ ํ•œ๋‹ค"""
    updated_user = {"id": 1, "name": "Updated User", "email": "updated@example.com"}
    response = requests.put(f"{BASE_URL}/users/1", json=updated_user)

    response.raise_for_status()

    assert response.status_code == 200
    print(f"\nโœ… TC02 PASS | raise_for_status() ์ •์ƒ ํ†ต๊ณผ")

๐Ÿ’ก ํฌ์ธํŠธ๏ผšresponse.raise_for_status() ๋Š” ์‹ค๋ฌด์—์„œ ํ•„์ˆ˜ 1ํ–‰์ž…๋‹ˆ๋‹ค. PUTใƒปPATCH ํ…Œ์ŠคํŠธ์—์„œ๋„ ๋ฐ˜๋“œ์‹œ ์ถ”๊ฐ€ํ•ด๋‘์„ธ์š”.

PUT ์‘๋‹ต ๋ฐ”๋”” ๊ฒ€์ฆ

์—…๋ฐ์ดํŠธํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์‘๋‹ต์— ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋ฐ˜์˜๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

def test_put_user_response_body():
    """TC03: PUT์œผ๋กœ ์—…๋ฐ์ดํŠธํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์‘๋‹ต์— ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋ฐ˜์˜๋˜์–ด์•ผ ํ•œ๋‹ค"""
    updated_user = {
        "id": 1,
        "name": "Updated Yoshitsugu",
        "email": "updated@example.com"
    }
    response = requests.put(f"{BASE_URL}/users/1", json=updated_user)

    response.raise_for_status()
    body = response.json()

    assert body["name"] == updated_user["name"],   f"name ๋ถˆ์ผ์น˜: {body['name']}"
    assert body["email"] == updated_user["email"], f"email ๋ถˆ์ผ์น˜: {body['email']}"

    print(f"\nโœ… TC03 PASS | ์—…๋ฐ์ดํŠธ ํ›„ ์ด๋ฆ„: {body['name']}")

ํ—ค๋” ๊ฒ€์ฆ

def test_put_user_header():
    """TC04: Content-Type์ด JSON์ด์–ด์•ผ ํ•œ๋‹ค"""
    updated_user = {"id": 1, "name": "Test", "email": "test@example.com"}
    response = requests.put(f"{BASE_URL}/users/1", json=updated_user)

    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โœ… TC04 PASS | Content-Type: {content_type}")

๐Ÿ’ก ํฌ์ธํŠธ๏ผšContent-Type์€ application/json; charset=utf-8 ํ˜•ํƒœ๋กœ ๋ฐ˜ํ™˜๋˜๋ฏ€๋กœ in ์œผ๋กœ ๋ถ€๋ถ„ ์ผ์น˜ ๊ฒ€์ฆ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.


03. Python์œผ๋กœ API PATCH ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•

๊ธฐ๋ณธ์ ์ธ PATCH ํ…Œ์ŠคํŠธ๏ผˆ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ ํ™•์ธ๏ผ‰

PATCH ์š”์ฒญ์œผ๋กœ ์ผ๋ถ€ ํ•„๋“œ๋งŒ ์—…๋ฐ์ดํŠธํ•˜๊ณ  200์ด ๋ฐ˜ํ™˜๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

def test_patch_user_status_code():
    """TC05: PATCH๋กœ ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ ์‹œ ์ƒํƒœ ์ฝ”๋“œ 200์ด ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค"""
    patch_data = {
        "email": "patched@example.com"  # email๋งŒ ์—…๋ฐ์ดํŠธ
    }
    response = requests.patch(f"{BASE_URL}/users/1", json=patch_data)

    assert response.status_code == 200, \
        f"๊ธฐ๋Œ“๊ฐ’: 200, ์‹ค์ œ ๊ฐ’: {response.status_code}"

    print(f"\nโœ… TC05 PASS | ์ƒํƒœ ์ฝ”๋“œ: {response.status_code}")

PATCH ์‘๋‹ต ๋ฐ”๋”” ๊ฒ€์ฆ

์ง€์ •ํ•œ ํ•„๋“œ๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

def test_patch_user_response_body():
    """TC06: PATCH๋กœ ์ง€์ •ํ•œ ํ•„๋“œ๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜์–ด์•ผ ํ•œ๋‹ค"""
    patch_data = {"email": "patched@example.com"}
    response = requests.patch(f"{BASE_URL}/users/1", json=patch_data)

    response.raise_for_status()
    body = response.json()

    assert body["email"] == patch_data["email"], \
        f"email์ด ์—…๋ฐ์ดํŠธ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค: {body['email']}"

    print(f"\nโœ… TC06 PASS | ์—…๋ฐ์ดํŠธ ํ›„ email: {body['email']}")

๐Ÿ’ก ํฌ์ธํŠธ๏ผšPATCH๋Š” ์ง€์ •ํ•œ ํ•„๋“œ๋งŒ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์ด ํŠน์ง•์ž…๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ํ•„๋“œ๊ฐ€ ์˜๋„์น˜ ์•Š๊ฒŒ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜๋Š”์ง€๋„ ํ•จ๊ป˜ ํ™•์ธํ•˜๋ฉด ๋”์šฑ ๊ฒฌ๊ณ ํ•œ ํ…Œ์ŠคํŠธ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

PATCH๋กœ ๋ณต์ˆ˜ ํ•„๋“œ๋ฅผ ์—…๋ฐ์ดํŠธ

def test_patch_user_multiple_fields():
    """TC07: PATCH๋กœ ๋ณต์ˆ˜ ํ•„๋“œ๋ฅผ ๋™์‹œ์— ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค"""
    patch_data = {
        "name": "Patched Name",
        "email": "patched@example.com"
    }
    response = requests.patch(f"{BASE_URL}/users/1", json=patch_data)

    response.raise_for_status()
    body = response.json()

    assert body["name"] == patch_data["name"],   f"name ๋ถˆ์ผ์น˜: {body['name']}"
    assert body["email"] == patch_data["email"], f"email ๋ถˆ์ผ์น˜: {body['email']}"

    print(f"\nโœ… TC07 PASS | ์—…๋ฐ์ดํŠธ ์™„๋ฃŒ: {body['name']} / {body['email']}")

04. API ํ…Œ์ŠคํŠธ PUTใƒปPATCH ์Šคํ‚ค๋งˆ ๊ฒ€์ฆ

์—…๋ฐ์ดํŠธ๋œ ๋ฆฌ์†Œ์Šค์˜ ์‘๋‹ต์ด ์˜ฌ๋ฐ”๋ฅธ ํƒ€์ž…์ธ์ง€๋„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

def test_put_user_schema():
    """TC08: PUT ์‘๋‹ต ์Šคํ‚ค๋งˆ๏ผˆํƒ€์ž…๏ผ‰๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๋‹ค"""
    updated_user = {"id": 1, "name": "Updated User", "email": "updated@example.com"}
    response = requests.put(f"{BASE_URL}/users/1", json=updated_user)

    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["email"], str), f"email์€ strํ˜•์ด ๊ธฐ๋Œ“๊ฐ’: {type(body['email'])}"

    print(f"\nโœ… TC08 PASS | ์Šคํ‚ค๋งˆ ๊ฒ€์ฆ ์™„๋ฃŒ")

05. API ํ…Œ์ŠคํŠธ PUTใƒปPATCH ์ด์ƒ๊ณ„ ํŒจํ„ด

์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฆฌ์†Œ์Šค์— PUT๏ผˆ404 ํ™•์ธ๏ผ‰

def test_put_nonexistent_user():
    """TC09: ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฆฌ์†Œ์Šค์— PUTํ–ˆ์„ ๋•Œ ์ ์ ˆํ•œ ์ƒํƒœ๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค"""
    updated_user = {"id": 99999, "name": "Ghost User", "email": "ghost@example.com"}
    response = requests.put(f"{BASE_URL}/users/99999", json=updated_user)

    # JSONPlaceholder๋Š” ๋ชฉ์ด๋ฏ€๋กœ 200์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ๋„ ์žˆ๋‹ค
    # ์‹ค์ œ API์—์„œ๋Š” 404๊ฐ€ ๊ธฐ๋Œ€๋จ
    assert response.status_code in [200, 404], \
        f"์˜ˆ์ƒ ๋ฐ–์˜ ์ƒํƒœ ์ฝ”๋“œ: {response.status_code}"

    print(f"\nโœ… TC09 PASS | ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฆฌ์†Œ์Šค์— PUT: {response.status_code}")

โš ๏ธ ์ฃผ์˜๏ผšJSONPlaceholder๋Š” ๋ชฉ API์ด๋ฏ€๋กœ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ID์—๋„ 200์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์‹ค์ œ API์—์„œ๋Š” 404๊ฐ€ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฒƒ์„ ๊ฒ€์ฆํ•ด์ฃผ์„ธ์š”.


06. PUTใƒปPATCH ํ…Œ์ŠคํŠธ ์ „์ฒด ์ฝ”๋“œ

"""
PUT / PATCH API Test
Target: JSONPlaceholder (https://jsonplaceholder.typicode.com)
Framework: Python + requests + pytest
"""
import requests

BASE_URL = "https://jsonplaceholder.typicode.com"


def test_put_user_status_code():
    """TC01: PUT์œผ๋กœ ์ „์ฒด ์—…๋ฐ์ดํŠธ ์‹œ ์ƒํƒœ ์ฝ”๋“œ 200์ด ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค"""
    updated_user = {"id": 1, "name": "Updated Yoshitsugu", "email": "updated@example.com"}
    response = requests.put(f"{BASE_URL}/users/1", json=updated_user)
    assert response.status_code == 200, \
        f"๊ธฐ๋Œ“๊ฐ’: 200, ์‹ค์ œ ๊ฐ’: {response.status_code}"
    print(f"\nโœ… TC01 PASS | status: {response.status_code}")


def test_put_user_raise_for_status():
    """TC02: 4xx/5xx ์˜ค๋ฅ˜ ์‹œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•ด์•ผ ํ•œ๋‹ค"""
    updated_user = {"id": 1, "name": "Updated User", "email": "updated@example.com"}
    response = requests.put(f"{BASE_URL}/users/1", json=updated_user)
    response.raise_for_status()
    assert response.status_code == 200
    print(f"\nโœ… TC02 PASS | raise_for_status() ์ •์ƒ ํ†ต๊ณผ")


def test_put_user_response_body():
    """TC03: PUT์œผ๋กœ ์—…๋ฐ์ดํŠธํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์‘๋‹ต์— ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋ฐ˜์˜๋˜์–ด์•ผ ํ•œ๋‹ค"""
    updated_user = {"id": 1, "name": "Updated Yoshitsugu", "email": "updated@example.com"}
    response = requests.put(f"{BASE_URL}/users/1", json=updated_user)
    response.raise_for_status()
    body = response.json()
    assert body["name"] == updated_user["name"]
    assert body["email"] == updated_user["email"]
    print(f"\nโœ… TC03 PASS | name: {body['name']}")


def test_put_user_header():
    """TC04: Content-Type์ด JSON์ด์–ด์•ผ ํ•œ๋‹ค"""
    updated_user = {"id": 1, "name": "Test", "email": "test@example.com"}
    response = requests.put(f"{BASE_URL}/users/1", json=updated_user)
    response.raise_for_status()
    content_type = response.headers.get("Content-Type", "")
    assert "application/json" in content_type
    print(f"\nโœ… TC04 PASS | Content-Type: {content_type}")


def test_patch_user_status_code():
    """TC05: PATCH๋กœ ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ ์‹œ ์ƒํƒœ ์ฝ”๋“œ 200์ด ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค"""
    patch_data = {"email": "patched@example.com"}
    response = requests.patch(f"{BASE_URL}/users/1", json=patch_data)
    assert response.status_code == 200, \
        f"๊ธฐ๋Œ“๊ฐ’: 200, ์‹ค์ œ ๊ฐ’: {response.status_code}"
    print(f"\nโœ… TC05 PASS | status: {response.status_code}")


def test_patch_user_response_body():
    """TC06: PATCH๋กœ ์ง€์ •ํ•œ ํ•„๋“œ๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜์–ด์•ผ ํ•œ๋‹ค"""
    patch_data = {"email": "patched@example.com"}
    response = requests.patch(f"{BASE_URL}/users/1", json=patch_data)
    response.raise_for_status()
    body = response.json()
    assert body["email"] == patch_data["email"]
    print(f"\nโœ… TC06 PASS | email: {body['email']}")


def test_patch_user_multiple_fields():
    """TC07: PATCH๋กœ ๋ณต์ˆ˜ ํ•„๋“œ๋ฅผ ๋™์‹œ์— ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค"""
    patch_data = {"name": "Patched Name", "email": "patched@example.com"}
    response = requests.patch(f"{BASE_URL}/users/1", json=patch_data)
    response.raise_for_status()
    body = response.json()
    assert body["name"] == patch_data["name"]
    assert body["email"] == patch_data["email"]
    print(f"\nโœ… TC07 PASS | {body['name']} / {body['email']}")


def test_put_user_schema():
    """TC08: PUT ์‘๋‹ต ์Šคํ‚ค๋งˆ๏ผˆํƒ€์ž…๏ผ‰๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๋‹ค"""
    updated_user = {"id": 1, "name": "Updated User", "email": "updated@example.com"}
    response = requests.put(f"{BASE_URL}/users/1", json=updated_user)
    response.raise_for_status()
    body = response.json()
    assert isinstance(body["id"],    int)
    assert isinstance(body["name"],  str)
    assert isinstance(body["email"], str)
    print(f"\nโœ… TC08 PASS | ์Šคํ‚ค๋งˆ ๊ฒ€์ฆ ์™„๋ฃŒ")


def test_put_nonexistent_user():
    """TC09: ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฆฌ์†Œ์Šค์— PUTํ–ˆ์„ ๋•Œ ์ ์ ˆํ•œ ์ƒํƒœ๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค"""
    updated_user = {"id": 99999, "name": "Ghost User", "email": "ghost@example.com"}
    response = requests.put(f"{BASE_URL}/users/99999", json=updated_user)
    assert response.status_code in [200, 404]
    print(f"\nโœ… TC09 PASS | status: {response.status_code}")

์‹คํ–‰ ์ปค๋งจ๋“œ

pytest test_put_patch_api.py -v -s

์‹คํ–‰ ๊ฒฐ๊ณผ ์ƒ˜ํ”Œ

test_put_patch_api.py::test_put_user_status_code       PASSED
test_put_patch_api.py::test_put_user_raise_for_status  PASSED
test_put_patch_api.py::test_put_user_response_body     PASSED
test_put_patch_api.py::test_put_user_header            PASSED
test_put_patch_api.py::test_patch_user_status_code     PASSED
test_put_patch_api.py::test_patch_user_response_body   PASSED
test_put_patch_api.py::test_patch_user_multiple_fields PASSED
test_put_patch_api.py::test_put_user_schema            PASSED
test_put_patch_api.py::test_put_nonexistent_user       PASSED

9 passed in 4.23s โœ…

07. ์ž์ฃผ ๊ฒช๋Š” ๋ฌธ์ œ & ํ•ด๊ฒฐ๋ฒ•

๊ตฌํ˜„ ์ค‘ ์‹ค์ œ๋กœ ๊ฒช์—ˆ๋˜ ๋ฌธ์ œ๋“ค์„ ์ •๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ฐ™์€ ๊ณณ์—์„œ ๋ง‰ํžˆ๋Š” ๋ถ„๋“ค๊ป˜ ๋„์›€์ด ๋˜๋ฉด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค.


โ‘  PUT์—์„œ ์ƒ๋žตํ•œ ํ•„๋“œ๊ฐ€ null์ด ๋œ๋‹ค

PUT์—์„œ ์ผ๋ถ€ ํ•„๋“œ๋งŒ ์ „์†กํ–ˆ๋”๋‹ˆ ์ „์†กํ•˜์ง€ ์•Š์€ ํ•„๋“œ๊ฐ€ null์ด๋‚˜ ๋นˆ ๊ฐ’์ด ๋˜์–ด๋ฒ„๋ ธ์Šต๋‹ˆ๋‹ค. PUT์€ ๋ฆฌ์†Œ์Šค ์ „์ฒด๋ฅผ ๊ต์ฒดํ•˜๋ฏ€๋กœ ์ „์ฒด ํ•„๋“œ๋ฅผ ๋ณด๋‚ด์ง€ ์•Š์œผ๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ์‚ฌ๋ผ์ง‘๋‹ˆ๋‹ค.

# โŒ PUT์—์„œ ์ผ๋ถ€๋งŒ ๋ณด๋‚ด๋ฉด ๋‹ค๋ฅธ ํ•„๋“œ๊ฐ€ ์‚ฌ๋ผ์งˆ ๊ฐ€๋Šฅ์„ฑ
response = requests.put(url, json={"name": "Updated"})
# โ†’ phone, website ๋“ฑ์ด null์ด ๋˜์–ด๋ฒ„๋ฆฐ๋‹ค๏ผ

# โœ… PUT์€ ์ „์ฒด ํ•„๋“œ๋ฅผ ํฌํ•จํ•ด์„œ ๋ณด๋‚ธ๋‹ค
updated_user = {
    "id": 1,
    "name": "Updated",
    "email": "original@example.com",  # ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๋Š” ํ•„๋“œ๋„ ํฌํ•จ
    "phone": "090-1234-5678",
    "website": "qa-auto-lab.com"
}
response = requests.put(url, json=updated_user)

๐Ÿ’ก ํฌ์ธํŠธ๏ผš์ผ๋ถ€ ํ•„๋“œ๋งŒ ๋ณ€๊ฒฝํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ๋Š” PUT์ด ์•„๋‹ˆ๋ผ PATCH๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ •๋‹ต์ž…๋‹ˆ๋‹ค.


โ‘ก PATCH์—์„œ ์—…๋ฐ์ดํŠธ๋˜์ง€ ์•Š์€ ํ•„๋“œ ์ฒดํฌ๋ฅผ ์žŠ์–ด๋ฒ„๋ฆฐ๋‹ค

PATCH์—์„œ 1๊ฐœ ํ•„๋“œ๋งŒ ์—…๋ฐ์ดํŠธํ–ˆ์„ ๋•Œ ๋‹ค๋ฅธ ํ•„๋“œ๊ฐ€ ์˜๋„์น˜ ์•Š๊ฒŒ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ฒƒ์„ ์žŠ์—ˆ์Šต๋‹ˆ๋‹ค.

# โŒ ์—…๋ฐ์ดํŠธํ•œ ํ•„๋“œ๋งŒ ํ™•์ธํ•˜๋ฉด ์ถฉ๋ถ„ํ•˜์ง€ ์•Š๋‹ค
patch_data = {"email": "new@example.com"}
response = requests.patch(url, json=patch_data)
body = response.json()
assert body["email"] == "new@example.com"  # ์ด๊ฒƒ๋งŒ์œผ๋กœ๋Š” ๋ถ€์กฑ

# โœ… ์—…๋ฐ์ดํŠธํ•˜์ง€ ์•Š์€ ํ•„๋“œ๋„ ๋ณ€ํ•˜์ง€ ์•Š์•˜์Œ์„ ํ™•์ธ
assert body["email"] == "new@example.com"  # ์—…๋ฐ์ดํŠธ๋˜์—ˆ์„ ๊ฒƒ
assert body["name"] == "Leanne Graham"     # ๋ณ€ํ•˜์ง€ ์•Š์•˜์„ ๊ฒƒ

๐Ÿ’ก ํฌ์ธํŠธ๏ผšPATCH ํ…Œ์ŠคํŠธ์—์„œ๋Š” ใ€Œ๋ณ€๊ฒฝํ•œ ํ•„๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ๋‹คใ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ใ€Œ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์€ ํ•„๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜๋‹คใ€๋„ ํ™•์ธํ•˜๋ฉด ์™„๋ฒฝํ•ฉ๋‹ˆ๋‹ค.


โ‘ข PUT๊ณผ PATCH ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ˜ผ๋™ํ•œ๋‹ค

PUT๊ณผ PATCH๊ฐ€ ๊ฐ™์€ ์—”๋“œํฌ์ธํŠธ URL์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ ํ˜ผ๋™ํ•ด๋ฒ„๋ ธ์Šต๋‹ˆ๋‹ค.

# โŒ ๋ฉ”์„œ๋“œ๋ฅผ ์ฐฉ๊ฐํ•ด๋ฒ„๋ฆฐ๋‹ค
response = requests.put(url, json=patch_data)    # PATCH๋ฅผ ์“ธ ์ƒ๊ฐ์ด์—ˆ๋Š”๋ฐ PUT์„ ์‚ฌ์šฉ

# โœ… ๋ฉ”์„œ๋“œ๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ๊ตฌ๋ถ„ํ•ด์„œ ์‚ฌ์šฉํ•œ๋‹ค
# ์ „์ฒด ์—…๋ฐ์ดํŠธ โ†’ PUT
response = requests.put(f"{BASE_URL}/users/1", json=full_data)

# ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ โ†’ PATCH
response = requests.patch(f"{BASE_URL}/users/1", json=partial_data)

โš ๏ธ ์ฃผ์˜๏ผšURL์€ ๊ฐ™์•„๋„ HTTP ๋ฉ”์„œ๋“œ๊ฐ€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. ํ•จ์ˆ˜๋ช…์— put/patch๋ฅผ ๋ช…๊ธฐํ•˜์—ฌ ํ˜ผ๋™์„ ๋ฐฉ์ง€ํ•˜์„ธ์š”.


โ‘ฃ ์—…๋ฐ์ดํŠธ ํ›„ ID๊ฐ€ ๋ณ€ํ•˜์ง€ ์•Š์•˜๋Š”์ง€ ํ™•์ธ์„ ์žŠ๋Š”๋‹ค

PUT์œผ๋กœ ์—…๋ฐ์ดํŠธํ•œ ํ›„ ID๊ฐ€ ์˜๋„์น˜ ์•Š๊ฒŒ ๋ณ€ํ•˜์ง€ ์•Š์•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ฒƒ์„ ์žŠ์—ˆ์Šต๋‹ˆ๋‹ค.

# โœ… ์—…๋ฐ์ดํŠธ ํ›„์—๋„ ID๊ฐ€ ๊ฐ™์€์ง€ ํ™•์ธ
updated_user = {"id": 1, "name": "Updated", "email": "updated@example.com"}
response = requests.put(f"{BASE_URL}/users/1", json=updated_user)
body = response.json()

assert body["id"] == 1,                         "ID๊ฐ€ ๋ณ€ํ•ด์„œ๋Š” ์•ˆ ๋œ๋‹ค"
assert body["name"] == "Updated",               "name์ด ์—…๋ฐ์ดํŠธ๋˜์—ˆ์„ ๊ฒƒ"
assert body["email"] == "updated@example.com",  "email์ด ์—…๋ฐ์ดํŠธ๋˜์—ˆ์„ ๊ฒƒ"

๐Ÿ’ก ํฌ์ธํŠธ๏ผš์—…๋ฐ์ดํŠธ ์กฐ์ž‘์—์„œ ID๊ฐ€ ๋ณ€ํ•˜์ง€ ์•Š์•˜์Œ์„ ํ™•์ธํ•˜๋Š” ๊ฒƒ์€ ์‹ค๋ฌด์—์„œ ์ž์ฃผ ๊ฐ„๊ณผ๋˜๋Š” ํ…Œ์ŠคํŠธ์ž…๋‹ˆ๋‹ค. ๋ฐ˜๋“œ์‹œ ์ถ”๊ฐ€ํ•˜์„ธ์š”.


โ‘ค ๋ชฉ API์—์„œ ๋ฉฑ๋“ฑ์„ฑ์„ ํ™•์ธํ•  ์ˆ˜ ์—†๋‹ค

PUT์˜ ํŠน์ง•์ธ ใ€Œ๋ช‡ ๋ฒˆ ๋ณด๋‚ด๋„ ๊ฐ™์€ ๊ฒฐ๊ณผ๊ฐ€ ๋œ๋‹ค๏ผˆ๋ฉฑ๋“ฑ์„ฑ๏ผ‰ใ€๋ฅผ JSONPlaceholder๋กœ ํ™•์ธํ•˜๋ ค๊ณ  ํ–ˆ์ง€๋งŒ, ๋ชฉ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋งค๋ฒˆ ๊ฐ™์€ ์‘๋‹ต์ด ๋ฐ˜ํ™˜๋˜์–ด ํ™•์ธํ•  ์ˆ˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค.

# ๋ฉฑ๋“ฑ์„ฑ ํ™•์ธ๏ผˆ์‹ค์ œ API๋กœ ์‹œํ—˜ํ•˜๋Š” ๊ฒฝ์šฐ๏ผ‰
updated_user = {"id": 1, "name": "Idempotent User", "email": "idem@example.com"}

response1 = requests.put(url, json=updated_user)  # 1ํšŒ์งธ
response2 = requests.put(url, json=updated_user)  # 2ํšŒ์งธ๏ผˆ๊ฐ™์€ ์š”์ฒญ๏ผ‰

# ๊ฐ™์€ ๊ฒฐ๊ณผ๊ฐ€ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฒƒ์„ ํ™•์ธ
assert response1.json() == response2.json(), "PUT์€ ๋ฉฑ๋“ฑ์ด์–ด์•ผ ํ•œ๋‹ค"

โš ๏ธ ์ฃผ์˜๏ผš๋ฉฑ๋“ฑ์„ฑ ํ™•์ธ์€ ์‹ค์ œ๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€ํ•˜๋Š” API๋กœ ์‹œํ—˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. JSONPlaceholder๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์œ ์ง€๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ ํ™•์ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.


08. ์ž์ฃผ ๋ฌป๋Š” ์งˆ๋ฌธ๏ผˆFAQ๏ผ‰

Q. PUT๊ณผ PATCH ์–ด๋А ๊ฒƒ์„ ์‚ฌ์šฉํ•˜๋ฉด ์ข‹๋‚˜์š”๏ผŸ
A. ์ผ๋ถ€ ํ•„๋“œ๋งŒ ๋ณ€๊ฒฝํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ๋Š” PATCH, ๋ฆฌ์†Œ์Šค ์ „์ฒด๋ฅผ ๊ต์ฒดํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ๋Š” PUT์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์‹ค๋ฌด์—์„œ๋Š” PATCH๊ฐ€ ๋” ๋งŽ์ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ํ•˜๋‚˜์˜ ํ•„๋“œ๋งŒ ๋ณ€๊ฒฝํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์ „์ฒด ํ•„๋“œ๋ฅผ ์ „์†กํ•  ํ•„์š”๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

Q. PUT ์„ฑ๊ณต ์‹œ ์ƒํƒœ ์ฝ”๋“œ๋Š” 200๊ณผ 204 ์–ด๋А ๊ฒƒ์ด ์˜ฌ๋ฐ”๋ฅธ๊ฐ€์š”๏ผŸ
A. API ์„ค๊ณ„์— ๋”ฐ๋ผ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. ์—…๋ฐ์ดํŠธ ํ›„ ๋ฆฌ์†Œ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ๋Š” 200, ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๋Š” 204๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” API๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์ „์— API ๋ฌธ์„œ๋กœ ํ™•์ธํ•ด์ฃผ์„ธ์š”. JSONPlaceholder๋Š” 200์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

Q. PATCH ํ›„ ๋‹ค๋ฅธ ํ•„๋“œ๊ฐ€ ๋ณ€ํ•˜์ง€ ์•Š์•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋ฐฉ๋ฒ•์€๏ผŸ
A. PATCH ์ „์— GET์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ทจ๋“ํ•ด๋‘๊ณ  PATCH ํ›„์— ๋‹ค์‹œ GETํ•ด์„œ ๋น„๊ตํ•˜๋Š” ๊ฒƒ์ด ํ™•์‹คํ•ฉ๋‹ˆ๋‹ค. ์ง€์ •ํ•œ ํ•„๋“œ๋งŒ ๋ณ€๊ฒฝ๋˜๊ณ  ๋‹ค๋ฅธ ๊ฒƒ์€ ์›๋ž˜ ๊ฐ’์ธ ์ฑ„๋กœ ์žˆ์Œ์„ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Q. ๋ฉฑ๋“ฑ์„ฑ๏ผˆidempotency๏ผ‰์ด๋ž€ ๋ฌด์—‡์ธ๊ฐ€์š”๏ผŸ
A. ๊ฐ™์€ ์š”์ฒญ์„ ๋ช‡ ๋ฒˆ ๋ณด๋‚ด๋„ ๊ฒฐ๊ณผ๊ฐ€ ๋ฐ”๋€Œ์ง€ ์•Š๋Š” ์„ฑ์งˆ์ž…๋‹ˆ๋‹ค. PUT์€ ๋ฉฑ๋“ฑ์„ฑ์„ ๊ฐ€์ง€์ง€๋งŒ POST๋Š” ๋งค๋ฒˆ ์ƒˆ๋กœ์šด ๋ฆฌ์†Œ์Šค๊ฐ€ ๋งŒ๋“ค์–ด์ง€๋ฏ€๋กœ ๋ฉฑ๋“ฑ์ด ์•„๋‹™๋‹ˆ๋‹ค. API ํ’ˆ์งˆ ๊ฒ€์ฆ์—์„œ ์ค‘์š”ํ•œ ๊ฐœ๋…์ž…๋‹ˆ๋‹ค.

Q. PUTใƒปPATCH ํ…Œ์ŠคํŠธ๋Š” GETใƒปPOST ํ…Œ์ŠคํŠธ ํ›„์— ์ž‘์„ฑํ•ด์•ผ ํ•˜๋‚˜์š”๏ผŸ
A. ์˜ˆ. CRUD ์กฐ์ž‘ ์ˆœ์„œ๋Œ€๋กœ ใ€ŒGETโ†’POSTโ†’PUT/PATCHโ†’DELETEใ€์˜ ์ˆœ์œผ๋กœ ๊ตฌํ˜„ํ•˜๋ฉด ํ…Œ์ŠคํŠธ์˜ ํ๋ฆ„์ด ์ž์—ฐ์Šค๋Ÿฝ์Šต๋‹ˆ๋‹ค. ์—…๋ฐ์ดํŠธ ํ…Œ์ŠคํŠธ๋Š” ๊ธฐ์กด ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•˜๋Š” ๊ฒƒ์ด ์ „์ œ๊ฐ€ ๋˜๋ฏ€๋กœ GET๊ณผ POST๊ฐ€ ๋™์ž‘ํ•˜๋Š” ๊ฒƒ์„ ๋จผ์ € ํ™•์ธํ•˜์„ธ์š”.


09. ์ •๋ฆฌ

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

์ด ๊ธ€์—์„œ๋Š” pytest์™€ requests๋ฅผ ์‚ฌ์šฉํ•œ API PUTใƒปPATCH ํ…Œ์ŠคํŠธ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•์„ ํ•ด์„คํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋‚ด์šฉ
TC01PUT์œผ๋กœ ์ „์ฒด ์—…๋ฐ์ดํŠธ ์‹œ ์ƒํƒœ ์ฝ”๋“œ 200์ด ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค
TC02raise_for_status()๋กœ 4xx/5xx๋ฅผ ๊ฒ€์ง€
TC03PUT์œผ๋กœ ์—…๋ฐ์ดํŠธํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์‘๋‹ต์— ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋ฐ˜์˜๋˜์–ด์•ผ ํ•œ๋‹ค
TC04Content-Type์ด JSON์ด์–ด์•ผ ํ•œ๋‹ค
TC05PATCH๋กœ ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ ์‹œ ์ƒํƒœ ์ฝ”๋“œ 200์ด ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค
TC06PATCH๋กœ ์ง€์ •ํ•œ ํ•„๋“œ๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜์–ด์•ผ ํ•œ๋‹ค
TC07PATCH๋กœ ๋ณต์ˆ˜ ํ•„๋“œ๋ฅผ ๋™์‹œ์— ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค
TC08์‘๋‹ต ์Šคํ‚ค๋งˆ๏ผˆํƒ€์ž…๏ผ‰๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๋‹ค
TC09์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฆฌ์†Œ์Šค์— PUTํ–ˆ์„ ๋•Œ ์ ์ ˆํ•œ ์ƒํƒœ๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค

๋‹ค์Œ ๊ธ€์—์„œ๋Š” DELETE ์š”์ฒญ์˜ API ํ…Œ์ŠคํŠธ๏ผˆ๋ฐ์ดํ„ฐ ์‚ญ์ œ ๊ฒ€์ฆ๏ผ‰๋ฅผ ํ•ด์„คํ•ฉ๋‹ˆ๋‹ค.

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