Framework Pengujian Otomatis API dengan Pytest: Tutorial Praktis

INEZA Felin-Michel

INEZA Felin-Michel

22 May 2026

Framework Pengujian Otomatis API dengan Pytest: Tutorial Praktis

Apidog untuk Perusahaan

Penerapan On-Premises

SSO & RBAC

Sesuai SOC 2

Jelajahi Apidog Enterprise

Pengembang Python memilih pytest karena ia tidak menghalangi pekerjaan. Sebuah tes hanyalah sebuah fungsi yang namanya diawali dengan test_, sebuah pernyataan (assertion) hanyalah sebuah pernyataan assert biasa, dan runner akan melakukan sisanya. Pasangkan dengan pustaka requests dan Anda memiliki kerangka kerja lengkap yang mengutamakan kode untuk mengotomatiskan pengujian API, tanpa upacara yang memberatkan.

Tutorial ini menunjukkan cara membangun suite pengujian API pytest yang nyata. Anda akan menyiapkan proyek, menulis tes permintaan pertama Anda, berbagi logika pengaturan dengan fixture, menjalankan tes yang sama terhadap banyak masukan dengan parametrize, dan melakukan pernyataan terhadap status respons, body, dan Skema JSON. Setiap contoh menggunakan API bergaya publik yang realistis sehingga Anda dapat langsung mengadaptasi kodenya.

Menyiapkan proyek

Instal dua pustaka yang Anda butuhkan ke dalam lingkungan virtual:

python -m venv .venv
source .venv/bin/activate
pip install pytest requests jsonschema

Tata letak yang bersih menjaga suite tetap dapat dipelihara seiring pertumbuhannya:

api-tests/
  conftest.py        # shared fixtures
  test_users.py      # tests for the users endpoints
  test_orders.py     # tests for the orders endpoints
  pytest.ini         # configuration

Pytest menemukan tes secara otomatis. File harus dimulai dengan test_ atau diakhiri dengan _test.py, fungsi harus dimulai dengan test_, dan kelas tes harus dimulai dengan Test serta tidak memiliki metode __init__. Ikuti aturan tersebut dan Anda tidak perlu mengkonfigurasi penemuan secara manual. Jika pengujian otomatis sebagai sebuah disiplin ilmu masih baru bagi Anda, pengantar kami tentang apa itu pengujian otomatis akan memberikan konteksnya.

Mengapa pytest khusus untuk pengujian API? Pustaka requests menangani HTTP, dan pytest menangani segala sesuatu di sekitarnya: penemuan, pernyataan dengan output kegagalan yang mudah dibaca, pengaturan dan pembongkaran melalui fixture, eksekusi berbasis data melalui parametrize, dan pelaporan. Anda merangkai kerangka kerja otomatisasi API lengkap dari dua pustaka kecil yang terdokumentasi dengan baik, dalam bahasa yang kemungkinan besar sudah digunakan tim Anda untuk aplikasi itu sendiri. Kedekatan itu penting. Tes yang berada di repositori yang sama dengan kode tetap jujur, karena perubahan yang merusak dan tes yang gagal akan muncul dalam permintaan pull yang sama.

Tes API pytest mengirimkan permintaan dan melakukan pernyataan (assertion) pada respons. Berikut adalah tes terhadap 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 suite dengan pytest -v. Setiap pernyataan assert yang gagal menghasilkan laporan rinci yang menunjukkan nilai sebenarnya, yang merupakan salah satu fitur terbaik pytest. Anda tidak memerlukan metode pernyataan khusus; kerangka kerja ini menulis ulang pernyataan assert biasa untuk memberikan output yang kaya. Untuk rangkaian pemeriksaan yang lebih luas yang layak dilakukan pada respons, lihat panduan kami tentang pernyataan API.

Berbagi pengaturan dengan fixture

Mengulang URL dasar dan sesi HTTP di setiap tes adalah pemborosan. Fixture menyelesaikan masalah ini. Fixture adalah sebuah fungsi yang didekorasi dengan @pytest.fixture yang menghasilkan nilai yang dapat diminta oleh tes dengan menamainya sebagai parameter.

Letakkan fixture bersama di conftest.py agar setiap file tes dapat menggunakannya tanpa mengimpor:

# 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"},
    )
    return response.json()["token"]

Argumen scope="session" berarti sesi dibuat sekali untuk seluruh eksekusi, bukan per tes. Kata kunci yield memisahkan pengaturan dari pembongkaran: kode sebelum yield berjalan lebih dulu, kode setelahnya berjalan ketika fixture keluar dari cakupan. Sebuah tes cukup meminta apa yang dibutuhkannya:

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},
    )
    assert response.status_code == 201
    assert response.json()["status"] == "pending"

Fixture adalah pengganti modern pytest untuk gaya setup_function dan teardown_function yang lebih lama. Mereka tersusun dengan rapi, mendukung cakupan, dan membuat dependensi eksplisit, itulah sebabnya dokumentasi fixture pytest resmi merekomendasikannya sebagai pendekatan default.

Menjalankan satu tes terhadap banyak masukan

Endpoint API biasanya perlu diperiksa terhadap banyak masukan: nilai valid, nilai tidak valid, dan kasus batas. Menulis fungsi terpisah untuk setiap kasus akan membosankan. Dekorator @pytest.mark.parametrize menjalankan satu badan tes terhadap daftar masukan:

import pytest
import requests

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

Ini menghasilkan empat kasus uji terpisah dari satu fungsi. Masing-masing berjalan dan melaporkan secara independen, sehingga satu masukan yang buruk tidak menyembunyikan yang lain. Parametrize adalah jawaban bawaan pytest untuk pengujian berbasis data. Ketika kumpulan masukan menjadi besar, muatlah dari file sebagai gantinya; panduan kami tentang pengujian API berbasis data dengan CSV dan JSON mencakup pola tersebut. Jika Anda tidak yakin kode status apa yang harus dikembalikan oleh setiap masukan, referensi tentang kode status HTTP yang harus digunakan oleh REST API adalah pendamping yang berguna.

Melakukan pernyataan pada body dan skema respons

Kode status itu perlu tetapi tidak cukup. Respons 200 dengan body yang salah format masih merupakan bug. Lakukan pernyataan pada JSON yang telah diurai secara langsung:

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 isinstance(body["id"], int)
    assert body["quantity"] == 2
    assert body["total"] > 0
    assert response.elapsed.total_seconds() < 1.0

Untuk jaminan yang lebih kuat, validasi body terhadap Skema JSON. Ini menangkap pergeseran struktural, seperti bidang yang diganti namanya atau hilang, yang terlewat oleh pemeriksaan manual:

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},
    )
    validate(instance=response.json(), schema=order_schema)

Validasi skema lebih baik daripada pernyataan bidang per bidang karena satu skema mencakup seluruh bentuk respons. Pustaka jsonschema adalah pilihan standar, dan dokumentasi validasinya menjelaskan kata kunci yang didukung.

Menjalankan suite di CI

Suite pytest membuktikan nilainya ketika berjalan secara otomatis. Pytest mengembalikan kode keluar bukan nol saat gagal, yang persis seperti yang dibutuhkan server CI untuk menggagalkan pembangunan (build). Keluarkan laporan JUnit untuk tampilan sebaris:

pytest -v --junitxml=results.xml

Hubungkan perintah itu ke langkah GitHub Actions atau pipeline lainnya dan tes API Anda akan menjaga setiap komit. Panduan kami tentang tes API dalam pipeline CI/CD menunjukkan pengaturan lengkapnya, termasuk injeksi rahasia untuk token dan pemilihan lingkungan.

Dua kebiasaan CI menjaga suite pytest tetap andal. Pertama, jangan pernah meng-hardcode rahasia atau URL lingkungan di file tes. Bacalah dari variabel lingkungan agar suite yang sama berjalan terhadap staging di CI dan secara lokal tanpa perlu diedit:

import os

BASE_URL = os.environ.get("API_BASE_URL", "https://staging.example.com/v1")

Kedua, jalankan tes independen secara paralel untuk menjaga umpan balik tetap cepat. Plugin pytest-xdist menyebarkan tes di seluruh inti CPU dengan pytest -n auto. Eksekusi paralel hanya berfungsi jika tes Anda tidak berbagi status, yang merupakan alasan lain mengapa lapisan data tes itu penting. Suite yang bergantung pada urutan eksekusi akan gagal secara tidak terduga saat berjalan secara paralel.

Menjaga suite pytest tetap dapat dipelihara

Suite berisi lima puluh tes itu mudah. Suite berisi lima ratus tes membutuhkan disiplin. Tiga praktik menjaga kerangka kerja API pytest tetap sehat seiring pertumbuhannya.

Kelompokkan tes terkait ke dalam modul dan gunakan kelas hanya ketika mereka berbagi pengaturan, bukan untuk dekorasi. File test_orders.py dengan kumpulan fungsi yang jelas lebih mudah dibaca daripada satu file raksasa. Gunakan mark, yang terdaftar di pytest.ini, untuk menandai tes sehingga Anda dapat menjalankan subset: @pytest.mark.smoke untuk pemeriksaan cepat, @pytest.mark.slow untuk pemeriksaan lengkap. Jalankan kumpulan smoke pada setiap komit dan kumpulan lengkap setiap malam.

Sentralisasi konfigurasi. URL dasar, skema, dan fixture bersama harus berada di conftest.py atau modul konfigurasi kecil, jangan pernah disalin-tempel di antara file. Ketika URL staging berubah, Anda seharusnya hanya perlu mengedit satu baris. Disiplin modular yang sama yang berlaku untuk kerangka kerja apa pun, yang dibahas dalam panduan kami tentang menulis skrip tes otomatis, berlaku di sini: ekstrak apa pun yang Anda tulis dua kali ke dalam fixture atau helper.

Kapan harus beralih ke platform lain

Kerangka kerja pytest sangat baik ketika tim Anda menulis Python dan menginginkan tes yang berada di samping kode aplikasi. Ini kurang nyaman ketika staf QA atau produk perlu berkontribusi, atau ketika Anda menginginkan desain, mocking, dan eksekusi tes di satu tempat tanpa perlu memelihara kode penghubung.

Apidog mengisi celah tersebut. Ini menyediakan pembangunan tes visual, validasi skema terhadap spesifikasi OpenAPI Anda, eksekusi berbasis data dari CSV dan JSON, serta runner CLI untuk CI, semuanya tanpa menulis kode fixture dan pernyataan secara manual. Banyak tim menjalankan keduanya: pytest untuk skenario yang padat logika dan Apidog untuk cakupan luas serta untuk merancang dan mem-mock API yang diuji oleh suite pytest. Anda dapat mengunduh Apidog dan membandingkan kedua pendekatan ini pada endpoint nyata dalam satu sore.

Pertanyaan yang sering diajukan

Mengapa menggunakan pytest alih-alih unittest bawaan Python untuk pengujian API?

Pytest membutuhkan lebih sedikit boilerplate. Tes adalah fungsi biasa, pernyataan adalah pernyataan assert biasa dengan output kegagalan yang kaya, dan fixture menangani pengaturan lebih fleksibel daripada metode berbasis kelas unittest. Pytest juga memiliki ekosistem plugin yang besar dan parametrize bawaan untuk tes berbasis data. Ia masih dapat menjalankan tes bergaya unittest yang ada, sehingga migrasinya berisiko rendah.

Apa perbedaan antara fixture dan parametrize?

Fixture menyediakan sumber daya yang dapat digunakan kembali, seperti sesi HTTP atau token otentikasi, untuk tes apa pun yang memintanya. Parametrize menjalankan badan tes yang sama beberapa kali terhadap nilai masukan yang berbeda. Fixture berbagi pengaturan; parametrize memperbanyak kasus. Keduanya saling melengkapi dengan baik: tes yang diparametrize masih dapat bergantung pada fixture.

Haruskah saya melakukan pernyataan pada waktu respons dalam tes API pytest?

Anda bisa, menggunakan response.elapsed.total_seconds(), dan batas atas yang longgar dapat menangkap regresi besar. Tapi pytest adalah alat pengujian fungsional, bukan penguji beban. Untuk pekerjaan kinerja yang nyata, gunakan alat khusus. Pertahankan pernyataan waktu yang cukup longgar agar variasi jaringan normal tidak menyebabkan kegagalan yang tidak konsisten.

Bagaimana cara menjaga tes API tetap independen di pytest?

Berikan setiap tes datanya sendiri melalui fixture yang membuat dan membersihkan sumber daya, dan hindari bergantung pada urutan eksekusi tes. Pytest menjalankan tes dalam urutan file secara default, tetapi suite yang dirancang dengan baik tidak bergantung pada hal itu. Tes independen dapat berjalan secara paralel dan gagal secara terpisah, yang membuat debugging jauh lebih mudah.

Bisakah pytest memvalidasi respons terhadap spesifikasi OpenAPI?

Pytest itu sendiri tidak bisa, tetapi Anda dapat memvalidasi terhadap Skema JSON dengan pustaka jsonschema, dan ada plugin yang memeriksa respons terhadap dokumen OpenAPI. Jika validasi skema adalah inti dari alur kerja Anda, platform seperti Apidog yang memvalidasi terhadap spesifikasi OpenAPI Anda secara otomatis dapat menghemat Anda dari pengaturan plugin.

Mengembangkan API dengan Apidog

Apidog adalah alat pengembangan API yang membantu Anda mengembangkan API dengan lebih mudah dan efisien.