Thực hành kiểm thử API với Python: Xây dựng và tự động hóa

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

Thẻ: Flask pytest requests api-testing python

Đăng vào ngày 1 tháng 6 lúc 11:08