DEV Community

Cover image for Framework Pengujian Otomatis API dengan Pytest: Tutorial Praktis
Walse
Walse

Posted on • Originally published at apidog.com

Framework Pengujian Otomatis API dengan Pytest: Tutorial Praktis

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.

Coba Apidog hari ini

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 Test dan tidak memiliki __init__

Contoh konfigurasi dasar pytest.ini:

[pytest]
testpaths = .
python_files = test_*.py *_test.py
python_functions = test_*
addopts = -v
Enter fullscreen mode Exit fullscreen mode

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:

  1. Mengirim request
  2. Membaca response
  3. 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"
Enter fullscreen mode Exit fullscreen mode

Jalankan:

pytest -v
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Catatan implementasi:

  • scope="session" membuat requests.Session() dibuat sekali untuk seluruh test run.
  • yield memisahkan 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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,
    )
Enter fullscreen mode Exit fullscreen mode

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",
)
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

Jalankan terhadap environment berbeda:

API_BASE_URL=https://staging.example.com/v1 pytest
API_BASE_URL=https://api.example.com/v1 pytest
Enter fullscreen mode Exit fullscreen mode

Menjalankan suite di CI

Pytest mengembalikan exit code non-zero saat tes gagal, sehingga cocok untuk pipeline CI/CD.

Perintah dasar:

pytest -v
Enter fullscreen mode Exit fullscreen mode

Untuk menghasilkan laporan JUnit:

pytest -v --junitxml=results.xml
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Jalankan:

pytest -n auto
Enter fullscreen mode Exit fullscreen mode

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:

  1. Pisahkan tes berdasarkan domain endpoint
   test_users.py
   test_orders.py
   test_payments.py
Enter fullscreen mode Exit fullscreen mode
  1. Gunakan fixture untuk setup bersama

Jangan copy-paste login, header, atau pembuatan data test di setiap file.

  1. Gunakan marker untuk subset tes

Contoh pytest.ini:

   [pytest]
   markers =
       smoke: quick checks for critical API flows
       slow: slower end-to-end API checks
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Jalankan hanya smoke test:

   pytest -m smoke
Enter fullscreen mode Exit fullscreen mode
  1. 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)