Latihan

Selesai

Dalam latihan ini, Anda akan menggunakan pytest dengan @pytest.mark.parametrize untuk menguji fungsi, termasuk kasus yang mengharapkan pengecualian. Kemudian, Anda akan merefaktor pengujian berbasis kelas dengan pendekatan gaya xUnit menggunakan setup_method() dan teardown_method() ke suatu fixture. Menggunakan parametrisasi dan konfigurasi awal membantu Anda menulis pengujian yang mencakup lebih banyak kasus dengan lebih sedikit pengulangan dan penyiapan yang lebih jelas.

Untuk bantuan penyiapan, lihat dokumentasi Python untuk lingkungan virtual, dokumentasi pip untuk pip install, dan dokumentasi pytest untuk menginstal pytest. Untuk detail selengkapnya tentang fitur pytest dalam latihan ini, lihat dokumentasi pytest untuk fungsi pengujian parametrizing, pernyataan tentang pengecualian yang diharapkan, perlengkapan, penyiapan gaya xUnit, dan direktori dan file sementara.

Sebelum Anda mulai

Gunakan Python 3.10 atau yang lebih baru, seperti yang dijelaskan dalam prasyarat modul. Jika Anda sudah memiliki lingkungan virtual dengan pytest yang diinstal dan diaktifkan, lanjutkan ke Langkah 1. Jika tidak, buat dan aktifkan lingkungan virtual di folder tempat Anda akan membuat test_advanced.py.

Pada Windows PowerShell:

python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install pytest
python -m pytest --version

Di macOS, Linux, atau Subsistem Windows untuk Linux:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install pytest
python -m pytest --version

Perintah versi harus mencetak versi pytest, mirip dengan output ini:

pytest x.y.z

Perintah instal tidak menyematkan pytest ke versi tertentu. Pip menggunakan metadata paket untuk memilih rilis pytest stabil yang kompatibel dengan versi Python di lingkungan virtual.

Gunakan python -m pytest selama latihan ini agar pytest berjalan dari lingkungan aktif. Jika Windows PowerShell memblokir Activate.ps1, lihat dokumentasi Python venv untuk opsi kebijakan eksekusi PowerShell, atau jalankan Python lingkungan virtual secara langsung untuk perintah pip dan pytest. Contohnya:

.\.venv\Scripts\python -m pip install --upgrade pip
.\.venv\Scripts\python -m pip install pytest
.\.venv\Scripts\python -m pytest -v test_advanced.py

Langkah 1 - Tambahkan file dengan pengujian untuk latihan ini

  1. Di folder yang sama tempat Anda akan menjalankan pytest, buat file pengujian baru bernama test_advanced.py. Jangan letakkan file di dalam folder .venv . Tambahkan kode berikut:

    import pytest
    
    
    def str_to_bool(string):
        normalized = string.lower()
        if normalized in ["yes", "y", "1"]:
            return True
        if normalized in ["no", "n", "0"]:
            return False
        raise ValueError(f"Cannot convert {string!r} to a boolean")
    

    Fungsi str_to_bool() menerima string sebagai input. Ini mengembalikan True untuk nilai true yang dikenali, mengembalikan False untuk nilai false yang dikenali, dan menaikkan ValueError untuk nilai lainnya.

  2. Di file yang sama, tambahkan pengujian untuk fungsi str_to_bool(). Gunakan @pytest.mark.parametrize untuk menguji nilai benar terlebih dahulu:

    @pytest.mark.parametrize("string", ["Y", "y", "1", "YES"])
    def test_str_to_bool_true(string):
        assert str_to_bool(string) is True
    
  3. Selanjutnya, tambahkan pengujian lain dengan nilai false:

    @pytest.mark.parametrize("string", ["N", "n", "0", "NO"])
    def test_str_to_bool_false(string):
        assert str_to_bool(string) is False
    

    Sekarang ada dua fungsi pengujian yang mencakup input representatif untuk kedua nilai pengembalian True dan False.

  4. Terakhir, tambahkan pengujian parametris untuk nilai yang tidak boleh diterima fungsi. Gunakan pytest.raises() sebagai manajer konteks sehingga pengujian hanya lolos saat str_to_bool() menaikkan pengecualian yang diharapkan:

    @pytest.mark.parametrize("string", ["maybe", "2"])
    def test_str_to_bool_invalid(string):
        with pytest.raises(ValueError, match="Cannot convert"):
            str_to_bool(string)
    

    Argumen match memeriksa pesan pengecualian dengan ekspresi reguler. Di sini, pengujian hanya memeriksa bagian stabil pesan sehingga tidak bergantung pada nilai yang persis tidak valid.

Catatan

Untuk kesederhanaan, latihan ini menyimpan kode yang sedang diuji dan tes dalam file yang sama. Dalam proyek Python nyata, kode aplikasi dan pengujian biasanya dipisahkan menjadi file dan direktori yang berbeda, seperti paket src/ dan direktori tests/.

Langkah 2 - Jalankan pengujian dan jelajahi laporan

Setelah Anda menambahkan pengujian, jalankan pytest dan periksa output. Gunakan bendera verbositas yang ditingkatkan (-v) sehingga Anda dapat melihat setiap nilai input yang diperlakukan sebagai pengujian terpisah.

Pada Windows PowerShell:

python -m pytest -v test_advanced.py

Di macOS, Linux, atau Subsistem Windows untuk Linux:

python -m pytest -v test_advanced.py

Output harus mirip dengan laporan berikut:

============================= test session starts ==============================
platform ... -- Python 3.x.y, pytest-x.y.z, pluggy-x.y.z -- ...
rootdir: ...
...
collected 10 items

test_advanced.py::test_str_to_bool_true[Y] PASSED                        [ 10%]
test_advanced.py::test_str_to_bool_true[y] PASSED                        [ 20%]
test_advanced.py::test_str_to_bool_true[1] PASSED                        [ 30%]
test_advanced.py::test_str_to_bool_true[YES] PASSED                      [ 40%]
test_advanced.py::test_str_to_bool_false[N] PASSED                       [ 50%]
test_advanced.py::test_str_to_bool_false[n] PASSED                       [ 60%]
test_advanced.py::test_str_to_bool_false[0] PASSED                       [ 70%]
test_advanced.py::test_str_to_bool_false[NO] PASSED                      [ 80%]
test_advanced.py::test_str_to_bool_invalid[maybe] PASSED                 [ 90%]
test_advanced.py::test_str_to_bool_invalid[2] PASSED                     [100%]

============================== 10 passed in 0.01s ==============================

Sistem operasi Anda, versi Python, versi pytest, jalur yang dapat dieksekusi, direktori akar, baris plugin dan cache, pemformatan kemajuan koleksi, pembungkusan baris, dan waktu proses mungkin berbeda. Meskipun Anda hanya menulis tiga fungsi pengujian, pytest mengumpulkan 10 pengujian karena setiap nilai input parametris berjalan sebagai kasus pengujiannya sendiri.

Langkah 3 - Refactor setup dan teardown menjadi sebuah fixture

  1. Tambahkan pengujian berbasis kelas ke file test_advanced.py . Pengujian ini menggunakan metode xUnit dengan gaya setup_method() dan teardown_method() untuk membuat dan menghapus file dalam setiap metode pengujian.

    from pathlib import Path
    
    
    class TestFile:
    
        def setup_method(self):
            self.path = Path("done")
            self.path.write_text("1", encoding="utf-8")
    
        def teardown_method(self):
            self.path.unlink(missing_ok=True)
    
        def test_done_file(self):
            contents = self.path.read_text(encoding="utf-8")
            assert contents == "1"
    

    Pengujian ini dapat lulus, tetapi menulis nama file tetap di direktori kerja saat ini dan memerlukan kode pembersihan terpisah. Jika pengujian terganggu atau dijalankan bersamaan, seperti dengan pytest-xdist atau tumpang tindih pengujian lokal, pendekatan ini dapat meninggalkan file usang, menimpa file yang sudah ada dengan nama selesai, atau menyebabkan pengujian saling memengaruhi satu sama lain.

  2. Tambahkan fixture tingkat modul yang menggunakan fixture bawaan tmp_path untuk membuat file sementara. Pada langkah berikutnya, Anda akan memperbarui kelas untuk menggunakan fixture ini alih-alih metode penyiapan dan pembongkaran. Untuk keterbacaan, letakkan perlengkapan tingkat modul ini setelah pengujian parametris str_to_bool dan sebelum kelas TestFile. Pytest menemukan fixture tingkat modul tanpa mempedulikan di mana fixture tersebut muncul dalam modul, sehingga posisinya ditujukan untuk pembaca manusia, bukan pytest.

    @pytest.fixture
    def tmp_file(tmp_path):
        def write():
            path = tmp_path / "done"
            path.write_text("1", encoding="utf-8")
            return path
        return write
    

    Fitur tmp_file() ini menggunakan fitur pytest tmp_path yang menyediakan direktori unik sementara pathlib.Path untuk setiap pemanggilan fungsi pengujian, termasuk setiap kasus yang diparameterkan. Pytest mengelola direktori sementara, sehingga pengujian tidak bergantung pada jalur file yang dikodekan secara permanen.

  3. Hapus kelas sebelumnya TestFile, termasuk metode setup_method() dan teardown_method(), dan ganti dengan versi akhir ini yang menggunakan perkakas alih-alih metode penyiapan dan teardown.

    class TestFile:
    
        def test_done_file(self, tmp_file):
            path = tmp_file()
            contents = path.read_text(encoding="utf-8")
            assert contents == "1"
    

    Anda juga dapat menghapus from pathlib import Path impor yang Anda tambahkan pada langkah sebelumnya, karena pengujian tidak lagi membuat objek Path secara langsung.

Periksa pekerjaan Anda

Sekarang Anda harus memiliki file Python bernama test_advanced.py yang berisi:

  • Fungsi str_to_bool() yang menerima string dan mengembalikan nilai boolean untuk nilai true dan false yang dikenali.
  • Tiga pengujian parametrik untuk fungsi str_to_bool(): satu yang menguji nilai True, satu yang menguji nilai False, dan yang memeriksa nilai tidak valid yang memicu ValueError.
  • Fixture pytest kustom yang menggunakan fixture bawaan tmp_path untuk membuat file sementara done dengan beberapa konten.
  • Satu TestFile kelas dengan satu metode yang menggunakan fixture kustom tmp_file untuk membuat dan membaca file.
  • Tidak ada from pathlib import Path metode impor dan tidak ada setup_method()/teardown_method() dalam versi akhir.

File akhir akan terlihat mirip dengan yang berikut ini:

import pytest


def str_to_bool(string):
    normalized = string.lower()
    if normalized in ["yes", "y", "1"]:
        return True
    if normalized in ["no", "n", "0"]:
        return False
    raise ValueError(f"Cannot convert {string!r} to a boolean")


@pytest.mark.parametrize("string", ["Y", "y", "1", "YES"])
def test_str_to_bool_true(string):
    assert str_to_bool(string) is True


@pytest.mark.parametrize("string", ["N", "n", "0", "NO"])
def test_str_to_bool_false(string):
    assert str_to_bool(string) is False


@pytest.mark.parametrize("string", ["maybe", "2"])
def test_str_to_bool_invalid(string):
    with pytest.raises(ValueError, match="Cannot convert"):
        str_to_bool(string)


@pytest.fixture
def tmp_file(tmp_path):
    def write():
        path = tmp_path / "done"
        path.write_text("1", encoding="utf-8")
        return path
    return write


class TestFile:

    def test_done_file(self, tmp_file):
        path = tmp_file()
        contents = path.read_text(encoding="utf-8")
        assert contents == "1"

Jalankan file pengujian lagi.

Pada Windows PowerShell:

python -m pytest -v test_advanced.py

Di macOS, Linux, atau Subsistem Windows untuk Linux:

python -m pytest -v test_advanced.py

Laporan akhir harus menunjukkan 11 tes yang dikumpulkan: 10 kasus dari tes parametris dan satu pengujian berbasis kelas.

============================= test session starts ==============================
platform ... -- Python 3.x.y, pytest-x.y.z, pluggy-x.y.z -- ...
rootdir: ...
...
collected 11 items

test_advanced.py::test_str_to_bool_true[Y] PASSED                        [  9%]
test_advanced.py::test_str_to_bool_true[y] PASSED                        [ 18%]
test_advanced.py::test_str_to_bool_true[1] PASSED                        [ 27%]
test_advanced.py::test_str_to_bool_true[YES] PASSED                      [ 36%]
test_advanced.py::test_str_to_bool_false[N] PASSED                       [ 45%]
test_advanced.py::test_str_to_bool_false[n] PASSED                       [ 54%]
test_advanced.py::test_str_to_bool_false[0] PASSED                       [ 63%]
test_advanced.py::test_str_to_bool_false[NO] PASSED                      [ 72%]
test_advanced.py::test_str_to_bool_invalid[maybe] PASSED                 [ 81%]
test_advanced.py::test_str_to_bool_invalid[2] PASSED                     [ 90%]
test_advanced.py::TestFile::test_done_file PASSED                        [100%]

============================== 11 passed in 0.01s ==============================

Semua pengujian harus lulus tanpa kesalahan.