Pengembang Python memilih pytest karena tidak menghalangi workflow: tes cukup berupa fungsi test_*, assertion cukup memakai assert, dan runner menangani sisanya. Dengan requests, Anda bisa membangun suite pengujian API yang ringan, mudah dibaca, dan cocok dijalankan lokal maupun di CI.
Artikel ini menunjukkan cara membangun suite pengujian API dengan pytest: setup proyek, menulis request pertama, memakai fixture untuk konfigurasi bersama, menjalankan test case berbasis data dengan parametrize, memvalidasi status code, body, JSON Schema, dan menjalankannya di CI.
Menyiapkan proyek
Buat virtual environment, lalu instal dependensi utama:
python -m venv .venv
source .venv/bin/activate
pip install pytest requests jsonschema
Gunakan struktur proyek yang sederhana agar suite mudah dipelihara:
api-tests/
conftest.py # shared fixtures
test_users.py # tests for users endpoints
test_orders.py # tests for orders endpoints
pytest.ini # pytest configuration
Pytest akan menemukan tes secara otomatis jika Anda mengikuti konvensi berikut:
- File dimulai dengan
test_atau diakhiri dengan_test.py - Fungsi tes dimulai dengan
test_ - Class tes dimulai dengan
Testdan tidak memiliki__init__
Contoh konfigurasi dasar pytest.ini:
[pytest]
testpaths = .
python_files = test_*.py *_test.py
python_functions = test_*
addopts = -v
Jika konsep pengujian otomatis masih baru bagi Anda, baca juga pengantar tentang apa itu pengujian otomatis.
Menulis tes API pertama
Tes API biasanya melakukan tiga hal:
- Mengirim request
- Membaca response
- Melakukan assertion
Contoh tes sederhana untuk endpoint pengguna:
import requests
BASE_URL = "https://api.example.com/v1"
def test_get_user_returns_200():
response = requests.get(f"{BASE_URL}/users/42")
assert response.status_code == 200
def test_get_user_returns_expected_fields():
response = requests.get(f"{BASE_URL}/users/42")
body = response.json()
assert body["id"] == 42
assert "email" in body
assert body["status"] == "active"
Jalankan:
pytest -v
Jika assertion gagal, pytest menampilkan nilai aktual dan ekspektasi dengan jelas. Anda tidak perlu API assertion khusus karena pytest menulis ulang assert biasa agar output kegagalannya lebih informatif.
Untuk daftar assertion yang umum dipakai dalam pengujian API, lihat panduan tentang pernyataan API.
Berbagi konfigurasi dengan fixture
Jika setiap file tes menulis ulang BASE_URL, session, header, atau token, suite akan cepat sulit dirawat. Gunakan fixture.
Buat conftest.py:
# conftest.py
import pytest
import requests
BASE_URL = "https://api.example.com/v1"
@pytest.fixture(scope="session")
def api_session():
session = requests.Session()
session.headers.update({"Accept": "application/json"})
yield session
session.close()
@pytest.fixture
def auth_token(api_session):
response = api_session.post(
f"{BASE_URL}/auth/login",
json={
"email": "qa@example.com",
"password": "test-pass",
},
)
assert response.status_code == 200
return response.json()["token"]
Gunakan fixture tersebut langsung sebagai parameter tes:
def test_create_order(api_session, auth_token):
response = api_session.post(
f"{BASE_URL}/orders",
headers={"Authorization": f"Bearer {auth_token}"},
json={
"product_id": 7,
"quantity": 2,
},
)
body = response.json()
assert response.status_code == 201
assert body["status"] == "pending"
Catatan implementasi:
-
scope="session"membuatrequests.Session()dibuat sekali untuk seluruh test run. -
yieldmemisahkan setup dan teardown. - Fixture membuat dependency eksplisit: tes yang butuh token cukup meminta
auth_token.
Fixture adalah pendekatan default yang direkomendasikan dalam dokumentasi fixture pytest.
Menjalankan satu tes dengan banyak input
Endpoint API perlu diuji dengan input valid, invalid, dan edge case. Jangan tulis fungsi terpisah untuk setiap variasi. Gunakan @pytest.mark.parametrize.
import pytest
BASE_URL = "https://api.example.com/v1"
@pytest.mark.parametrize(
"user_id, expected_status",
[
(42, 200),
(99999, 404),
(0, 404),
(-1, 400),
],
)
def test_get_user_status_codes(api_session, user_id, expected_status):
response = api_session.get(f"{BASE_URL}/users/{user_id}")
assert response.status_code == expected_status
Pytest akan menghasilkan empat test case terpisah dari satu fungsi. Setiap input berjalan dan dilaporkan secara independen.
Untuk data yang lebih besar, pindahkan data test ke CSV atau JSON. Pola tersebut dibahas dalam panduan pengujian API berbasis data dengan CSV dan JSON.
Jika Anda perlu menentukan status code yang tepat untuk setiap skenario, referensi tentang kode status HTTP yang harus digunakan oleh REST API bisa membantu.
Melakukan assertion pada body response
Status code saja tidak cukup. Response 200 dengan struktur body yang salah tetap merupakan bug.
Contoh assertion pada body JSON:
def test_order_response_shape(api_session, auth_token):
response = api_session.post(
f"{BASE_URL}/orders",
headers={"Authorization": f"Bearer {auth_token}"},
json={
"product_id": 7,
"quantity": 2,
},
)
body = response.json()
assert response.status_code == 201
assert isinstance(body["id"], int)
assert body["quantity"] == 2
assert body["total"] > 0
assert response.elapsed.total_seconds() < 1.0
Gunakan assertion waktu response dengan batas yang longgar. Assertion seperti ini berguna untuk menangkap regresi besar, tetapi bukan pengganti load testing.
Memvalidasi response dengan JSON Schema
Untuk validasi struktur yang lebih kuat, gunakan jsonschema.
from jsonschema import validate
order_schema = {
"type": "object",
"required": ["id", "product_id", "quantity", "status", "total"],
"properties": {
"id": {"type": "integer"},
"product_id": {"type": "integer"},
"quantity": {"type": "integer", "minimum": 1},
"status": {"type": "string"},
"total": {"type": "number"},
},
}
def test_order_matches_schema(api_session, auth_token):
response = api_session.post(
f"{BASE_URL}/orders",
headers={"Authorization": f"Bearer {auth_token}"},
json={
"product_id": 7,
"quantity": 2,
},
)
assert response.status_code == 201
validate(
instance=response.json(),
schema=order_schema,
)
Validasi skema membantu menangkap perubahan seperti:
- Field wajib hilang
- Nama field berubah
- Tipe data berubah
- Nilai numerik melanggar batas minimum
Pustaka jsonschema adalah pilihan umum untuk validasi seperti ini. Lihat dokumentasi validasi jsonschema untuk kata kunci yang didukung.
Membaca konfigurasi dari environment variable
Jangan hardcode URL staging, token, atau rahasia lain di file tes. Ambil dari environment variable agar suite yang sama bisa berjalan lokal dan di CI.
Contoh:
# config.py
import os
BASE_URL = os.environ.get(
"API_BASE_URL",
"https://staging.example.com/v1",
)
Lalu gunakan di fixture:
# conftest.py
import pytest
import requests
from config import BASE_URL
@pytest.fixture(scope="session")
def api_session():
session = requests.Session()
session.headers.update({"Accept": "application/json"})
yield session
session.close()
Jalankan terhadap environment berbeda:
API_BASE_URL=https://staging.example.com/v1 pytest
API_BASE_URL=https://api.example.com/v1 pytest
Menjalankan suite di CI
Pytest mengembalikan exit code non-zero saat tes gagal, sehingga cocok untuk pipeline CI/CD.
Perintah dasar:
pytest -v
Untuk menghasilkan laporan JUnit:
pytest -v --junitxml=results.xml
Contoh GitHub Actions minimal:
name: API Tests
on:
pull_request:
push:
branches:
- main
jobs:
api-tests:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest requests jsonschema
- name: Run API tests
env:
API_BASE_URL: ${{ secrets.API_BASE_URL }}
run: |
pytest -v --junitxml=results.xml
Panduan lengkap tentang integrasi pipeline tersedia di artikel tes API dalam pipeline CI/CD.
Menjalankan tes secara paralel
Jika suite mulai lambat, gunakan pytest-xdist.
Instal:
pip install pytest-xdist
Jalankan:
pytest -n auto
Pastikan tes Anda independen sebelum menjalankannya paralel. Tes yang saling bergantung pada urutan eksekusi, data global, atau resource yang sama akan mudah gagal secara tidak konsisten.
Menjaga suite pytest tetap mudah dipelihara
Suite dengan 50 tes masih mudah dikelola. Suite dengan 500 tes membutuhkan struktur yang disiplin.
Praktik yang disarankan:
- Pisahkan tes berdasarkan domain endpoint
test_users.py
test_orders.py
test_payments.py
- Gunakan fixture untuk setup bersama
Jangan copy-paste login, header, atau pembuatan data test di setiap file.
- Gunakan marker untuk subset tes
Contoh pytest.ini:
[pytest]
markers =
smoke: quick checks for critical API flows
slow: slower end-to-end API checks
Contoh penggunaan:
import pytest
@pytest.mark.smoke
def test_get_current_user(api_session, auth_token):
response = api_session.get(
f"{BASE_URL}/me",
headers={"Authorization": f"Bearer {auth_token}"},
)
assert response.status_code == 200
Jalankan hanya smoke test:
pytest -m smoke
- Sentralisasi konfigurasi
URL dasar, schema, helper, dan fixture bersama sebaiknya berada di conftest.py, config.py, atau modul helper kecil.
Prinsip modular yang sama juga dibahas dalam panduan menulis skrip tes otomatis.
Kapan memakai platform lain
Pytest sangat cocok jika tim Anda:
- Menulis Python
- Ingin tes berada dekat dengan kode aplikasi
- Butuh kontrol penuh atas logic test
- Nyaman memelihara fixture dan helper sendiri
Namun, pendekatan berbasis kode bisa kurang ideal jika QA, product, atau stakeholder non-developer perlu berkontribusi langsung, atau jika Anda ingin desain API, mocking, validasi schema, dan eksekusi tes berada di satu tempat.
Apidog mengisi kebutuhan tersebut dengan test builder visual, validasi schema terhadap spesifikasi OpenAPI, eksekusi berbasis data dari CSV dan JSON, serta runner CLI untuk CI tanpa harus menulis fixture dan assertion secara manual.
Banyak tim memakai keduanya:
- Pytest untuk skenario kompleks yang butuh logic Python
- Apidog untuk cakupan API yang luas, desain API, mocking, dan validasi berbasis OpenAPI
Anda dapat mengunduh Apidog dan membandingkan kedua pendekatan tersebut pada endpoint nyata.
Pertanyaan yang sering diajukan
Mengapa menggunakan pytest alih-alih unittest bawaan Python untuk pengujian API?
Pytest membutuhkan lebih sedikit boilerplate. Tes bisa berupa fungsi biasa, assertion memakai assert, fixture menangani setup dengan fleksibel, dan parametrize mendukung pengujian berbasis data. Pytest juga dapat menjalankan tes bergaya unittest, sehingga migrasi bisa dilakukan bertahap.
Apa perbedaan antara fixture dan parametrize?
Fixture menyediakan resource yang bisa digunakan ulang, seperti HTTP session atau token autentikasi. parametrize menjalankan fungsi tes yang sama berkali-kali dengan input berbeda. Fixture berbagi setup; parametrize memperbanyak test case.
Haruskah saya melakukan assertion pada waktu response?
Bisa, memakai response.elapsed.total_seconds(). Gunakan batas yang longgar agar variasi jaringan normal tidak membuat tes flaky. Untuk pengujian performa serius, gunakan alat load testing khusus.
Bagaimana menjaga tes API tetap independen?
Berikan setiap tes data sendiri melalui fixture, bersihkan resource setelah tes, dan jangan bergantung pada urutan eksekusi. Tes yang independen lebih mudah dijalankan paralel dan lebih mudah di-debug saat gagal.
Bisakah pytest memvalidasi response terhadap spesifikasi OpenAPI?
Pytest sendiri tidak menyediakan validasi OpenAPI bawaan. Anda bisa memakai jsonschema untuk validasi JSON Schema atau plugin tambahan untuk memvalidasi response terhadap dokumen OpenAPI. Jika validasi OpenAPI menjadi bagian utama workflow, platform seperti Apidog dapat mengurangi konfigurasi manual.
Top comments (0)