- 1. API Testing คืออะไร
- 2. ทำไมจึงเหมาะกับ pytest
- 3. ติดตั้งเครื่องมือ
- 4. โครงสร้างโปรเจกต์
- 5. API ที่ใช้ทดลอง
- 6. ทดสอบ GET Request
- 7. ตรวจสอบ Response Header
- 8. ทดสอบรายการข้อมูล
- 9. ทดสอบ POST Request
- 10. ใช้ Fixture ลด Code ซ้ำ
- 11. Parameterized Testing
- 12. ทดสอบ Query Parameter
- 13. Negative Testing
- 14. ทดสอบ Authentication
- 15. ใช้ Marker แบ่ง Test Suite
- 16. ตรวจสอบ Response Time
- 17. ตรวจสอบ JSON Schema
- 18. ตัวอย่าง Test Suite แบบรวม
- 19. คำสั่ง pytest ที่ควรรู้
- 20. แนวทางออกแบบ API Test ที่ดี
- 21. แนวทางสำหรับโปรเจกต์จริง
- 22. Workflow ของ API Testing ด้วย pytest
- 23. สรุป
- แหล่งอ้างอิง
pytest เป็น Testing Framework ยอดนิยมของภาษา Python ที่สามารถนำมาใช้ทดสอบได้ทั้ง Unit Test, Integration Test และ API Test โดยมีจุดเด่นคือ syntax กระชับ ใช้ assert แบบ Python ปกติ รองรับ Fixture และ Parameterized Test และสามารถต่อยอดเข้าสู่ CI/CD ได้ง่าย
สำหรับการทดสอบ REST API เราสามารถใช้ pytest ทำหน้าที่เป็น Test Runner และใช้ไลบรารี requests สำหรับส่ง HTTP Request ไปยัง API
ตัวอย่างในบทความนี้ใช้ JSONPlaceholder ซึ่งเป็น Fake REST API สำหรับการทดลองและการทำ Prototype
#1. API Testing คืออะไร
API Testing คือการตรวจสอบพฤติกรรมของ Application Programming Interface ว่าทำงานตรงตามข้อกำหนดหรือไม่ เช่น
- HTTP Status Code ถูกต้องหรือไม่
- Response Body มีข้อมูลตามที่กำหนดหรือไม่
- JSON Structure ถูกต้องหรือไม่
- Header ถูกต้องหรือไม่
- Authentication และ Authorization ทำงานถูกต้องหรือไม่
- API จัดการข้อมูลผิดรูปแบบได้อย่างเหมาะสมหรือไม่
- Response Time อยู่ในระดับที่ยอมรับได้หรือไม่
ตัวอย่างเช่น เมื่อเรียก
GET /posts/1
เราอาจคาดหวังว่า API จะตอบกลับด้วย HTTP Status 200 และ JSON ที่มี field เช่น id, title, body และ userId
#2. ทำไมจึงเหมาะกับ pytest
จุดเด่นของ pytest สำหรับงาน API Testing ได้แก่
- เขียน Test Case ได้กระชับ
- ใช้
assertของ Python ได้โดยตรง - มี Fixture สำหรับจัดการข้อมูลหรือ Client ที่ใช้ร่วมกัน
- รองรับ Parameterized Test
- แยก Test Suite ด้วย Marker ได้
- ใช้ร่วมกับ Requests, HTTPX และไลบรารี Python อื่นได้ง่าย
- ทำงานร่วมกับ GitHub Actions, GitLab CI, Jenkins และระบบ CI/CD อื่นได้ดี
#3. ติดตั้งเครื่องมือ
ตรวจสอบ Python
python --version
สร้าง Virtual Environment
python -m venv .venv
เปิดใช้งานบน macOS/Linux
source .venv/bin/activate
บน Windows PowerShell
.venv\Scripts\Activate.ps1
ติดตั้งแพ็กเกจ
pip install pytest requests
ตรวจสอบ pytest
pytest --version
#4. โครงสร้างโปรเจกต์
ตัวอย่างโครงสร้างที่เหมาะกับ API Test ขนาดเล็กถึงกลาง
api-testing/
├── tests/
│ ├── conftest.py
│ ├── test_posts.py
│ └── test_users.py
├── pytest.ini
└── requirements.txt
ไฟล์ requirements.txt
pytest
requests
ติดตั้ง dependency
pip install -r requirements.txt
#5. API ที่ใช้ทดลอง
กำหนด Base URL เป็น
https://jsonplaceholder.typicode.com
ตัวอย่าง Endpoint
GET /posts
GET /posts/1
POST /posts
PUT /posts/1
PATCH /posts/1
DELETE /posts/1
JSONPlaceholder จำลองการสร้าง แก้ไข และลบข้อมูล แต่ไม่ได้บันทึกการเปลี่ยนแปลงจริงลงฐานข้อมูล
#6. ทดสอบ GET Request
สร้างไฟล์
tests/test_posts.py
แล้วเขียน
import requests
BASE_URL = "https://jsonplaceholder.typicode.com"
def test_get_post():
response = requests.get(
f"{BASE_URL}/posts/1",
timeout=10
)
assert response.status_code == 200
data = response.json()
assert data["id"] == 1
assert "title" in data
assert "body" in data
assert "userId" in data
รัน
pytest
หรือแสดงรายละเอียดเพิ่มเติม
pytest -v
สิ่งที่ Test Case ตรวจสอบคือ
Request
↓
GET /posts/1
↓
Response
↓
Status Code == 200
↓
ตรวจสอบ JSON
#7. ตรวจสอบ Response Header
สามารถตรวจสอบ HTTP Header ได้เช่นกัน
def test_response_content_type():
response = requests.get(
f"{BASE_URL}/posts/1",
timeout=10
)
assert response.status_code == 200
assert "application/json" in response.headers["Content-Type"]
#8. ทดสอบรายการข้อมูล
def test_get_posts():
response = requests.get(
f"{BASE_URL}/posts",
timeout=10
)
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
assert len(data) > 0
นอกจากตรวจสอบ Status Code ควรตรวจสอบชนิดและเนื้อหาของข้อมูลด้วย เพราะ HTTP 200 เพียงอย่างเดียวไม่ได้หมายความว่า Response ถูกต้องตาม Business Requirement
#9. ทดสอบ POST Request
ตัวอย่างสร้าง Post ใหม่
def test_create_post():
payload = {
"title": "API Testing with pytest",
"body": "Testing REST API using Python",
"userId": 1
}
response = requests.post(
f"{BASE_URL}/posts",
json=payload,
timeout=10
)
assert response.status_code == 201
data = response.json()
assert data["title"] == payload["title"]
assert data["body"] == payload["body"]
assert data["userId"] == payload["userId"]
assert "id" in data
เมื่อใช้
json=payload
ไลบรารี Requests จะ serialize dictionary เป็น JSON สำหรับ Request ให้อัตโนมัติ
#10. ใช้ Fixture ลด Code ซ้ำ
ถ้ามีหลาย Test Case ที่ใช้ Base URL หรือ HTTP Session เดียวกัน สามารถใช้ Fixture
สร้าง
tests/conftest.py
import pytest
import requests
@pytest.fixture(scope="session")
def base_url():
return "https://jsonplaceholder.typicode.com"
@pytest.fixture(scope="session")
def api_client():
with requests.Session() as session:
yield session
จากนั้น Test Case สามารถรับ Fixture ผ่าน Parameter
def test_get_post(api_client, base_url):
response = api_client.get(
f"{base_url}/posts/1",
timeout=10
)
assert response.status_code == 200
assert response.json()["id"] == 1
ข้อดีคือ Configuration และ Resource ที่ใช้ร่วมกันถูกรวมไว้ในจุดเดียว
#11. Parameterized Testing
ถ้าต้องทดสอบ Endpoint เดียวกันกับหลายค่า ไม่ควรเขียน Test Function ซ้ำ
ใช้ pytest.mark.parametrize
import pytest
@pytest.mark.parametrize("post_id", [1, 2, 3, 4, 5])
def test_get_multiple_posts(api_client, base_url, post_id):
response = api_client.get(
f"{base_url}/posts/{post_id}",
timeout=10
)
assert response.status_code == 200
assert response.json()["id"] == post_id
pytest จะสร้าง Test Case จากข้อมูลแต่ละชุดให้อัตโนมัติ
test_get_multiple_posts[1]
test_get_multiple_posts[2]
test_get_multiple_posts[3]
test_get_multiple_posts[4]
test_get_multiple_posts[5]
#12. ทดสอบ Query Parameter
ตัวอย่างค้นหา Post ของ User หมายเลข 1
def test_filter_posts_by_user(api_client, base_url):
params = {
"userId": 1
}
response = api_client.get(
f"{base_url}/posts",
params=params,
timeout=10
)
assert response.status_code == 200
posts = response.json()
assert len(posts) > 0
assert all(post["userId"] == 1 for post in posts)
รูปแบบ Request ที่เกิดขึ้นคือ
GET /posts?userId=1
#13. Negative Testing
API Testing ไม่ควรทดสอบเฉพาะ Happy Path
ควรตรวจสอบ Invalid Input, Resource ที่ไม่มีอยู่ หรือกรณี Error ต่าง ๆ ด้วย
ตัวอย่างเรียก Resource ที่ไม่มีอยู่
def test_post_not_found(api_client, base_url):
response = api_client.get(
f"{base_url}/posts/999999",
timeout=10
)
assert response.status_code == 404
Negative Test ของระบบจริงอาจประกอบด้วย
- Missing required field
- Invalid data type
- Invalid token
- Expired token
- Unauthorized user
- Forbidden operation
- Resource not found
- Duplicate data
- Invalid query parameter
#14. ทดสอบ Authentication
API จริงมักใช้ Bearer Token
ตัวอย่าง
def test_profile():
token = "YOUR_ACCESS_TOKEN"
headers = {
"Authorization": f"Bearer {token}"
}
response = requests.get(
"https://api.example.com/profile",
headers=headers,
timeout=10
)
assert response.status_code == 200
ไม่ควรเขียน Token จริงลง Source Code
ควรอ่านจาก Environment Variable เช่น
import os
token = os.getenv("API_TOKEN")
แล้วกำหนดค่า
macOS/Linux:
export API_TOKEN="your-secret-token"
Windows PowerShell:
$env:API_TOKEN="your-secret-token"
#15. ใช้ Marker แบ่ง Test Suite
สร้าง pytest.ini
[pytest]
markers =
smoke: critical API smoke tests
regression: regression API tests
กำหนด Marker
import pytest
@pytest.mark.smoke
def test_health_check(api_client, base_url):
response = api_client.get(
f"{base_url}/posts/1",
timeout=10
)
assert response.status_code == 200
รันเฉพาะ Smoke Test
pytest -m smoke -v
รันเฉพาะ Regression
pytest -m regression -v
#16. ตรวจสอบ Response Time
สามารถวัดเวลาตอบสนองแบบง่ายได้
def test_response_time(api_client, base_url):
response = api_client.get(
f"{base_url}/posts/1",
timeout=10
)
assert response.status_code == 200
assert response.elapsed.total_seconds() < 2
อย่างไรก็ตามการตรวจ Response Time ใน Functional Test ไม่ใช่สิ่งทดแทน Performance Testing
หากต้องการ Load Test หรือ Stress Test ควรใช้เครื่องมือ เช่น
- k6
- JMeter
- Locust
#17. ตรวจสอบ JSON Schema
เมื่อ API มี Contract ชัดเจน เราสามารถตรวจสอบ Structure ของ JSON ได้
ติดตั้ง
pip install jsonschema
ตัวอย่าง Schema
from jsonschema import validate
POST_SCHEMA = {
"type": "object",
"properties": {
"userId": {"type": "integer"},
"id": {"type": "integer"},
"title": {"type": "string"},
"body": {"type": "string"}
},
"required": [
"userId",
"id",
"title",
"body"
]
}
def test_post_schema(api_client, base_url):
response = api_client.get(
f"{base_url}/posts/1",
timeout=10
)
assert response.status_code == 200
validate(
instance=response.json(),
schema=POST_SCHEMA
)
การตรวจ Schema ช่วยตรวจจับ Breaking Change เช่น field หายไป หรือชนิดข้อมูลเปลี่ยน
#18. ตัวอย่าง Test Suite แบบรวม
tests/test_posts.py
import pytest
def test_get_post(api_client, base_url):
response = api_client.get(
f"{base_url}/posts/1",
timeout=10
)
assert response.status_code == 200
data = response.json()
assert data["id"] == 1
assert isinstance(data["title"], str)
def test_create_post(api_client, base_url):
payload = {
"title": "pytest API",
"body": "API automation testing",
"userId": 1
}
response = api_client.post(
f"{base_url}/posts",
json=payload,
timeout=10
)
assert response.status_code == 201
data = response.json()
assert data["title"] == payload["title"]
assert data["body"] == payload["body"]
assert data["userId"] == payload["userId"]
@pytest.mark.parametrize("post_id", [1, 2, 3])
def test_multiple_posts(
api_client,
base_url,
post_id
):
response = api_client.get(
f"{base_url}/posts/{post_id}",
timeout=10
)
assert response.status_code == 200
assert response.json()["id"] == post_id
def test_post_not_found(api_client, base_url):
response = api_client.get(
f"{base_url}/posts/999999",
timeout=10
)
assert response.status_code == 404
#19. คำสั่ง pytest ที่ควรรู้
รัน Test ทั้งหมด
pytest
แสดงรายละเอียด
pytest -v
ลด Output
pytest -q
รันเฉพาะไฟล์
pytest tests/test_posts.py
รันเฉพาะ Test Function
pytest tests/test_posts.py::test_get_post
เลือก Test จากชื่อ
pytest -k "post"
หยุดเมื่อพบ Failure แรก
pytest -x
หยุดเมื่อ Failure ครบ 2 ครั้ง
pytest --maxfail=2
#20. แนวทางออกแบบ API Test ที่ดี
ไม่ควรตรวจเพียง
assert response.status_code == 200
แต่ควรตรวจหลายระดับ
1. Status Code
2. Response Headers
3. Response Body
4. Data Type
5. Required Fields
6. Business Rules
7. Error Handling
8. Authentication / Authorization
9. Schema / Contract
10. Response Time ตามเกณฑ์ที่เหมาะสม
ตัวอย่าง
def test_user(api_client, base_url):
response = api_client.get(
f"{base_url}/users/1",
timeout=10
)
assert response.status_code == 200
assert "application/json" in response.headers["Content-Type"]
data = response.json()
assert data["id"] == 1
assert isinstance(data["name"], str)
assert isinstance(data["email"], str)
assert "@" in data["email"]
#21. แนวทางสำหรับโปรเจกต์จริง
เมื่อ Test Suite ใหญ่ขึ้น ควรแยก Layer เช่น
api-testing/
├── clients/
│ ├── posts_client.py
│ └── users_client.py
├── tests/
│ ├── test_posts.py
│ └── test_users.py
├── schemas/
│ ├── post_schema.py
│ └── user_schema.py
├── config/
│ └── settings.py
├── conftest.py
├── pytest.ini
└── requirements.txt
แนวคิดคือไม่ให้ Test Case ต้องรู้รายละเอียดการสร้าง HTTP Request มากเกินไป
ตัวอย่าง API Client
class PostsClient:
def __init__(self, session, base_url):
self.session = session
self.base_url = base_url
def get_post(self, post_id):
return self.session.get(
f"{self.base_url}/posts/{post_id}",
timeout=10
)
def create_post(self, payload):
return self.session.post(
f"{self.base_url}/posts",
json=payload,
timeout=10
)
Test จะอ่านง่ายขึ้น
def test_get_post(posts_client):
response = posts_client.get_post(1)
assert response.status_code == 200
assert response.json()["id"] == 1
#22. Workflow ของ API Testing ด้วย pytest
┌──────────────────┐
│ Test Case │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ pytest Fixture │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ requests.Session │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ REST API │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ HTTP Response │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Assertions │
│ Status / JSON │
│ Header / Schema │
└──────────────────┘
#23. สรุป
pytest + requests เป็นชุดเครื่องมือที่เหมาะสำหรับเริ่มต้นสร้าง Automated API Testing ด้วย Python เพราะเรียนรู้ได้เร็วและขยาย Test Suite ได้ดี
แนวทางที่แนะนำคือเริ่มจาก
pytest
↓
GET / POST
↓
Assertions
↓
Fixtures
↓
Parametrization
↓
Negative Testing
↓
Authentication
↓
Schema Validation
↓
CI/CD
เมื่อระบบมีขนาดใหญ่ขึ้น ควรแยก API Client, Test Data, Configuration และ Schema ออกจาก Test Case เพื่อให้ Test Suite อ่านง่าย ดูแลรักษาง่าย และนำกลับมาใช้ซ้ำได้
#แหล่งอ้างอิง
- pytest Documentation: https://docs.pytest.org/
- Requests Documentation: https://requests.readthedocs.io/
- JSONPlaceholder Guide: https://jsonplaceholder.typicode.com/guide/