FDE PulseViệc làm FDE đang mở 316Mới đăng 7 ngày qua 10Chủ đề nổi bật: Đào tạo kỹ năng FDE tại Đông Nam Á

Tờ báo của nghề Forward Deployed Engineer

Bách khoa

Mock API của khách và viết contract test trong một buổi chiều

Khách chỉ cần đổi tên một field là code tích hợp của bạn có thể hỏng mà không báo một lỗi nào, trừ khi có một bài test được viết riêng để bắt đúng khoảnh khắc đó.

Đồ hoạMock và bước verify nằm ở hai phía của API khách
Mock (phía bạn)Verify (phía khách)
Chạy ở đâuTrên máy bạn, localhost:8001, đọc từ contract.jsonTừ CI, gửi từng request trong contract tới staging của khách
Bên được kiểm traCode tích hợp của bạn (consumer)API của khách (provider)
Bắt đượcCode xử lý sai ca thành công hoặc ca 404Khách đổi status, đổi tên hoặc bỏ một field
Không bắt đượcKhách đổi API: mock vẫn trả dữ liệu cũ, test vẫn xanhSai lệch về giá trị, vì bản giản lược chỉ so tên field
Cần khách sẵn sàng?Không, chạy được cả khi staging của khách đang sậpCó, cần staging chạy và dữ liệu test cố định

Mock chỉ kiểm tra code của bạn, còn verify mới chạm tới API thật, nên cần cả hai để bắt lỗi khi khách đổi API.

Đồ hoạ: FDE Times

Tóm tắt nhanh

  • Mock giúp bạn viết và test code trước khi API của khách sẵn sàng, nhưng không cho bạn biết khi khách thay đổi API.
  • Contract test dùng chung một file mô tả: phía bạn test trên mock, rồi đem từng request trong file đó gửi tới API thật để kiểm tra.
  • Đừng mock quá tay: các luồng quan trọng vẫn phải chạy với API thật, và contract phải được cập nhật mỗi khi API thay đổi.
Chia sẻLinkedInFacebookX

Thử hình dung: thứ Hai bạn deploy xong integration với hệ thống đơn hàng của khách và mọi test đều xanh. Sang thứ Năm, đội backend bên khách đổi tên field total thành amount mà không báo ai. Code của bạn không crash mà chỉ âm thầm trả về None, và báo cáo doanh thu cuối tuần ra số 0.

Lỗi này không cần đến bug phức tạp. Nó chỉ cần hai đội cùng làm trên một điểm tích hợp mà không có gì ràng buộc thỏa thuận giữa hai bên. Với một FDE, rủi ro này càng đáng lo, vì API thường nằm bên phía khách: nhiều khả năng bạn không kiểm soát được lịch release của họ, và có khi cũng không được đọc code của họ.

Hướng dẫn này đi qua hai việc trong một buổi chiều. Việc đầu tiên là mock API của khách để bạn làm việc được ngay. Việc thứ hai là viết contract test để biết được ngay khi khách đổi API.

Bạn sẽ dựng những gì?

Bạn sẽ có bốn file Python: một file contract, một mock server, một bài test phía bạn và một script verify chạy với API thật. Bạn cần Python 3, một terminal, và nếu có thể thì URL staging của một API thật để thử bước cuối.

Code dưới đây là phiên bản tự viết và đã giản lược, chỉ dùng thư viện chuẩn của Python, để bạn thấy rõ cơ chế bên trong. Trong dự án thật, Pact làm sẵn những việc này.

Theo tài liệu của Pact, đây là công cụ “code-first” để contract test cho cả tích hợp HTTP lẫn message, chủ yếu dành cho developer và tester có viết code. Cú pháp cụ thể của Pact bạn tra ở docs.pact.io; ở đây ta tự dựng lại cơ chế bằng tay để hiểu nó chạy thế nào.

Bước 1: Ghi lại thỏa thuận trước khi viết code

Cơ chế của Pact như sau: test phía consumer (phía gọi API, tức là bạn) sinh ra một pact file mô tả từng tương tác. Ở đây ta viết tay file đó, với ví dụ giả định là API đơn hàng của khách:

{
  "consumer": "fde-integration",
  "provider": "customer-orders-api",
  "interactions": [
    {
      "description": "lấy đơn hàng tồn tại",
      "request": {"method": "GET", "path": "/orders/42"},
      "response": {"status": 200,
                   "body": {"id": 42, "status": "PAID", "total": 150000}}
    },
    {
      "description": "đơn hàng không tồn tại",
      "request": {"method": "GET", "path": "/orders/999"},
      "response": {"status": 404, "body": {"error": "not_found"}}
    }
  ]
}

Lưu thành contract.json. Kiểm tra: đưa file này cho kỹ sư bên khách đọc. Nếu họ xác nhận API trả về đúng như thế thì bạn đã có thỏa thuận bằng văn bản đầu tiên, và mọi chỗ hai bên hiểu lệch nhau sẽ lộ ra ngay ở bước này.

Ca 404 được đưa vào có chủ đích. Mock cho phép bạn test vượt ra ngoài các kịch bản thông thường, và ca lỗi là chỗ code tích hợp dễ vỡ mà ít ai test tới.

Bước 2: Dựng mock từ chính contract

API mock là một web service mô phỏng service thật, và nó dùng được ngay từ giai đoạn đầu, khi service thật chưa sẵn sàng. Mock của ta sẽ đọc thẳng từ contract, để hai thứ này không bao giờ lệch nhau:

# mock_server.py — bản giản lược, chỉ khớp theo path
import json
from http.server import BaseHTTPRequestHandler, HTTPServer

CONTRACT = json.load(open("contract.json"))

class Mock(BaseHTTPRequestHandler):
    def do_GET(self):
        for it in CONTRACT["interactions"]:
            if it["request"]["path"] == self.path:
                res = it["response"]
                self.send_response(res["status"])
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps(res["body"]).encode())
                return
        self.send_response(500)
        self.end_headers()

HTTPServer(("localhost", 8001), Mock).serve_forever()
python3 mock_server.py &
curl -i http://localhost:8001/orders/999

Kiểm tra: bạn phải thấy HTTP/1.0 404 và body {"error": "not_found"}. Nếu gọi một path không có trong contract thì nhận về 500. Lỗi này có chủ đích: code của bạn đang gọi một thứ chưa có trong thỏa thuận.

Bước 3: Test code của bạn trên mock

Đây là đoạn code tích hợp thật sự, cùng bài test của nó:

# client.py
import json, urllib.request, urllib.error

def get_order_total(base_url, order_id):
    try:
        with urllib.request.urlopen(f"{base_url}/orders/{order_id}") as r:
            return json.load(r).get("total")
    except urllib.error.HTTPError as e:
        if e.code == 404:
            return None
        raise
# test_consumer.py
from client import get_order_total
BASE = "http://localhost:8001"
assert get_order_total(BASE, 42) == 150000
assert get_order_total(BASE, 999) is None
print("consumer OK")

Kiểm tra: python3 test_consumer.py in ra consumer OK. Để ý dòng .get("total"): khi field biến mất, hàm không ném lỗi mà trả về None. Đó chính là kiểu hỏng âm thầm ở đầu bài, và rất nhiều code tích hợp được viết đúng như vậy.

Mock là một API bị cô lập, không bị ảnh hưởng khi service, database hay hệ thống bên ngoài thay đổi. Vì thế bài test này chạy được cả khi staging của khách đang sập.

Nhưng chính sự cô lập đó lại là điểm yếu. Nếu khách đổi total thành amount, mock vẫn trả total và test vẫn xanh. Mock chỉ giúp bạn khi khách chưa sẵn sàng. Nó không báo cho bạn biết khi khách đã đổi API.

Bước 4: Đem contract đi hỏi API thật

Đây là nửa còn lại, và cũng là lý do contract tồn tại. Pact gọi bước này là provider verification: từng request trong pact được gửi tới provider. Contract testing kiểm tra một điểm tích hợp bằng cách xét từng ứng dụng riêng rẽ. Bạn test phía mình trên mock, còn phía khách được test bằng chính file đó.

# verify_provider.py — giản lược: so status và tên field, không so giá trị
import json, sys, urllib.request, urllib.error

contract = json.load(open("contract.json"))
base = sys.argv[1]
fails = 0
for it in contract["interactions"]:
    req, exp = it["request"], it["response"]
    try:
        r = urllib.request.urlopen(base + req["path"])
        status, body = r.status, json.load(r)
    except urllib.error.HTTPError as e:
        status, body = e.code, json.load(e)
    missing = [k for k in exp["body"] if k not in body]
    if status != exp["status"] or missing:
        fails += 1
        print(f"FAIL {it['description']}: status={status}, thiếu {missing}")
sys.exit(1 if fails else 0)
python3 verify_provider.py https://staging.khach-hang.example

Script cố ý chỉ so tên field chứ không so giá trị, vì đơn 42 trên staging của khách chắc chắn không có tổng tiền đúng bằng 150000. Kiểm tra: khi khách đổi tên field, bạn sẽ thấy FAIL lấy đơn hàng tồn tại: status=200, thiếu ['total'] và exit code 1.

Contract test giúp debug nhanh hơn hẳn, và lý do nằm ngay trong dòng output đó: thông báo lỗi chỉ ra đúng field, đúng tương tác, thay vì một con số 0 bí ẩn trên dashboard.

Lưu ý một giả định ngầm: đơn 42 phải tồn tại trên staging. Hãy thống nhất với khách về dữ liệu test cố định, nếu không bước verify sẽ đỏ vì những lý do chẳng liên quan gì đến contract.

Bước 5: Đưa vào CI để không phải nhớ chạy tay

Một script phải nhớ chạy tay thì sớm muộn cũng bị quên. Hãy cho verify_provider.py chạy theo lịch trong CI, và vì script trả exit code khác 0 khi fail, pipeline sẽ tự đỏ.

Với Pact, việc nối vào CI do Pact Broker đảm nhận. Khi số API và số đội phải verify bắt đầu tăng, đó là lúc nên bỏ bản tự viết và chuyển sang công cụ này.

Ba lỗi hay gặp

Lỗi đầu tiên là mock quá tay. Nguyên tắc là tránh over-mocking và dùng API thật bất cứ khi nào có thể, nhất là với các luồng quan trọng. Nếu luồng thanh toán của khách chỉ từng chạy trên mock, bạn chưa thật sự test nó.

Lỗi thứ hai là để contract đứng yên. Test chỉ còn phản ánh đúng hệ thống khi được cập nhật theo API đang thay đổi. Khi khách thông báo đổi API, việc đầu tiên là sửa contract.json chứ không phải sửa code. Mock và bài verify sẽ tự theo sau.

Lỗi thứ ba là so khớp quá chặt. Nếu assert từng giá trị trên dữ liệu thật, test sẽ đỏ mỗi ngày và cả đội sẽ học cách bỏ qua nó.

Làm tại chỗ khách, kỹ năng này dùng vào đâu?

Tuần đầu tại khách, API thường chưa xong, tài liệu thì cũ. Viết contract.json rồi mang đi hỏi kỹ sư bên khách là cách nhanh nhất để lộ ra những chỗ hai bên hiểu khác nhau, trước khi viết dòng code nào.

Đến lúc đi vào vận hành, bài verify chạy hằng đêm là thứ báo cho bạn biết khách vừa đổi API, thay vì phải chờ họ gọi điện báo.

Trong CV, đừng chỉ ghi “biết Pact”. Hãy ghi bạn đã dựng contract cho bao nhiêu tương tác và đã chặn được loại thay đổi nào trước khi nó lên production.

Code tích hợp của bạn chỉ chạy đúng khi API của khách giữ nguyên như hai bên đã thỏa thuận. Contract test là cách để kiểm tra điều đó mỗi ngày.

6 nguồn
Đọc tiếp trên lộ trình · Chặng 5: Triển khaiCách FDE chuẩn bị hồ sơ model risk để qua hội đồng AI theo EU AI ActHội đồng AI của khách không chấm độ chính xác của model; họ muốn biết sau mọi biện pháp, phần rủi ro còn lại là gì và ai đã ký chấp nhận nó.