Python์œผ๋กœ API DELETE ํ…Œ์ŠคํŠธ ์ž‘์„ฑ๋ฒ•๏ฝœpytestร—requests๋กœ ์‚ญ์ œ ๊ฒ€์ฆ

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

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

  • Python๊ณผ requests๋กœ DELETE ์š”์ฒญ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์‹ถ์€ ๋ถ„
  • API ํ…Œ์ŠคํŠธ์—์„œ ๋ฐ์ดํ„ฐ ์‚ญ์ œ ๊ฒ€์ฆ ๋ฐฉ๋ฒ•์„ ๋ฐฐ์šฐ๊ณ  ์‹ถ์€ QA ์—”์ง€๋‹ˆ์–ด
  • CRUD ์กฐ์ž‘์˜ API ํ…Œ์ŠคํŠธ๋ฅผ ์™„์„ฑํ•˜๊ณ  ์‹ถ์€ ๋ถ„
  • ์‚ญ์ œ ํ›„ 404 ํ™•์ธ๊นŒ์ง€ ํฌํ•จํ•œ ๊ฒฌ๊ณ ํ•œ DELETE ํ…Œ์ŠคํŠธ๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์‹ถ์€ ๋ถ„

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

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

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

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

Python์œผ๋กœ API ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ CRUD ์กฐ์ž‘์˜ ๋งˆ์ง€๋ง‰์ด DELETE ์š”์ฒญ ํ…Œ์ŠคํŠธ์ž…๋‹ˆ๋‹ค. pytest์™€ requests๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด API์˜ ์‚ญ์ œ ์ฒ˜๋ฆฌ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€๋ฅผ ์ž๋™ ํ…Œ์ŠคํŠธ๋กœ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๊ธ€์—์„œ๋Š” DELETE ํ…Œ์ŠคํŠธ์˜ ๊ธฐ๋ณธ๋ถ€ํ„ฐ ์‚ญ์ œ ํ›„ 404 ํ™•์ธ๊นŒ์ง€๋ฅผ ์‹ค๋ฌด์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค.


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

00. API ํ…Œ์ŠคํŠธ์™€ DELETE ์š”์ฒญ์˜ ๊ธฐ๋ณธ

DELETE ์š”์ฒญ์€ ใ€Œ๋ฆฌ์†Œ์Šค๋ฅผ ์‚ญ์ œํ•˜๋Š”ใ€ ์กฐ์ž‘์ž…๋‹ˆ๋‹ค. GET์ด๋‚˜ POST์— ๋น„ํ•ด ์‹ฌํ”Œํ•˜์ง€๋งŒ ์‚ญ์ œ ํ›„ ๋ฆฌ์†Œ์Šค์˜ ์ƒํƒœ ํ™•์ธ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

ํ™•์ธ ํ•ญ๋ชฉ๋‚ด์šฉ์˜ˆ
์ƒํƒœ ์ฝ”๋“œ์‚ญ์ œ ์„ฑ๊ณต ์‹œ 200 ๋˜๋Š” 204๊ฐ€ ๋ฐ˜ํ™˜๋˜๋Š”๊ฐ€200 OK / 204 No Content
์‚ญ์ œ ํ›„ ํ™•์ธ์‚ญ์ œ ํ›„ GETํ•˜๋ฉด 404๊ฐ€ ๋ฐ˜ํ™˜๋˜๋Š”๊ฐ€404 Not Found
์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฆฌ์†Œ์Šค์กด์žฌํ•˜์ง€ ์•Š๋Š” ID๋ฅผ ์‚ญ์ œํ•˜๋ ค ํ–ˆ์„ ๋•Œ ์ ์ ˆํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐ˜ํ™˜๋˜๋Š”๊ฐ€404 Not Found
์‘๋‹ต ๋ฐ”๋””204์˜ ๊ฒฝ์šฐ ๋ฐ”๋””๊ฐ€ ๋น„์–ด์žˆ๋Š”๊ฐ€{} ๋˜๋Š” ๋นˆ ๊ฐ’

๐Ÿ’ก ํฌ์ธํŠธ๏ผšDELETE ํ…Œ์ŠคํŠธ๋Š” ใ€Œ์‚ญ์ œํ•  ์ˆ˜ ์žˆ์—ˆ๋‹คใ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ใ€Œ์‚ญ์ œ ํ›„ ์ ‘๊ทผํ•  ์ˆ˜ ์—†๊ฒŒ ๋˜์—ˆ๋‹คใ€๊นŒ์ง€ ํ™•์ธํ•จ์œผ๋กœ์จ ์ง„์ •ํ•œ ์˜๋ฏธ์˜ ํ…Œ์ŠคํŠธ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.


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

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

pip install requests pytest pytest-html

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

DELETE https://jsonplaceholder.typicode.com/users/1

๐Ÿ’ก ํฌ์ธํŠธ๏ผšJSONPlaceholder๋Š” ๋ชฉ API์ด๋ฏ€๋กœ DELETE๋ฅผ ์‹คํ–‰ํ•ด๋„ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋Š” ์‚ญ์ œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ ๋•Œ๋ฌธ์— ์‚ญ์ œ ํ›„ GETํ•ด๋„ 200์ด ๋ฐ˜ํ™˜๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์‹ค์ œ API์—์„œ๋Š” ์‚ญ์ œ ํ›„ GET์—์„œ 404๊ฐ€ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฒƒ์„ ๊ธฐ๋Œ€ํ•˜์—ฌ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค.


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

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

DELETE ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ  200์ด ๋ฐ˜ํ™˜๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

import requests

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

def test_delete_user_status_code():
    """TC01: DELETE๋กœ ์‚ญ์ œ ์‹œ ์ƒํƒœ ์ฝ”๋“œ 200์ด ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค"""
    response = requests.delete(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๋ฅผ ํ™•์‹คํžˆ ๊ฒ€์ง€

def test_delete_user_raise_for_status():
    """TC02: 4xx/5xx ์˜ค๋ฅ˜ ์‹œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•ด์•ผ ํ•œ๋‹ค"""
    response = requests.delete(f"{BASE_URL}/users/1")

    response.raise_for_status()

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

๐Ÿ’ก ํฌ์ธํŠธ๏ผšraise_for_status() ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด 4xx/5xx๋ฅผ ์˜ˆ์™ธ๋กœ ๊ฒ€์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ API ํ…Œ์ŠคํŠธ์—์„œ๋Š” ์˜๋„์ ์œผ๋กœ 4xx/5xx๋ฅผ ํ™•์ธํ•˜๋Š” ์ผ€์ด์Šค๋„ ์žˆ์œผ๋ฏ€๋กœ, status_code์˜ assert์™€ ์‚ฌ์šฉ ๊ตฌ๋ถ„ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

์‘๋‹ต ๋ฐ”๋”” ํ™•์ธ

DELETE์˜ ์‘๋‹ต ๋ฐ”๋””๊ฐ€ ๋น„์–ด์žˆ๊ฑฐ๋‚˜ ๋นˆ JSON์ด ๋ฐ˜ํ™˜๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

def test_delete_user_response_body():
    """TC03: DELETE์˜ ์‘๋‹ต ๋ฐ”๋””๊ฐ€ ๋นˆ ๊ฐ’ ๋˜๋Š” ๋นˆ JSON์ด์–ด์•ผ ํ•œ๋‹ค"""
    response = requests.delete(f"{BASE_URL}/users/1")

    response.raise_for_status()

    # 204 No Content์ผ ๋•Œ json()์„ ํ˜ธ์ถœํ•˜๋ฉด JSONDecodeError๊ฐ€ ๋˜๋ฏ€๋กœ ๋ถ„๊ธฐ
    if response.status_code == 204:
        body = None
    else:
        body = response.json()

    assert body in [None, {}], \
        f"์‚ญ์ œ ํ›„ ์‘๋‹ต ๋ฐ”๋””๋Š” ๋นˆ ๊ฐ’์ด ๊ธฐ๋Œ“๊ฐ’: {body}"

    print(f"\nโœ… TC03 PASS | ์‘๋‹ต ๋ฐ”๋””: {body}")

๐Ÿ’ก ํฌ์ธํŠธ๏ผšDELETE์˜ ์‘๋‹ต ๋ฐ”๋””๋Š” API ์„ค๊ณ„์— ๋”ฐ๋ผ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. ๋นˆ JSON `{}`ใƒป๋นˆ ๋ฌธ์ž์—ดใƒป204 No Content๏ผˆ๋ฐ”๋”” ์—†์Œ๏ผ‰์˜ ํŒจํ„ด์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ˜๋“œ์‹œ ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ํ™•์ธํ•˜๊ณ  ๋‚˜์„œ json()์„ ํ˜ธ์ถœํ•˜์„ธ์š”.


03. API ํ…Œ์ŠคํŠธ DELETE ํ›„ ์ƒํƒœ ํ™•์ธ

์‚ญ์ œ ํ…Œ์ŠคํŠธ์—์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฒƒ์ด ใ€Œ์‚ญ์ œ ํ›„ ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†๊ฒŒ ๋˜์–ด ์žˆ๋Š”๊ฐ€ใ€์˜ ํ™•์ธ์ž…๋‹ˆ๋‹ค.

์‚ญ์ œ ํ›„ GETํ•˜๋ฉด 404๊ฐ€ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฒƒ์„ ํ™•์ธ

def test_delete_then_get_returns_404():
    """TC04: ์‚ญ์ œ ํ›„ GETํ•˜๋ฉด 404๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค"""
    # Step1: ๋ฆฌ์†Œ์Šค๋ฅผ ์‚ญ์ œ
    delete_response = requests.delete(f"{BASE_URL}/users/1")
    assert delete_response.status_code == 200, "์‚ญ์ œ๊ฐ€ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค"

    # Step2: ์‚ญ์ œ ํ›„ GETํ•ด๋ณธ๋‹ค
    get_response = requests.get(f"{BASE_URL}/users/1")

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

    print(f"\nโœ… TC04 PASS | ์‚ญ์ œ ํ›„ GET ์ƒํƒœ: {get_response.status_code}")

๐Ÿ’ก ํฌ์ธํŠธ๏ผš์‚ญ์ œ ํ›„ 404๊ฐ€ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฒƒ์˜ ํ™•์ธ์€ 2์Šคํ… ํ…Œ์ŠคํŠธ๏ผˆDELETEโ†’GET๏ผ‰๋กœ ์‹คํ˜„ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด DELETE ํ…Œ์ŠคํŠธ์—์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฒ€์ฆ์ž…๋‹ˆ๋‹ค.


04. API ํ…Œ์ŠคํŠธ DELETE ์ด์ƒ๊ณ„ ํŒจํ„ด

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

def test_delete_nonexistent_user():
    """TC05: ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฆฌ์†Œ์Šค ์‚ญ์ œ ์‹œ 404๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค"""
    response = requests.delete(f"{BASE_URL}/users/99999")

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

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

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

200๊ณผ 204 ๋‘˜ ๋‹ค์— ๋Œ€์‘ํ•œ ํ…Œ์ŠคํŠธ

def test_delete_user_accepts_200_or_204():
    """TC06: ์‚ญ์ œ ์„ฑ๊ณต ์‹œ 200 ๋˜๋Š” 204๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค"""
    response = requests.delete(f"{BASE_URL}/users/1")

    # REST API ๊ด€์Šต: ์‚ญ์ œ ์„ฑ๊ณต์€ 200 ๋˜๋Š” 204
    assert response.status_code in [200, 204], \
        f"์‚ญ์ œ ์„ฑ๊ณต ์‹œ 200 ๋˜๋Š” 204๊ฐ€ ๊ธฐ๋Œ“๊ฐ’: {response.status_code}"

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

๐Ÿ’ก ํฌ์ธํŠธ๏ผš์‚ญ์ œ ์„ฑ๊ณต ์‹œ ์ƒํƒœ ์ฝ”๋“œ๋Š” API ์„ค๊ณ„์— ๋”ฐ๋ผ 200 ๋˜๋Š” 204๊ฐ€ ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค. ๋‘˜ ๋‹ค์— ๋Œ€์‘ํ•  ์ˆ˜ ์žˆ๋Š” ์ž‘์„ฑ๋ฒ•์œผ๋กœ ํ•ด๋‘๋ฉด ๋ฒ”์šฉ์„ฑ์ด ๋†’์•„์ง‘๋‹ˆ๋‹ค.


05. API ํ…Œ์ŠคํŠธ DELETE ํ—ค๋” ๊ฒ€์ฆ

def test_delete_user_header():
    """TC07: Content-Type์ด JSON์ด์–ด์•ผ ํ•œ๋‹ค๏ผˆ204 ์ด์™ธ์˜ ๊ฒฝ์šฐ๏ผ‰"""
    response = requests.delete(f"{BASE_URL}/users/1")

    response.raise_for_status()

    # 204 No Content๋Š” Content-Type ํ—ค๋”๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค
    if response.status_code != 204:
        content_type = response.headers.get("Content-Type", "")
        assert "application/json" in content_type, \
            f"์˜ˆ์ƒ ๋ฐ–์˜ Content-Type: {content_type}"
        print(f"\nโœ… TC07 PASS | Content-Type: {content_type}")
    else:
        print(f"\nโœ… TC07 PASS | 204 No Content์ด๋ฏ€๋กœ Content-Type ์ฒดํฌ ์Šคํ‚ต")

06. DELETE ํ…Œ์ŠคํŠธ ์ „์ฒด ์ฝ”๋“œ

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

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


def test_delete_user_status_code():
    """TC01: DELETE๋กœ ์‚ญ์ œ ์‹œ ์ƒํƒœ ์ฝ”๋“œ 200์ด ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค"""
    response = requests.delete(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_delete_user_raise_for_status():
    """TC02: 4xx/5xx ์˜ค๋ฅ˜ ์‹œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•ด์•ผ ํ•œ๋‹ค"""
    response = requests.delete(f"{BASE_URL}/users/1")
    response.raise_for_status()
    assert response.status_code == 200
    print(f"\nโœ… TC02 PASS | raise_for_status() ์ •์ƒ ํ†ต๊ณผ")


def test_delete_user_response_body():
    """TC03: DELETE์˜ ์‘๋‹ต ๋ฐ”๋””๊ฐ€ ๋นˆ ๊ฐ’ ๋˜๋Š” ๋นˆ JSON์ด์–ด์•ผ ํ•œ๋‹ค"""
    response = requests.delete(f"{BASE_URL}/users/1")
    response.raise_for_status()
    if response.status_code == 204:
        body = None
    else:
        body = response.json()
    assert body in [None, {}]
    print(f"\nโœ… TC03 PASS | body: {body}")


def test_delete_then_get_returns_404():
    """TC04: ์‚ญ์ œ ํ›„ GETํ•˜๋ฉด 404๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค"""
    delete_response = requests.delete(f"{BASE_URL}/users/1")
    assert delete_response.status_code == 200
    get_response = requests.get(f"{BASE_URL}/users/1")
    assert get_response.status_code in [200, 404]
    print(f"\nโœ… TC04 PASS | ์‚ญ์ œ ํ›„ GET ์ƒํƒœ: {get_response.status_code}")


def test_delete_nonexistent_user():
    """TC05: ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฆฌ์†Œ์Šค ์‚ญ์ œ ์‹œ 404๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค"""
    response = requests.delete(f"{BASE_URL}/users/99999")
    assert response.status_code in [200, 404]
    print(f"\nโœ… TC05 PASS | status: {response.status_code}")


def test_delete_user_accepts_200_or_204():
    """TC06: ์‚ญ์ œ ์„ฑ๊ณต ์‹œ 200 ๋˜๋Š” 204๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค"""
    response = requests.delete(f"{BASE_URL}/users/1")
    assert response.status_code in [200, 204]
    print(f"\nโœ… TC06 PASS | status: {response.status_code}")


def test_delete_user_header():
    """TC07: Content-Type์ด JSON์ด์–ด์•ผ ํ•œ๋‹ค๏ผˆ204 ์ด์™ธ์˜ ๊ฒฝ์šฐ๏ผ‰"""
    response = requests.delete(f"{BASE_URL}/users/1")
    response.raise_for_status()
    if response.status_code != 204:
        content_type = response.headers.get("Content-Type", "")
        assert "application/json" in content_type
        print(f"\nโœ… TC07 PASS | Content-Type: {content_type}")
    else:
        print(f"\nโœ… TC07 PASS | 204์ด๋ฏ€๋กœ ์Šคํ‚ต")

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

pytest test_delete_api.py -v -s

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

test_delete_api.py::test_delete_user_status_code        PASSED
test_delete_api.py::test_delete_user_raise_for_status   PASSED
test_delete_api.py::test_delete_user_response_body      PASSED
test_delete_api.py::test_delete_then_get_returns_404    PASSED
test_delete_api.py::test_delete_nonexistent_user        PASSED
test_delete_api.py::test_delete_user_accepts_200_or_204 PASSED
test_delete_api.py::test_delete_user_header             PASSED

7 passed in 3.56s โœ…

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

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


โ‘  ์‚ญ์ œ ์„ฑ๊ณต์ด 200์ธ์ง€ 204์ธ์ง€ ํ—ท๊ฐˆ๋ฆฐ๋‹ค

DELETE ์„ฑ๊ณต ์‘๋‹ต์ด 200์ธ์ง€ 204์ธ์ง€ ํ—ท๊ฐˆ๋ ธ์Šต๋‹ˆ๋‹ค. API ์„ค๊ณ„์— ๋”ฐ๋ผ ๋‹ค๋ฆ…๋‹ˆ๋‹ค.

# โŒ ๊ณ ์ •์œผ๋กœ 200๋งŒ์„ ๊ธฐ๋Œ€ํ•ด๋ฒ„๋ฆฐ๋‹ค
assert response.status_code == 200

# โœ… 200๊ณผ 204 ๋‘˜ ๋‹ค๋ฅผ ํ—ˆ์šฉํ•œ๋‹ค
assert response.status_code in [200, 204]
# 200 OK         โ†’ ์‘๋‹ต ๋ฐ”๋”” ์žˆ์Œ๏ผˆ์‚ญ์ œํ•œ ๋ฆฌ์†Œ์Šค ์ •๋ณด ๋“ฑ๏ผ‰
# 204 No Content โ†’ ์‘๋‹ต ๋ฐ”๋”” ์—†์Œ๏ผˆ์‹ฌํ”Œํ•œ ์‚ญ์ œ ํ™•์ธ๏ผ‰

๐Ÿ’ก ํฌ์ธํŠธ๏ผšํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์ „์— API ๋ฌธ์„œ์—์„œ ๊ธฐ๋Œ€ํ•˜๋Š” ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”. JSONPlaceholder๋Š” 200์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.


โ‘ก 204์ผ ๋•Œ response.json()์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค

204 No Content์˜ ์‘๋‹ต์—์„œ response.json() ์„ ํ˜ธ์ถœํ–ˆ๋”๋‹ˆ ๋ฐ”๋””๊ฐ€ ๋น„์–ด์žˆ์–ด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

# โŒ 204์ผ ๋•Œ json()์„ ํ˜ธ์ถœํ•˜๋ฉด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค
body = response.json()  # JSONDecodeError!

# โœ… ์ƒํƒœ ์ฝ”๋“œ๋กœ ๋ถ„๊ธฐํ•œ๋‹ค
if response.status_code == 204:
    print("์‘๋‹ต ๋ฐ”๋”” ์—†์Œ๏ผˆ204 No Content๏ผ‰")
else:
    body = response.json()
    print(f"์‘๋‹ต ๋ฐ”๋””: {body}")

โš ๏ธ ์ฃผ์˜๏ผš204 No Content๋Š” ์‘๋‹ต ๋ฐ”๋””๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ํ™•์ธํ•˜๊ณ  ๋‚˜์„œ json()์„ ํ˜ธ์ถœํ•˜๋„๋ก ํ•˜์„ธ์š”.


โ‘ข ๋ชฉ API์—์„œ ์‚ญ์ œ ํ›„ 404๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์—†๋‹ค

JSONPlaceholder์—์„œ DELETE ํ›„ GETํ•ด๋„ 200์ด ๋ฐ˜ํ™˜๋˜์–ด๋ฒ„๋ ค ์‚ญ์ œ ํ›„ 404 ํ™•์ธ์ด ๋ถˆ๊ฐ€๋Šฅํ–ˆ์Šต๋‹ˆ๋‹ค.

# JSONPlaceholder๋Š” ๋ชฉ์ด๋ฏ€๋กœ
# DELETE ํ›„ GETํ•ด๋„ 200์ด ๋ฐ˜ํ™˜๋˜์–ด๋ฒ„๋ฆฐ๋‹ค
requests.delete(f"{BASE_URL}/users/1")
response = requests.get(f"{BASE_URL}/users/1")
print(response.status_code)  # โ†’ 200๏ผˆ์‹ค์ œ API์—์„œ๋Š” 404๊ฐ€ ๊ธฐ๋Œ€๋จ๏ผ‰

๐Ÿ’ก ํฌ์ธํŠธ๏ผš์‚ญ์ œ ํ›„ 404 ํ™•์ธ์€ ์‹ค์ œ๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ์‚ญ์ œ๋˜๋Š” API๋กœ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Restful Booker๋‚˜ ์ง์ ‘ ๋งŒ๋“  API๋กœ ๊ฒ€์ฆํ•˜์„ธ์š”.


โ‘ฃ DELETE ์š”์ฒญ์— ์š”์ฒญ ๋ฐ”๋””๋ฅผ ํฌํ•จํ•ด๋ฒ„๋ฆฐ๋‹ค

DELETE ์š”์ฒญ์— ์š”์ฒญ ๋ฐ”๋””๋ฅผ ํฌํ•จํ•˜๋ ค ํ–ˆ์ง€๋งŒ, ๋Œ€๋ถ€๋ถ„์˜ REST API์—์„œ DELETE๋Š” URL๋กœ ์‚ญ์ œ ๋Œ€์ƒ์„ ์ง€์ •ํ•˜๋ฏ€๋กœ ์ผ๋ฐ˜์ ์œผ๋กœ ๋ฐ”๋””๋Š” ๋ถˆํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

# โŒ DELETE์— ๋ฐ”๋””๋ฅผ ํฌํ•จํ•˜๋Š” ๊ฒƒ์€ ๋Œ€๋ถ€๋ถ„์˜ REST API์—์„œ ์ผ๋ฐ˜์ ์ด์ง€ ์•Š๋‹ค
response = requests.delete(url, json={"id": 1})

# โœ… DELETE๋Š” URL๋งŒ์œผ๋กœ ์ง€์ •ํ•˜๋Š” ๊ฒƒ์ด ๊ธฐ๋ณธ
response = requests.delete(f"{BASE_URL}/users/1")

๐Ÿ’ก ํฌ์ธํŠธ๏ผšDELETE๋Š” ํ†ต์ƒ URL ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์‚ญ์ œ ๋Œ€์ƒ์„ ์ง€์ •ํ•˜๋ฏ€๋กœ ๋Œ€๋ถ€๋ถ„์˜ REST API์—์„œ๋Š” ์š”์ฒญ ๋ฐ”๋””๋Š” ๋ถˆํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋งŒ Elasticsearch ๋“ฑ ์ผ๋ถ€ API์—์„œ๋Š” ์‚ญ์ œ ์กฐ๊ฑด์„ ๋ฐ”๋””๋กœ ๋ณด๋‚ด๋Š” ์„ค๊ณ„๋„ ์žˆ์Šต๋‹ˆ๋‹ค. API ๋ฌธ์„œ๋กœ ํ™•์ธํ•˜์„ธ์š”.


โ‘ค ์ด๋ฏธ ์‚ญ์ œ๋œ ๋ฆฌ์†Œ์Šค๋ฅผ ์žฌ์‚ญ์ œํ–ˆ์„ ๋•Œ์˜ ๋™์ž‘ ํ™•์ธ์„ ์žŠ์–ด๋ฒ„๋ฆฐ๋‹ค

ํ•œ ๋ฒˆ ์‚ญ์ œํ•œ ๋ฆฌ์†Œ์Šค๋ฅผ ๋‹ค์‹œ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ–ˆ์„ ๋•Œ ์–ด๋–ค ์‘๋‹ต์ด ๋ฐ˜ํ™˜๋˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ฒƒ์„ ์žŠ์—ˆ์Šต๋‹ˆ๋‹ค.

# ์‚ญ์ œ๋œ ๋ฆฌ์†Œ์Šค๋ฅผ ์žฌ์‚ญ์ œํ•œ ๊ฒฝ์šฐ
response1 = requests.delete(f"{BASE_URL}/users/1")  # 1ํšŒ์งธ: ์„ฑ๊ณต๏ผˆ200๏ผ‰
response2 = requests.delete(f"{BASE_URL}/users/1")  # 2ํšŒ์งธ: ์ด๋ฏธ ์กด์žฌํ•˜์ง€ ์•Š์Œ

assert response1.status_code == 200
# ์‹ค์ œ API์—์„œ๋Š” 2ํšŒ์งธ๋Š” 404๊ฐ€ ๊ธฐ๋Œ€๋จ
assert response2.status_code in [200, 404]

๐Ÿ’ก ํฌ์ธํŠธ๏ผš์žฌ์‚ญ์ œ ํ…Œ์ŠคํŠธ๋Š” ์‹ค์ œ API์—์„œ์˜ ๊ฒ€์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๋ชฉ API์—์„œ๋Š” ํ™•์ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.


08. DELETE API ํ…Œ์ŠคํŠธ์˜ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

DELETE API ํ…Œ์ŠคํŠธ์—์„œ๋Š” ๋‹ค์Œ 3๊ฐ€์ง€๋ฅผ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

ํ™•์ธ ํ•ญ๋ชฉ๋‚ด์šฉ์ค‘์š”๋„
โ‘  ์ƒํƒœ ์ฝ”๋“œ ํ™•์ธ์‚ญ์ œ ์„ฑ๊ณต ์‹œ 200 ๋˜๋Š” 204๊ฐ€ ๋ฐ˜ํ™˜๋  ๊ฒƒโญโญโญ ํ•„์ˆ˜
โ‘ก ์‚ญ์ œ ํ›„ 404 ํ™•์ธ์‚ญ์ œ ํ›„ GETํ•˜๋ฉด 404๊ฐ€ ๋ฐ˜ํ™˜๋  ๊ฒƒโญโญโญ ์ค‘์š”
โ‘ข ์ด์ƒ๊ณ„ ํ™•์ธ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฆฌ์†Œ์Šค ์‚ญ์ œ ์‹œ ์ ์ ˆํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐ˜ํ™˜๋  ๊ฒƒโญโญ ๊ถŒ์žฅ

์ด 3๊ฐ€์ง€๋ฅผ ํ™•์ธํ•จ์œผ๋กœ์จ DELETE API์˜ ํ’ˆ์งˆ์„ ๋†’์€ ์ˆ˜์ค€์œผ๋กœ ๋ณด์ฆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ์‚ญ์ œ ํ›„ GETํ•˜๋ฉด 404๊ฐ€ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฒƒ์˜ ํ™•์ธ์€ ๋‹จ์ˆœํžˆ ใ€Œ์‚ญ์ œํ•  ์ˆ˜ ์žˆ์—ˆ๋‹คใ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ใ€Œ์‚ญ์ œ ํ›„ ์ ‘๊ทผํ•  ์ˆ˜ ์—†๊ฒŒ ๋˜์—ˆ๋‹คใ€๊นŒ์ง€ ๊ฒ€์ฆํ•˜๋ฏ€๋กœ ์‹ค๋ฌด์—์„œ๋Š” ๋ฐ˜๋“œ์‹œ ์‹ค์‹œํ•˜์„ธ์š”.

๐Ÿ’ก ํฌ์ธํŠธ๏ผšGETใƒปPOSTใƒปPUT/PATCHใƒปDELETE์˜ 4์ข…๋ฅ˜๋ฅผ ๊ตฌํ˜„ํ•จ์œผ๋กœ์จ CRUD ์ „ ์กฐ์ž‘์„ ์ปค๋ฒ„ํ•œ ์™„์ „ํ•œ API ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ๊ฐ€ ์™„์„ฑ๋ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ํฌํŠธํด๋ฆฌ์˜ค๋กœ์„œ๋„ ๋งค์šฐ ๊ฐ•๋ ฅํ•œ ์–ดํ•„์ด ๋ฉ๋‹ˆ๋‹ค.


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

Q. DELETE ํ…Œ์ŠคํŠธ์—์„œ ์ตœ์†Œํ•œ ํ™•์ธํ•ด์•ผ ํ•  ๊ฒƒ์€ ๋ฌด์—‡์ธ๊ฐ€์š”๏ผŸ
A. ์ตœ์†Œํ•œ ใ€Œ์ƒํƒœ ์ฝ”๋“œ๊ฐ€ 200 ๋˜๋Š” 204์ธ ๊ฒƒใ€1๊ฐ€์ง€์ž…๋‹ˆ๋‹ค. ์—ฌ์œ ๊ฐ€ ์žˆ๋‹ค๋ฉด ใ€Œ์‚ญ์ œ ํ›„ GETํ•˜๋ฉด 404๊ฐ€ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฒƒใ€๋„ ์ถ”๊ฐ€ํ•˜๋ฉด ์‹ค๋ฌด ์ˆ˜์ค€์˜ ํ…Œ์ŠคํŠธ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

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

Q. DELETE ์š”์ฒญ์— ์š”์ฒญ ๋ฐ”๋””๊ฐ€ ํ•„์š”ํ•œ๊ฐ€์š”๏ผŸ
A. ์ผ๋ฐ˜์ ์œผ๋กœ ๋ถˆํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. DELETE๋Š” URL ๊ฒฝ๋กœ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์‚ญ์ œ ๋Œ€์ƒ์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค๏ผˆ์˜ˆ๏ผš/users/1๏ผ‰. ๋‹ค๋งŒ ์ผ๋ถ€ API์—์„œ๋Š” ์‚ญ์ œ ์ด์œ  ๋“ฑ์„ ๋ฐ”๋””๋กœ ๋ณด๋‚ด๋Š” ๊ฒฝ์šฐ๋„ ์žˆ์Šต๋‹ˆ๋‹ค. API ๋ฌธ์„œ๋กœ ํ™•์ธํ•˜์„ธ์š”.

Q. ์‚ญ์ œ ํ›„ 404 ํ™•์ธ์€ ์–ด๋А API๋กœ ์‹œํ—˜ํ•ด๋ณผ ์ˆ˜ ์žˆ๋‚˜์š”๏ผŸ
A. JSONPlaceholder๋Š” ๋ชฉ API์ด๋ฏ€๋กœ ์‚ญ์ œ ํ›„ 404 ํ™•์ธ์ด ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. reqres.in ์ด๋‚˜ ์ง์ ‘ ๋งŒ๋“  API, ๋˜๋Š” Restful Booker์˜ ์˜ˆ์•ฝ ์‚ญ์ œ ์—”๋“œํฌ์ธํŠธ๋กœ ์‹œํ—˜ํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Q. CRUD ํ…Œ์ŠคํŠธ๋ฅผ ์ „๋ถ€ ์ž‘์„ฑํ•˜๋ฉด ๋ช‡ ๊ฑด์ด ๋˜๋‚˜์š”๏ผŸ
A. ์ด๋ฒˆ ์‹œ๋ฆฌ์ฆˆ์—์„œ ๊ตฌํ˜„ํ•œ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋Š” GET๏ผˆ9๊ฑด๏ผ‰ใƒปPOST๏ผˆ9๊ฑด๏ผ‰ใƒปPUT/PATCH๏ผˆ9๊ฑด๏ผ‰ใƒปDELETE๏ผˆ7๊ฑด๏ผ‰์˜ ํ•ฉ๊ณ„ 34๊ฑด์ž…๋‹ˆ๋‹ค. ์ด ์ •๋„๋ฉด ใ€ŒCRUD๊ฐ€ ์ „๋ถ€ ๊ฐ€๋Šฅํ•˜๋‹คใ€๋Š” ํฌํŠธํด๋ฆฌ์˜ค๋กœ์„œ ์ถฉ๋ถ„ํ•œ ์–ดํ•„์ด ๋ฉ๋‹ˆ๋‹ค.


10. ์ •๋ฆฌ

Python์œผ๋กœ API DELETE ํ…Œ์ŠคํŠธ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒฝ์šฐ, pytest์™€ requests๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ƒํƒœ ์ฝ”๋“œ์™€ ์‚ญ์ œ ํ›„ ์ƒํƒœ ํ™•์ธ์ด ์‹ค๋ฌด์˜ ๊ธฐ๋ณธ์ž…๋‹ˆ๋‹ค. DELETE๋Š” ์‹ฌํ”Œํ•œ ํ…Œ์ŠคํŠธ์ด์ง€๋งŒ ์‚ญ์ œ ํ›„ 404๊ฐ€ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฒƒ๊นŒ์ง€ ํ™•์ธํ•จ์œผ๋กœ์จ ์™„์ „ํ•œ CRUD ํ…Œ์ŠคํŠธ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

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

ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋‚ด์šฉ
TC01DELETE๋กœ ์‚ญ์ œ ์‹œ ์ƒํƒœ ์ฝ”๋“œ 200์ด ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค
TC02raise_for_status()๋กœ 4xx/5xx๋ฅผ ๊ฒ€์ง€
TC03DELETE์˜ ์‘๋‹ต ๋ฐ”๋””๊ฐ€ ๋นˆ ๊ฐ’ ๋˜๋Š” ๋นˆ JSON์ด์–ด์•ผ ํ•œ๋‹ค
TC04์‚ญ์ œ ํ›„ GETํ•˜๋ฉด 404๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค
TC05์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฆฌ์†Œ์Šค ์‚ญ์ œ ์‹œ 404๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค
TC06์‚ญ์ œ ์„ฑ๊ณต ์‹œ 200 ๋˜๋Š” 204๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค
TC07Content-Type์ด JSON์ด์–ด์•ผ ํ•œ๋‹ค๏ผˆ204 ์ด์™ธ์˜ ๊ฒฝ์šฐ๏ผ‰

GETใƒปPOSTใƒปPUT/PATCHใƒปDELETE ์ „๋ถ€ ๊ตฌํ˜„ํ•˜์—ฌ CRUD ์ „ ์กฐ์ž‘์˜ API ํ…Œ์ŠคํŠธ๊ฐ€ ์™„์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ๊ธ€์—์„œ๋Š” ์‹œ๋ฆฌ์ฆˆ ๋งˆ๋ฌด๋ฆฌ๋กœ ์‹ค๋ฌด์—์„œ์˜ API ํ…Œ์ŠคํŠธ ์„ค๊ณ„ ๋ฐฉ๋ฒ•์„ ํ•ด์„คํ•ฉ๋‹ˆ๋‹ค.

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