Trong bài học này, chúng ta sẽ xây dựng các endpoint API đơn giản bằng Flask, sau đó viết kịch bản kiểm thử tự động sử dụng thư viện requests và khung chạy kiểm thử pytest. Trọng tâm là cách thiết kế, gọi và xác minh hành vi của API một cách hiệu quả — không phụ thuộc vào giao diện người dùng.
Xây dựng API mẫu
Endpoint tính tổng (GET/POST hỗn hợp)
Dưới đây là một dịch vụ cộng hai số, chấp nhận cả tham số URL (GET) và dữ liệu form (POST), với xử lý lỗi cơ bản:
from flask import Flask, request
app = Flask(__name__)
@app.route("/calculate/sum", methods=["GET", "POST"])
def compute_sum():
try:
# Ưu tiên lấy từ JSON nếu có, sau đó đến form, rồi params
if request.is_json:
payload = request.get_json()
val_a = payload.get("a")
val_b = payload.get("b")
else:
val_a = request.form.get("a") or request.args.get("a")
val_b = request.form.get("b") or request.args.get("b")
if val_a is None or val_b is None:
return {"error": "Thiếu tham số 'a' hoặc 'b'"}, 400
result = float(val_a) + float(val_b)
return {"status": "success", "result": result}, 200
except (ValueError, TypeError):
return {"error": "Giá trị đầu vào không hợp lệ"}, 400
except Exception as e:
return {"error": f"Lỗi hệ thống: {str(e)}"}, 500
if __name__ == "__main__":
app.run(host="127.0.0.1", port=8000, debug=True)
Endpoint trừ hai số (RESTful, chỉ POST + JSON)
Một endpoint tuân thủ nguyên tắc REST, yêu cầu dữ liệu đầu vào dưới dạng JSON và trả về phản hồi có cấu trúc rõ ràng:
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/api/difference", methods=["POST"])
def calculate_difference():
if not request.is_json:
return jsonify({
"code": 4001,
"message": "Yêu cầu phải ở định dạng JSON",
"data": None
}), 400
data = request.get_json()
a = data.get("minuend")
b = data.get("subtrahend")
if a is None or b is None:
return jsonify({
"code": 4002,
"message": "Thiếu trường 'minuend' hoặc 'subtrahend'",
"data": None
}), 400
try:
diff = float(a) - float(b)
return jsonify({
"code": 2000,
"message": "Tính toán thành công",
"data": {"difference": round(diff, 6)}
})
except ValueError:
return jsonify({
"code": 4003,
"message": "Không thể chuyển đổi tham số sang số thực",
"data": None
}), 400
if __name__ == "__main__":
app.run(port=8001)
Tự động hóa kiểm thử với pytest và requests
Kịch bản kiểm thử cho endpoint cộng
Một hàm kiểm thử đơn giản đảm bảo endpoint hoạt động đúng với các kiểu yêu cầu khác nhau:
import pytest
import requests
BASE_URL = "http://127.0.0.1:8000"
def test_sum_via_get():
response = requests.get(f"{BASE_URL}/calculate/sum", params={"a": "4.5", "b": "2.3"})
assert response.status_code == 200
assert response.json()["result"] == pytest.approx(6.8)
def test_sum_via_post_form():
response = requests.post(f"{BASE_URL}/calculate/sum", data={"a": "10", "b": "-3"})
assert response.status_code == 200
assert response.json()["result"] == 7.0
def test_sum_with_invalid_input():
response = requests.get(f"{BASE_URL}/calculate/sum", params={"a": "abc"})
assert response.status_code == 400
assert "Thiếu" in response.json()["error"]
Kịch bản kiểm thử cho endpoint trừ (JSON)
Đảm bảo header đúng, dữ liệu được gửi dưới dạng JSON và phản hồi được phân tích chính xác:
import json
import pytest
import requests
BASE_URL = "http://127.0.0.1:8001"
def test_difference_valid_json():
payload = {"minuend": 15.7, "subtrahend": 3.2}
headers = {"Content-Type": "application/json"}
response = requests.post(
f"{BASE_URL}/api/difference",
headers=headers,
data=json.dumps(payload)
)
assert response.status_code == 200
data = response.json()
assert data["code"] == 2000
assert data["data"]["difference"] == pytest.approx(12.5)
def test_difference_missing_field():
response = requests.post(
f"{BASE_URL}/api/difference",
headers={"Content-Type": "application/json"},
data=json.dumps({"minuend": 5})
)
assert response.status_code == 400
assert "subtrahend" in response.json()["message"]
Chạy kiểm thử và tạo báo cáo
Lưu các file kiểm thử với tiền tố test_ (ví dụ: test_calculations.py) và thực thi bằng lệnh:
pytest -v --html=report.html --self-contained-html
Các tùy chọn phổ biến:
-v: Hiển thị chi tiết từng case--html=report.html: Xuất báo cáo HTML tương tác--tb=short: Rút gọn thông tin traceback-k "sum": Chỉ chạy các case có tên chứa "sum"
Phân tích phản hồi và xử lý linh hoạt
Khi làm việc với requests.Response, nên kiểm tra trạng thái trước khi trích xuất nội dung:
response = requests.post(url, json=payload)
# Luôn kiểm tra status_code trước khi gọi .json()
if response.status_code == 200:
try:
result = response.json()
assert result["data"]["difference"] > 0
except ValueError:
pytest.fail("Phản hồi không phải JSON hợp lệ")
else:
pytest.fail(f"Yêu cầu thất bại với mã {response.status_code}: {response.text}")
Thiết lập môi trường kiểm thử
Để tránh xung đột cổng và tăng khả năng tái sử dụng, nên khởi động Flask trên cổng riêng biệt cho mỗi dịch vụ, và sử dụng pytest.fixture để quản lý vòng đời:
@pytest.fixture(scope="session")
def api_server():
import threading
import time
from your_app_module import app # giả sử đã tách app ra file riêng
server = threading.Thread(target=lambda: app.run(port=8000, debug=False))
server.daemon = True
server.start()
time.sleep(0.5) # Đợi server khởi động
yield
# Không cần tắt — thread daemon tự kết thúc khi pytest kết thúc