Library adalah kontrak dengan pengguna: slugify() berjanji mengembalikan slug, hitung_kata() berjanji menghitung kata, dan pengguna menaruh kepercayaannya pada janji itu. Test suite adalah satu-satunya dokumen yang membuktikan kontrak tersebut ditunaikan — dan karena package bisa dipasang orang lain, satu function yang rusak berarti program orang lain ikut rusak. Pada bagian ini kita menulis test untuk setiap function beserta kasus tepinya, lalu menjalankannya hingga seluruhnya lulus.

Test Suite Lengkap #

Simpan kode berikut sebagai tests/test_package.py:

python
"""Test suite untuk seluruh function pada example_package."""

from example_package import hitung_kata, ringkas, slugify


class TestSlugify:
    def test_teks_biasa_dengan_tanda_baca(self):
        assert slugify("Belajar Python Dasar!") == "belajar-python-dasar"

    def test_spasi_berulang_dipadatkan(self):
        assert slugify("  Halo,   Dunia!  ") == "halo-dunia"

    def test_karakter_diakritik_dinormalisasi(self):
        assert slugify("Café Résumé") == "cafe-resume"

    def test_angka_dan_titik(self):
        assert slugify("Python 3.12") == "python-3-12"

    def test_teks_kosong(self):
        assert slugify("") == ""

    def test_tanpa_karakter_alfanumerik(self):
        assert slugify("!!! ???") == ""


class TestHitungKata:
    def test_teks_biasa(self):
        assert hitung_kata("Belajar Python sambil praktik") == 4

    def test_whitespace_campuran(self):
        assert hitung_kata("  spasi\ttab\ndua\n\nkata  ") == 4

    def test_teks_kosong(self):
        assert hitung_kata("") == 0


class TestRingkas:
    def test_teks_pendek_tidak_berubah(self):
        teks = "Belajar Python"
        assert ringkas(teks, 50) == teks

    def test_panjang_sama_dengan_teks(self):
        assert ringkas("Python", 6) == "Python"

    def test_teks_panjang_dipotong(self):
        teks = "Belajar Python di Course ini sampai tuntas"
        assert ringkas(teks, 20) == "Belajar Python di..."

    def test_akhir_kustom(self):
        teks = "Belajar Python di Course ini sampai tuntas"
        assert ringkas(teks, 25, akhir="…") == "Belajar Python di Course…"

    def test_panjang_lebih_kecil_dari_akhir(self):
        assert ringkas("abcdefghij", 2) == "ab"

Struktur mengikuti konvensi pytest dari Chapter Menulis Test dengan pytest: setiap method bernama test_..., dan method yang menguji satu function yang sama dikelompokkan ke dalam class Test... tanpa __init__. Empat belas test di atas sengaja memilih kasus tepi, bukan hanya jalur bahagia:

  • slugify(): spasi berulang harus dipadatkan jadi satu tanda hubung, é harus dinormalisasi, angka dipertahankan, dan teks tanpa karakter alfanumerik harus mengembalikan string kosong.
  • hitung_kata(): whitespace campuran (spasi, tab, newline) dihitung satu pemisah, dan teks kosong mengembalikan 0.
  • ringkas(): teks pendek dikembalikan tanpa perubahan, batas tepat len(text) == panjang tidak memotong, akhiran kustom dihitung dalam panjang total, dan panjang yang lebih kecil dari akhir tidak boleh membuat hasil lebih panjang dari batas.

Bagaimana pytest Menemukan Package #

Pertanyaan teknis yang selalu muncul di titik ini: tests/test_package.py menjalankan import example_package, tetapi package berada di src/ dan belum pernah dipasang — dari mana pytest menemukannya?

Jawabannya ada pada dua baris konfigurasi dari Bagian 1. Saat dijalankan tanpa argumen, pytest mencari konfigurasi di pyproject.toml; tabel [tool.pytest.ini_options] menjadikan folder proyek akar sebagai rootdir. Kunci testpaths = ["tests"] lalu membatasi pengumpulan test ke folder tersebut, dan kunci pythonpath = ["src"] memasukkan folder src ke baris paling depan sys.path sebelum test dikumpulkan — fitur yang tersedia sejak pytest 7.0. Efeknya sama dengan PYTHONPATH=src pada Bagian 2, hanya saja kali ini otomatis dan terdokumentasi di konfigurasi proyek.

Menjalankan Test Suite #

Dari akar proyek, jalankan pytest:

bash
python3 -m pytest

Output yang Dihasilkan:

text
============================= test session starts ==============================
platform linux -- Python 3.12.15, pytest-9.0.2, pluggy-1.6.0
rootdir: /home/budi/paket-python-sederhana
configfile: pyproject.toml
testpaths: tests
collected 14 items

tests/test_package.py ..............                                     [100%]

============================== 14 passed in 0.02s ==============================

Baris rootdir menampilkan folder proyek tempat pytest berjalan — isinya mengikuti lokasi proyek di masing-masing mesin. Baris configfile: pyproject.toml menegaskan bahwa pytest membaca konfigurasi dari berkas kita, bukan mencari konfigurasi lain. Empat belas titik pada baris tests/test_package.py mewakili empat belas test; satu titik berarti satu test lulus, dan ringkasan 14 passed menutup sesi.

Test suite ini akan kembali dipakai pada Bagian 4 — sebelum membangun wheel, kita jalankan dulu seluruh test untuk memastikan yang dikemas memang versi yang sehat. Berkas tests/test_package.py lengkapnya tersedia pada bagian Berkas Sumber di bawah halaman ini.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

tests/test_package.py

Unduh berkas
python
"""Test suite untuk seluruh function pada example_package."""

from example_package import hitung_kata, ringkas, slugify


class TestSlugify:
    def test_teks_biasa_dengan_tanda_baca(self):
        assert slugify("Belajar Python Dasar!") == "belajar-python-dasar"

    def test_spasi_berulang_dipadatkan(self):
        assert slugify("  Halo,   Dunia!  ") == "halo-dunia"

    def test_karakter_diakritik_dinormalisasi(self):
        assert slugify("Café Résumé") == "cafe-resume"

    def test_angka_dan_titik(self):
        assert slugify("Python 3.12") == "python-3-12"

    def test_teks_kosong(self):
        assert slugify("") == ""

    def test_tanpa_karakter_alfanumerik(self):
        assert slugify("!!! ???") == ""


class TestHitungKata:
    def test_teks_biasa(self):
        assert hitung_kata("Belajar Python sambil praktik") == 4

    def test_whitespace_campuran(self):
        assert hitung_kata("  spasi\ttab\ndua\n\nkata  ") == 4

    def test_teks_kosong(self):
        assert hitung_kata("") == 0


class TestRingkas:
    def test_teks_pendek_tidak_berubah(self):
        teks = "Belajar Python"
        assert ringkas(teks, 50) == teks

    def test_panjang_sama_dengan_teks(self):
        assert ringkas("Python", 6) == "Python"

    def test_teks_panjang_dipotong(self):
        teks = "Belajar Python di Course ini sampai tuntas"
        assert ringkas(teks, 20) == "Belajar Python di..."

    def test_akhir_kustom(self):
        teks = "Belajar Python di Course ini sampai tuntas"
        assert ringkas(teks, 25, akhir="…") == "Belajar Python di Course…"

    def test_panjang_lebih_kecil_dari_akhir(self):
        assert ringkas("abcdefghij", 2) == "ab"