Python API POST ํ…Œ์ŠคํŠธ๏ฝœpytest + requests๋กœ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ ๊ฒ€์ฆ

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

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

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

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

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

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

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

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

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


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

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

POST ์š”์ฒญ์€ ใ€Œ๋ฐ์ดํ„ฐ๋ฅผ ์ƒˆ๋กœ ์ƒ์„ฑํ•˜๋Š”ใ€ ์กฐ์ž‘์ž…๋‹ˆ๋‹ค. GET๊ณผ ๋‹ฌ๋ฆฌ ์š”์ฒญ ๋ฐ”๋””์— ๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จํ•˜์—ฌ ์„œ๋ฒ„์— ์ „์†กํ•ฉ๋‹ˆ๋‹ค.

POST ํ…Œ์ŠคํŠธ์—์„œ๋Š” ์ฃผ๋กœ ๋‹ค์Œ์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.

ํ™•์ธ ํ•ญ๋ชฉ๋‚ด์šฉ์˜ˆ
์ƒํƒœ ์ฝ”๋“œ์ƒ์„ฑ ์„ฑ๊ณต ์‹œ 201์ด ๋ฐ˜ํ™˜๋˜๋Š”๊ฐ€201 Created
์‘๋‹ต ๋ฐ”๋””์ „์†กํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋ฐ˜ํ™˜๋˜๋Š”๊ฐ€name๊ณผ email ๊ฐ’
ID ๋ถ€์—ฌ์ƒˆ๋กœ ์ƒ์„ฑ๋œ ๋ฆฌ์†Œ์Šค์— ID๊ฐ€ ๋ถ€์—ฌ๋˜์–ด ์žˆ๋Š”๊ฐ€“id”: 11
์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ถ€์ •ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋ƒˆ์„ ๋•Œ ์ ์ ˆํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐ˜ํ™˜๋˜๋Š”๊ฐ€400 Bad Request

๐Ÿ’ก ํฌ์ธํŠธ๏ผšPOST ํ…Œ์ŠคํŠธ๋Š” GET๋ณด๋‹ค ๊ฒ€์ฆ ํ•ญ๋ชฉ์ด ๋งŽ์Šต๋‹ˆ๋‹ค. ์ •์ƒ๊ณ„๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์˜ค๋ฅ˜์˜ ์ด์ƒ๊ณ„๋„ ๋ฐ˜๋“œ์‹œ ํ…Œ์ŠคํŠธํ•จ์œผ๋กœ์จ API ํ’ˆ์งˆ์„ ๋‹ด๋ณดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


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

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

pip install requests pytest pytest-html

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

POST https://jsonplaceholder.typicode.com/users

์ „์†กํ•˜๋Š” ์š”์ฒญ ๋ฐ”๋”” ์ƒ˜ํ”Œ์ž…๋‹ˆ๋‹ค.

new_user = {
    "name": "Yoshitsugu Tester",
    "username": "yoshitsugu728",
    "email": "yoshitsugu@example.com",
    "phone": "090-1234-5678",
    "website": "qa-auto-lab.com"
}

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


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

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

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

import requests

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

def test_post_user_status_code():
    """TC01: ๋ฐ์ดํ„ฐ ์ƒ์„ฑ ์‹œ ์ƒํƒœ ์ฝ”๋“œ 201์ด ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค"""
    new_user = {
        "name": "Yoshitsugu Tester",
        "username": "yoshitsugu728",
        "email": "yoshitsugu@example.com"
    }
    response = requests.post(f"{BASE_URL}/users", json=new_user)

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

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

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

def test_post_user_raise_for_status():
    """TC02: 4xx/5xx ์˜ค๋ฅ˜ ์‹œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•ด์•ผ ํ•œ๋‹ค"""
    new_user = {"name": "Test User", "email": "test@example.com"}
    response = requests.post(f"{BASE_URL}/users", json=new_user)

    # 4xx/5xx ๋•Œ ์ž๋™์œผ๋กœ HTTPError ๋ฐœ์ƒ
    response.raise_for_status()

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

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

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

def test_post_user_header():
    """TC03: Content-Type์ด JSON์ด์–ด์•ผ ํ•œ๋‹ค"""
    new_user = {"name": "Test User", "email": "test@example.com"}
    response = requests.post(f"{BASE_URL}/users", json=new_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โœ… TC03 PASS | Content-Type: {content_type}")

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

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

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

def test_post_user_response_body():
    """TC04: ์ „์†กํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์‘๋‹ต์— ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ํฌํ•จ๋˜์–ด์•ผ ํ•œ๋‹ค"""
    new_user = {
        "name": "Yoshitsugu Tester",
        "username": "yoshitsugu728",
        "email": "yoshitsugu@example.com"
    }
    response = requests.post(f"{BASE_URL}/users", json=new_user)

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

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

    print(f"\nโœ… TC04 PASS | ์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž: {body['name']}")

ID๊ฐ€ ๋ถ€์—ฌ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ

์ƒˆ๋กœ ์ƒ์„ฑ๋œ ๋ฆฌ์†Œ์Šค์— ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ID๊ฐ€ ๋ถ€์—ฌ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

def test_post_user_id_assigned():
    """TC05: ์ƒ์„ฑ๋œ ๋ฆฌ์†Œ์Šค์— ID๊ฐ€ ๋ถ€์—ฌ๋˜์–ด์•ผ ํ•œ๋‹ค"""
    new_user = {"name": "Test User", "email": "test@example.com"}
    response = requests.post(f"{BASE_URL}/users", json=new_user)

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

    assert "id" in body,               "์‘๋‹ต์— id๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค"
    assert isinstance(body["id"], int), f"id๋Š” intํ˜•์ด ๊ธฐ๋Œ“๊ฐ’: {type(body['id'])}"
    assert body["id"] > 0,             f"id๋Š” ์–‘์˜ ์ •์ˆ˜๊ฐ€ ๊ธฐ๋Œ“๊ฐ’: {body['id']}"

    print(f"\nโœ… TC05 PASS | ๋ถ€์—ฌ๋œ ID: {body['id']}")

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


03. API ํ…Œ์ŠคํŠธ POST ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ฒ€์ฆ

์‹ค๋ฌด์—์„œ๋Š” ์ •์ƒ๊ณ„๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋ถ€์ •ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋ƒˆ์„ ๋•Œ ์ ์ ˆํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐ˜ํ™˜๋˜๋Š”์ง€๋„ ๋ฐ˜๋“œ์‹œ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.

ํ•„์ˆ˜ ํ•„๋“œ ์—†์ด ์ „์†ก๏ผˆ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์˜ค๋ฅ˜ ํ™•์ธ๏ผ‰

def test_post_user_missing_required_fields():
    """TC06: ํ•„์ˆ˜ ํ•„๋“œ ์—†์ด ์ „์†กํ–ˆ์„ ๋•Œ ์ ์ ˆํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค"""
    incomplete_user = {
        "username": "yoshitsugu728"
        # name๊ณผ email์„ ์˜๋„์ ์œผ๋กœ ์ƒ๋žต
    }
    response = requests.post(f"{BASE_URL}/users", json=incomplete_user)

    # JSONPlaceholder๋Š” ๋ชฉ์ด๋ฏ€๋กœ 201์„ ๋ฐ˜ํ™˜
    # ์‹ค์ œ API์—์„œ๋Š” 400 Bad Request๊ฐ€ ๊ธฐ๋Œ€๋จ
    assert response.status_code in [201, 400], \
        f"๊ธฐ๋Œ“๊ฐ’: 201 ๋˜๋Š” 400, ์‹ค์ œ ๊ฐ’: {response.status_code}"

    print(f"\nโœ… TC06 PASS | ํ•„์ˆ˜ ํ•„๋“œ ์—†์„ ๋•Œ ์ƒํƒœ: {response.status_code}")

๋นˆ ๋ฌธ์ž์—ด์„ ์ „์†ก

def test_post_user_empty_string():
    """TC07: ๋นˆ ๋ฌธ์ž์—ด์„ ์ „์†กํ–ˆ์„ ๋•Œ ์ ์ ˆํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค"""
    invalid_user = {
        "name": "",   # ๋นˆ ๋ฌธ์ž์—ด
        "email": ""   # ๋นˆ ๋ฌธ์ž์—ด
    }
    response = requests.post(f"{BASE_URL}/users", json=invalid_user)

    assert response.status_code in [201, 400, 422], \
        f"์˜ˆ์ƒ ๋ฐ–์˜ ์ƒํƒœ ์ฝ”๋“œ: {response.status_code}"

    print(f"\nโœ… TC07 PASS | ๋นˆ ๋ฌธ์ž์—ด ์ „์†ก ์‹œ ์ƒํƒœ: {response.status_code}")

์ž˜๋ชป๋œ ์ด๋ฉ”์ผ ํ˜•์‹์„ ์ „์†ก

def test_post_user_invalid_email():
    """TC08: ์ž˜๋ชป๋œ ์ด๋ฉ”์ผ ํ˜•์‹์„ ์ „์†กํ–ˆ์„ ๋•Œ ์ ์ ˆํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค"""
    invalid_user = {
        "name": "Test User",
        "email": "not-an-email"  # ์ด๋ฉ”์ผ ํ˜•์‹์ด ์•„๋‹˜
    }
    response = requests.post(f"{BASE_URL}/users", json=invalid_user)

    assert response.status_code in [201, 400, 422], \
        f"์˜ˆ์ƒ ๋ฐ–์˜ ์ƒํƒœ ์ฝ”๋“œ: {response.status_code}"

    print(f"\nโœ… TC08 PASS | ์ž˜๋ชป๋œ ์ด๋ฉ”์ผ ์ „์†ก ์‹œ ์ƒํƒœ: {response.status_code}")

โš ๏ธ ์ฃผ์˜๏ผšJSONPlaceholder๋Š” ๋ชฉ API์ด๋ฏ€๋กœ ๋ถ€์ •ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด๋„ 201์ด ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค. ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ •ํ™•ํ•˜๊ฒŒ ํ…Œ์ŠคํŠธํ•˜๋ ค๋ฉด ์‹ค์ œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ธฐ๋Šฅ์„ ๊ฐ€์ง„ API๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. Restful Booker๋‚˜ reqres.in ๋“ฑ์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์ง์ ‘ ๋งŒ๋“  API๋กœ ๊ฒ€์ฆํ•˜์„ธ์š”.


04. API ํ…Œ์ŠคํŠธ POST ์Šคํ‚ค๋งˆ ๊ฒ€์ฆ

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

def test_post_user_schema():
    """TC09: ์‘๋‹ต ์Šคํ‚ค๋งˆ๏ผˆํƒ€์ž…๏ผ‰๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๋‹ค"""
    new_user = {
        "name": "Yoshitsugu Tester",
        "email": "yoshitsugu@example.com"
    }
    response = requests.post(f"{BASE_URL}/users", json=new_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'])}"

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

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

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

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


def test_post_user_status_code():
    """TC01: ๋ฐ์ดํ„ฐ ์ƒ์„ฑ ์‹œ ์ƒํƒœ ์ฝ”๋“œ 201์ด ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค"""
    new_user = {"name": "Yoshitsugu Tester", "email": "yoshitsugu@example.com"}
    response = requests.post(f"{BASE_URL}/users", json=new_user)
    assert response.status_code == 201, \
        f"๊ธฐ๋Œ“๊ฐ’: 201, ์‹ค์ œ ๊ฐ’: {response.status_code}"
    print(f"\nโœ… TC01 PASS | status: {response.status_code}")


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


def test_post_user_header():
    """TC03: Content-Type์ด JSON์ด์–ด์•ผ ํ•œ๋‹ค"""
    new_user = {"name": "Test User", "email": "test@example.com"}
    response = requests.post(f"{BASE_URL}/users", json=new_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โœ… TC03 PASS | Content-Type: {content_type}")


def test_post_user_response_body():
    """TC04: ์ „์†กํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์‘๋‹ต์— ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ํฌํ•จ๋˜์–ด์•ผ ํ•œ๋‹ค"""
    new_user = {"name": "Yoshitsugu Tester", "email": "yoshitsugu@example.com"}
    response = requests.post(f"{BASE_URL}/users", json=new_user)
    response.raise_for_status()
    body = response.json()
    assert body["name"] == new_user["name"]
    assert body["email"] == new_user["email"]
    print(f"\nโœ… TC04 PASS | name: {body['name']}")


def test_post_user_id_assigned():
    """TC05: ์ƒ์„ฑ๋œ ๋ฆฌ์†Œ์Šค์— ID๊ฐ€ ๋ถ€์—ฌ๋˜์–ด์•ผ ํ•œ๋‹ค"""
    new_user = {"name": "Test User", "email": "test@example.com"}
    response = requests.post(f"{BASE_URL}/users", json=new_user)
    response.raise_for_status()
    body = response.json()
    assert "id" in body
    assert isinstance(body["id"], int)
    assert body["id"] > 0
    print(f"\nโœ… TC05 PASS | ๋ถ€์—ฌ๋œ ID: {body['id']}")


def test_post_user_missing_required_fields():
    """TC06: ํ•„์ˆ˜ ํ•„๋“œ ์—†์ด ์ „์†กํ–ˆ์„ ๋•Œ ์ ์ ˆํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค"""
    incomplete_user = {"username": "yoshitsugu728"}
    response = requests.post(f"{BASE_URL}/users", json=incomplete_user)
    assert response.status_code in [201, 400]
    print(f"\nโœ… TC06 PASS | status: {response.status_code}")


def test_post_user_empty_string():
    """TC07: ๋นˆ ๋ฌธ์ž์—ด์„ ์ „์†กํ–ˆ์„ ๋•Œ ์ ์ ˆํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค"""
    invalid_user = {"name": "", "email": ""}
    response = requests.post(f"{BASE_URL}/users", json=invalid_user)
    assert response.status_code in [201, 400, 422]
    print(f"\nโœ… TC07 PASS | status: {response.status_code}")


def test_post_user_invalid_email():
    """TC08: ์ž˜๋ชป๋œ ์ด๋ฉ”์ผ ํ˜•์‹์„ ์ „์†กํ–ˆ์„ ๋•Œ ์ ์ ˆํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค"""
    invalid_user = {"name": "Test User", "email": "not-an-email"}
    response = requests.post(f"{BASE_URL}/users", json=invalid_user)
    assert response.status_code in [201, 400, 422]
    print(f"\nโœ… TC08 PASS | status: {response.status_code}")


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

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

pytest test_post_api.py -v -s

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

test_post_api.py::test_post_user_status_code              PASSED
test_post_api.py::test_post_user_raise_for_status         PASSED
test_post_api.py::test_post_user_header                   PASSED
test_post_api.py::test_post_user_response_body            PASSED
test_post_api.py::test_post_user_id_assigned              PASSED
test_post_api.py::test_post_user_missing_required_fields  PASSED
test_post_api.py::test_post_user_empty_string             PASSED
test_post_api.py::test_post_user_invalid_email            PASSED
test_post_api.py::test_post_user_schema                   PASSED

9 passed in 4.12s โœ…

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

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


โ‘  json=๊ณผ data=์˜ ์ฐจ์ด๋กœ ๋ง‰ํžŒ๋‹ค

requests๋กœ POST ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ json= ๊ณผ data= ์˜ ์ฐจ์ด๋ฅผ ๋ชฐ๋ผ์„œ ๋ง‰ํ˜”์Šต๋‹ˆ๋‹ค. data= ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด Content-Type์ด application/x-www-form-urlencoded ๊ฐ€ ๋˜์–ด๋ฒ„๋ ค JSON API์—์„œ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

# โŒ data=๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด JSON ํ˜•์‹์œผ๋กœ ์ „์†ก๋˜์ง€ ์•Š์Œ
response = requests.post(url, data={"name": "Taro"})

# โœ… json=๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ž๋™์œผ๋กœ Content-Type: application/json์ด ์„ค์ •๋จ
response = requests.post(url, json={"name": "Taro"})

๐Ÿ’ก ํฌ์ธํŠธ๏ผšjson= ์„ ์‚ฌ์šฉํ•˜๋ฉด ์š”์ฒญ ๋ฐ”๋”” ๋ณ€ํ™˜๊ณผ Content-Type ์„ค์ •์ด ์ž๋™์œผ๋กœ ์ด๋ฃจ์–ด์ง€๋ฏ€๋กœ JSON API์—๋Š” ๋ฐ˜๋“œ์‹œ json= ์„ ์‚ฌ์šฉํ•˜์„ธ์š”.


โ‘ก POST ์ƒํƒœ ์ฝ”๋“œ๊ฐ€ 200์ธ์ง€ 201์ธ์ง€ ํ—ท๊ฐˆ๋ฆฐ๋‹ค

POST ์„ฑ๊ณต ์‘๋‹ต์ด 200์ธ์ง€ 201์ธ์ง€ ํ—ท๊ฐˆ๋ ธ์Šต๋‹ˆ๋‹ค. API ์„ค๊ณ„์— ๋”ฐ๋ผ ๋‹ค๋ฅด์ง€๋งŒ REST API ๊ด€์Šต์—์„œ๋Š” ์‹ ๊ทœ ์ƒ์„ฑ์€ 201์ด ์ผ๋ฐ˜์ ์ž…๋‹ˆ๋‹ค.

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

# โœ… API ์‚ฌ์–‘์— ๋งž๊ฒŒ ํ™•์ธํ•œ๋‹ค
# REST API ๊ด€์Šต: ์ƒ์„ฑ ์„ฑ๊ณต์€ 201
assert response.status_code == 201

# ์‚ฌ์–‘์ด ์• ๋งคํ•œ ๊ฒฝ์šฐ๋Š” ๋‘˜ ๋‹ค ํ—ˆ์šฉํ•˜๋Š” ์ž‘์„ฑ๋ฒ•๋„ ๊ฐ€๋Šฅ
assert response.status_code in [200, 201]

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


โ‘ข ์‘๋‹ต ๋ฐ”๋””์— ์ „์†กํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ํฌํ•จ๋˜์ง€ ์•Š๋Š”๋‹ค

POST ์‘๋‹ต์—๋Š” ์ „์†กํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜๋  ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ์ง€๋งŒ, ์„œ๋ฒ„ ์ธก์—์„œ ๊ฐ€๊ณต๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

# โŒ ์ „์†ก ๋ฐ์ดํ„ฐ์™€ ์™„์ „ ์ผ์น˜๋ฅผ ๊ธฐ๋Œ€ํ•ด๋ฒ„๋ฆฐ๋‹ค
assert body == new_user  # ์„œ๋ฒ„ ์ธก์—์„œ id๊ฐ€ ์ถ”๊ฐ€๋˜๋ฏ€๋กœ ์‹คํŒจ

# โœ… ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ๊ฐœ๋ณ„๋กœ ๊ฒ€์ฆํ•œ๋‹ค
assert body["name"] == new_user["name"]
assert body["email"] == new_user["email"]
assert "id" in body  # ์„œ๋ฒ„๊ฐ€ ๋ถ€์—ฌํ•œ ID๋„ ํ™•์ธ

๐Ÿ’ก ํฌ์ธํŠธ๏ผšPOST ์‘๋‹ต์—๋Š” ์„œ๋ฒ„๊ฐ€ ๋ถ€์—ฌํ•œ ID๋‚˜ ์ž๋™ ์ƒ์„ฑ ํ•„๋“œ๊ฐ€ ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. ์™„์ „ ์ผ์น˜๊ฐ€ ์•„๋‹Œ ํ•„์š”ํ•œ ํ•„๋“œ๋ฅผ ๊ฐœ๋ณ„๋กœ ๊ฒ€์ฆํ•˜๋Š” ํŽธ์ด ๊ฒฌ๊ณ ํ•ฉ๋‹ˆ๋‹ค.


โ‘ฃ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์˜ค๋ฅ˜ ์ƒํƒœ ์ฝ”๋“œ๊ฐ€ 400์ธ์ง€ 422์ธ์ง€ ํ—ท๊ฐˆ๋ฆฐ๋‹ค

์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์˜ค๋ฅ˜ ์‹œ ์ƒํƒœ ์ฝ”๋“œ๊ฐ€ 400์ธ์ง€ 422์ธ์ง€ ํ—ท๊ฐˆ๋ฆฌ๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

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

# โœ… API ์‚ฌ์–‘์— ๋”ฐ๋ผ ๋‘˜ ๋‹ค ํ—ˆ์šฉํ•œ๋‹ค
assert response.status_code in [400, 422]
# 400 Bad Request    โ†’ ์ผ๋ฐ˜์ ์ธ ์š”์ฒญ ์˜ค๋ฅ˜
# 422 Unprocessable  โ†’ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์˜ค๋ฅ˜๏ผˆFastAPI ๋“ฑ์—์„œ ๋งŽ์ด ์‚ฌ์šฉ๏ผ‰

๐Ÿ’ก ํฌ์ธํŠธ๏ผš์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์˜ค๋ฅ˜ ์ƒํƒœ ์ฝ”๋“œ๋Š” ํ”„๋ ˆ์ž„์›Œํฌ์— ๋”ฐ๋ผ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. Express๋Š” 400, FastAPI๋Š” 422๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค. API ๋ฌธ์„œ๋กœ ํ™•์ธํ•˜์„ธ์š”.


โ‘ค ๋ชฉ API์—์„œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ๊ฒ€์ฆํ•  ์ˆ˜ ์—†๋‹ค

JSONPlaceholder์—์„œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ–ˆ๋”๋‹ˆ ๋ถ€์ •ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด๋„ 201์ด ๋ฐ˜ํ™˜๋˜์–ด๋ฒ„๋ ค ํ…Œ์ŠคํŠธ์˜ ์˜๋ฏธ๊ฐ€ ์—†์–ด์กŒ์Šต๋‹ˆ๋‹ค.

# JSONPlaceholder๋Š” ๋ชฉ์ด๋ฏ€๋กœ
# ๋ถ€์ •ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด๋„ 201์ด ๋ฐ˜ํ™˜๋˜์–ด๋ฒ„๋ฆฐ๋‹ค
response = requests.post(url, json={"email": "not-an-email"})
print(response.status_code)  # โ†’ 201๏ผˆ๋ณธ๋ž˜๋Š” 400์ด ๊ธฐ๋Œ€๋จ๏ผ‰

โš ๏ธ ์ฃผ์˜๏ผš์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ •ํ™•ํ•˜๊ฒŒ ํ…Œ์ŠคํŠธํ•˜๋ ค๋ฉด ์‹ค์ œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ธฐ๋Šฅ์„ ๊ฐ€์ง„ API๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. Restful Booker๋‚˜ reqres.in ๋“ฑ์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์ง์ ‘ ๋งŒ๋“  API๋กœ ๊ฒ€์ฆํ•˜์„ธ์š”.


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

Q. POST ํ…Œ์ŠคํŠธ์—์„œ ์ตœ์†Œํ•œ ํ™•์ธํ•ด์•ผ ํ•  ๊ฒƒ์€ ๋ฌด์—‡์ธ๊ฐ€์š”๏ผŸ
A. ์ตœ์†Œํ•œ ใ€Œ์ƒํƒœ ์ฝ”๋“œ๊ฐ€ 201์ธ ๊ฒƒใ€ใ€Œ์ „์†กํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์‘๋‹ต์— ํฌํ•จ๋˜์–ด ์žˆ๋Š” ๊ฒƒใ€ใ€ŒID๊ฐ€ ๋ถ€์—ฌ๋˜์–ด ์žˆ๋Š” ๊ฒƒใ€์˜ 3๊ฐ€์ง€์ž…๋‹ˆ๋‹ค. ์—ฌ์œ ๊ฐ€ ์žˆ๋‹ค๋ฉด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌใƒป์Šคํ‚ค๋งˆ ๊ฒ€์ฆใƒปํ—ค๋” ํ™•์ธ๋„ ์ถ”๊ฐ€ํ•˜๋ฉด ์‹ค๋ฌด ์ˆ˜์ค€์˜ ํ…Œ์ŠคํŠธ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

Q. requests.post()๋กœ JSON์„ ๋ณด๋‚ด๋Š” ๋ฐฉ๋ฒ•์€๏ผŸ
A. requests.post(url, json=๋ฐ์ดํ„ฐ) ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. json= ์„ ์‚ฌ์šฉํ•˜๋ฉด Content-Type์ด ์ž๋™์œผ๋กœ application/json ์œผ๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. data= ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํผ ๋ฐ์ดํ„ฐ๋กœ ์ „์†ก๋˜์–ด๋ฒ„๋ฆฌ๋ฏ€๋กœ JSON API์—๋Š” ๋ฐ˜๋“œ์‹œ json= ์„ ์‚ฌ์šฉํ•˜์„ธ์š”.

Q. GET ํ…Œ์ŠคํŠธ์™€ POST ํ…Œ์ŠคํŠธ์˜ ์ฐจ์ด๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”๏ผŸ
A. GET์€ ใ€Œ๋ฐ์ดํ„ฐ ์ทจ๋“ใ€, POST๋Š” ใ€Œ๋ฐ์ดํ„ฐ ์ƒ์„ฑใ€์ž…๋‹ˆ๋‹ค. POST ํ…Œ์ŠคํŠธ๋Š” ์š”์ฒญ ๋ฐ”๋””๋ฅผ ์ „์†กํ•˜๋Š” ์ , ์„ฑ๊ณต ์‹œ ์ƒํƒœ ์ฝ”๋“œ๊ฐ€ 201์ธ ์ , ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์˜ค๋ฅ˜ ๊ฒ€์ฆ์ด ํ•„์š”ํ•œ ์ ์ด GET๊ณผ ๋‹ค๋ฆ…๋‹ˆ๋‹ค.

Q. ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ํ…Œ์ŠคํŠธ๋Š” ์–ด๋А API๋กœ ์‹œํ—˜ํ•ด๋ณผ ์ˆ˜ ์žˆ๋‚˜์š”๏ผŸ
A. JSONPlaceholder๋Š” ๋ชฉ API์ด๋ฏ€๋กœ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ธฐ๋Šฅ์ด ์—†์Šต๋‹ˆ๋‹ค. ๋ณธ๊ฒฉ์ ์ธ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ํ…Œ์ŠคํŠธ์—๋Š” reqres.in ์ด๋‚˜ ์ง์ ‘ ๋งŒ๋“  API๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. ํฌํŠธํด๋ฆฌ์˜ค์šฉ์œผ๋กœ๋Š” Restful Booker์˜ ์ธ์ฆ ์—”๋“œํฌ์ธํŠธ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Q. POST์™€ PUT์˜ ์ฐจ์ด๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”๏ผŸ
A. POST๋Š” ใ€Œ์‹ ๊ทœ ์ƒ์„ฑใ€, PUT์€ ใ€Œ๊ธฐ์กด ๋ฐ์ดํ„ฐ์˜ ์ „์ฒด ์—…๋ฐ์ดํŠธใ€์ž…๋‹ˆ๋‹ค. POST๋Š” ๋งค๋ฒˆ ์ƒˆ๋กœ์šด ๋ฆฌ์†Œ์Šค๊ฐ€ ์ƒ์„ฑ๋˜๋Š” ๋ฐ˜๋ฉด PUT์€ ๊ฐ™์€ ์š”์ฒญ์„ ๋ช‡ ๋ฒˆ ๋ณด๋‚ด๋„ ๊ฐ™์€ ๊ฒฐ๊ณผ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค๏ผˆ๋ฉฑ๋“ฑ์„ฑ๏ผ‰. ๋‹ค์Œ ๊ธ€์—์„œ PUTใƒปPATCH ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด์„คํ•ฉ๋‹ˆ๋‹ค.


08. ์ •๋ฆฌ

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

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

ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋‚ด์šฉ
TC01์ƒํƒœ ์ฝ”๋“œ๊ฐ€ 201์ด์–ด์•ผ ํ•œ๋‹ค
TC02raise_for_status()๋กœ 4xx/5xx๋ฅผ ๊ฒ€์ง€
TC03Content-Type์ด JSON์ด์–ด์•ผ ํ•œ๋‹ค
TC04์ „์†ก ๋ฐ์ดํ„ฐ๊ฐ€ ์‘๋‹ต์— ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ํฌํ•จ๋˜์–ด์•ผ ํ•œ๋‹ค
TC05์ƒ์„ฑ๋œ ๋ฆฌ์†Œ์Šค์— ID๊ฐ€ ๋ถ€์—ฌ๋˜์–ด์•ผ ํ•œ๋‹ค
TC06ํ•„์ˆ˜ ํ•„๋“œ ์—†์„ ๋•Œ ์ ์ ˆํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค
TC07๋นˆ ๋ฌธ์ž์—ด ์ „์†ก ์‹œ ์ ์ ˆํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค
TC08์ž˜๋ชป๋œ ์ด๋ฉ”์ผ ํ˜•์‹ ์‹œ ์ ์ ˆํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค
TC09์‘๋‹ต ์Šคํ‚ค๋งˆ๏ผˆํƒ€์ž…๏ผ‰๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๋‹ค

๋‹ค์Œ ๊ธ€์—์„œ๋Š” PUTใƒปPATCH ์š”์ฒญ์˜ API ํ…Œ์ŠคํŠธ๏ผˆ๊ธฐ์กด ๋ฐ์ดํ„ฐ์˜ ์—…๋ฐ์ดํŠธใƒป๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ๏ผ‰๋ฅผ ํ•ด์„คํ•ฉ๋‹ˆ๋‹ค.

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