Perlengkapan Pytest
Perlengkapan menyediakan konteks yang ditentukan dan dapat diandalkan untuk pengujian. Perlengkapan kustom Python fungsi yang dihiasi dengan @pytest.fixture. Anda menggunakannya untuk mengatur prasyarat seperti data pengujian, variabel lingkungan, koneksi database, atau file yang dibutuhkan pengujian sebelum dijalankan. Perlengkapan juga dapat menentukan langkah-langkah teardown yang membersihkan sumber daya setelah cakupan pengujian atau perlengkapan selesai.
Pengujian meminta perlengkapan dengan mendeklarasikan argumen dengan nama yang sama dengan perlengkapan. Pytest menemukan perlengkapan, menjalankannya, dan meneruskan nilai yang dikembalikan atau dihasilkan ke dalam pengujian. Untuk informasi selengkapnya tentang siklus hidup fikstur, lihat dokumentasi pytest untuk fikstur.
Perlengkapan sangat membantu saat diperlukan penyusunan atau pembersihan ulang, ketika kompleks, atau lebih jelas saat diberi nama. Untuk nilai yang bersifat sekali pakai dan sederhana, menyimpan kode penyiapan langsung dalam pengujian dapat lebih mudah dibaca.
Karakteristik utama dari perlengkapan pytest meliputi:
-
Kontrol cakupan: Fitur dapat menggunakan parameter
scopeuntuk mengontrol seberapa sering pytest membuat dan menghapus nilai fixture. Cakupan yang lebih luas dapat meningkatkan performa, tetapi juga berbagi status lebih lama. -
Penyiapan dan pembongkaran: Perlengkapan dapat menggunakan
yielduntuk pembersihan. Perangkat tingkat lanjut juga dapat menerima perangkat bawaanrequest(diperkenalkan nanti di unit ini) untuk memeriksa pengujian yang diminta dan memanggilrequest.addfinalizer()untuk mendaftarkan fungsi balik pembersihan. - Injeksi dependensi: Pengujian dan perlengkapan meminta perlengkapan sebagai argumen, membuat dependensi eksplisit.
- Kegunaan kembali dan modularitas: Perlengkapan dapat ditentukan sekali dan digunakan kembali di seluruh fungsi pengujian, modul, direktori, atau proyek.
-
Aktivasi otomatis: Fixture yang ditentukan dengan
@pytest.fixture(autouse=True)dijalankan pada setiap pengujian dalam cakupan ketersediaannya tanpa dicantumkan sebagai argumen. Gunakan opsi ini dengan hemat karena menyembunyikan dependensi pengujian.
Membuat pengaturan file sementara
Saat menulis pengujian yang berinteraksi dengan file, biasanya memerlukan jalur terisolasi yang tidak bertentangan dengan pengujian lain atau bergantung pada lokasi yang dikodekan secara permanen. Pytest menyediakan perlengkapan direktori sementara bawaan yang biasanya merupakan pilihan terbaik untuk pengujian baru karena pytest membuat direktori unik dan mengelola retensinya. Namun, membuat fixtur kustom kecil adalah cara yang berguna untuk memahami permintaan fixtur dan nilai pengembalian.
Fikstur berikut mengembalikan fungsi pabrik. Perlengkapan itu sendiri tidak membuat file selama penyiapan. Sebagai gantinya, fungsi berlapis create() membuat file saat pengujian memanggil tmp_file():
from pathlib import Path
import tempfile
import pytest
@pytest.fixture
def tmp_file():
def create(contents=""):
temp = tempfile.NamedTemporaryFile(delete=False)
try:
path = Path(temp.name)
finally:
temp.close()
path.write_text(contents, encoding="utf-8")
return path
return create
Fikstur menggunakan fungsi tempfile.NamedTemporaryFile() Python dengan delete=False sehingga file masih ada setelah ditutup. Handle file ditutup sebelum pengujian menerima path, yang penting ketika pengujian perlu membuka kembali atau menghapus file, terutama di Windows.
Berikut adalah contoh bagaimana pengujian mungkin menggunakan perlengkapan:
def test_file(tmp_file):
path = tmp_file("ready")
assert path.read_text(encoding="utf-8") == "ready"
Dalam pengaturan ini, tmp_file adalah nama fikstur, dan pengujian menerima fungsi create() yang dikembalikan oleh fikstur. Memanggil tmp_file() di dalam pengujian membuat file, menulis konten yang diminta, dan mengembalikan sebuah objek pathlib.Path.
Note
Berhati-hatilah saat menggabungkan pengaturan dengan pengelola konteks. Jika fixture mengembalikan jalur atau handle dari dalam blok with, manajer konteks mungkin menutup atau menghapus resource sebelum pengujian sempat menggunakannya. Tetap buka manajer konteks di seluruh yield saat pengujian membutuhkan sumber daya terkelola, atau tutup sumber daya dengan sengaja sebelum mengembalikan dan membersihkannya nanti.
Manajemen cakupan
Secara default, fixture pytest menggunakan function cakupan. Perlengkapan cakupan fungsi dibuat ketika pengujian memintanya dan dihancurkan di akhir pengujian tersebut. Jika fikstur menggunakan kode teardown, pytest menjalankan kode teardown tersebut di akhir cakupan fikstur.
Pytest juga memungkinkan cakupan perlengkapan yang lebih luas untuk mengoptimalkan pekerjaan penyiapan yang mahal. Nama cakupan perlengkapan bawaan adalah:
-
function: Cakupan bawaan. Perlengkapan dibuat sekali per fungsi pengujian. -
class: Perlengkapan dibuat sekali per kelas pengujian. -
module: Perlengkapan dibuat sekali untuk modul. -
package: Fixture dibuat sekali untuk paket tempat ia ditentukan dan dihapus setelah tes terakhir dalam paket tersebut, termasuk tes dalam subpaket dan subdirektori. -
session: Perlengkapan dibuat sekali untuk sesi pengujian.
Untuk kasus lanjutan, scope juga dapat menjadi fungsi yang mengembalikan salah satu nama cakupan ini. Pytest memanggil fungsi sekali saat mendefinisikan fikstur dan meneruskan argumen kata kunci fixture_name dan config. Baca dokumentasi pytest tentang cakupan dinamis untuk detailnya.
Dalam konteks ini, dibuat sekali berarti bahwa pytest menyimpan nilai fikstur untuk cakupan tersebut. Misalnya, sebuah fikstur dengan cakupan modul dapat diminta oleh beberapa pengujian dalam satu modul, tetapi pengujian tersebut menerima nilai cache fikstur yang sama. Fixtur parametrik adalah pengecualian: pytest dapat membuat nilai baru untuk setiap set parameter dalam cakupan.
Pastikan konfigurasi tetap dalam lingkup fungsi kecuali jika berbagi penyiapan aman dan berguna. Objek yang dapat diubah, koneksi terbuka, perubahan lingkungan, dan file dapat menimbulkan kebocoran keadaan antara pengujian jika di-cache dalam cakupan yang lebih luas.
Berikut tampilan perlengkapan tmp_file dengan cakupan modul:
from pathlib import Path
import tempfile
import pytest
@pytest.fixture(scope="module")
def tmp_file():
def create(contents=""):
temp = tempfile.NamedTemporaryFile(delete=False)
try:
path = Path(temp.name)
finally:
temp.close()
path.write_text(contents, encoding="utf-8")
return path
return create
Karena fikstur ini mengembalikan fungsi pembuat, scope="module" menyimpan fungsi pembuat dalam cache untuk modul. Pabrik masih membuat file baru setiap kali pengujian memanggil tmp_file(). Jika fixture dibuat dan langsung mengembalikan jalur file, maka setiap pengujian dalam modul akan menerima jalur yang sama.
Manajemen pembersihan
Versi tmp_file fikstur sebelumnya membuat file sementara, tetapi tidak menghapus file tersebut. Pytest merekomendasikan yield fixtures untuk pembersihan sederhana. Kode sebelum yield melakukan penyiapan dan memasok nilai ke pengujian. Kode setelah yield berjalan pada saat teardown, bahkan ketika tes gagal setelah penyiapan selesai. Jika suatu fixture mengeluarkan pengecualian sebelum mencapai yield, pytest tidak menjalankan kode teardown setelah itu yield, tetapi tetap menonaktifkan fixture sebelumnya yang telah selesai dengan sukses untuk pengujian yang sama.
Versi berikut melacak setiap file yang dibuat oleh pabrik dan mencoba menghapus file tersebut setelah pengujian selesai:
from pathlib import Path
import tempfile
import pytest
@pytest.fixture
def tmp_file():
paths = []
def create(contents=""):
temp = tempfile.NamedTemporaryFile(delete=False)
try:
path = Path(temp.name)
finally:
temp.close()
paths.append(path)
path.write_text(contents, encoding="utf-8")
return path
yield create
for path in paths:
try:
path.unlink()
except FileNotFoundError:
pass
Dengan cakupan default function , pytest menjalankan kode pembersihan setelah setiap pengujian yang meminta tmp_file. Jika Anda mengubah perlengkapan menjadi scope="module", pytest menjalankan pembersihan selama teardown modul, setelah pytest selesai menjalankan pengujian dalam modul tersebut. Ketika beberapa yield fixture aktif, pytest menjalankan kode teardown mereka dalam urutan yang berlawanan dari penyiapan.
Pembersihan menggunakan try/except FileNotFoundError karena pengujian mungkin menghapus file itu sendiri. Pada Windows, penghapusan juga dapat gagal dengan PermissionError jika pengujian membiarkan file terbuka, jadi pastikan pengujian menutup semua handle yang mereka buat. Anda juga dapat menggunakan request.addfinalizer() untuk pembersihan ketika Anda membutuhkan lebih banyak kontrol. Daftarkan finalizer hanya setelah sumber daya dibuat, karena pytest menjalankan finalizer tambahan selama teardown bahkan jika fikstur kemudian muncul. Untuk pembersihan langsung, yield perangkat biasanya lebih mudah dibaca.
Menggunakan conftest.py
Alih-alih menentukan perlengkapan di setiap file pengujian, Anda dapat menyimpan perlengkapan bersama dalam file bernama conftest.py. Fixtures dalam conftest.py secara otomatis tersedia untuk pengujian di direktori yang sama dan subdirektorinya tanpa harus diimpor secara eksplisit.
Anda dapat memiliki beberapa conftest.py file dalam rangkaian pengujian. Pytest menyelesaikan ketersediaan perlengkapan dari perspektif pengujian yang meminta: perlengkapan di kelas pengujian atau modul dipertimbangkan dalam cakupan lokal tersebut, lalu perlengkapan dari conftest.py file di direktori dan direktori induk yang sama. Pengujian dapat mencari perlengkapan ke atas, tetapi tidak mencari ke bawah ke direktori saudara atau anak. Perlengkapan plugin dicari setelah perlengkapan lokal. Jangan impor dari conftest.py dalam file pengujian; biarkan pytest menemukannya.
Menjelajahi perangkat bawaan
Pytest memiliki banyak perlengkapan bawaan yang dirancang untuk menyederhanakan pengujian. Perlengkapan ini menangani tugas penyiapan dan pembersihan umum sehingga Anda dapat fokus pada penulisan pernyataan pengujian. Untuk meninjau daftar lengkap, lihat referensi pytest untuk perlengkapan bawaan.
Perlengkapan bawaan utama meliputi:
-
cache: Menyimpan dan mengambil nilai di seluruh pengujian pytest. -
capsys: Menangkap teks yang ditulis kestdoutdanstderr. -
tmp_path: Menyediakanpathlib.Pathobjek untuk direktori sementara yang unik pada setiap pemanggilan fungsi pengujian, termasuk setiap kasus yang di-parameterisasi. -
tmpdir: Menyediakan direktori sementara sebagai objek warisanpy.path.local. Untuk pengujian baru, pytest merekomendasikantmp_path. -
monkeypatch: Memodifikasi sementara kelas, fungsi, kamus, variabel lingkungan, dan objek lainnya. -
request: Menyediakan informasi tentang pengujian atau perangkat uji yang sedang dijalankan dan mendukung finalizer.
Perlengkapan bawaan lain yang umum digunakan termasuk caplog (mengambil rekaman log), capfd (menangkap output tingkat deskriptor file), tmp_path_factory (membuat direktori sementara yang dilingkup sesi), dan pytestconfig (mengekspos konfigurasi pytest aktif). Untuk daftar lengkapnya, lihat referensi pytest untuk perlengkapan bawaan.
Latihan berikutnya menggunakan tmp_path dan pathlib.Path untuk membuat file sementara dalam direktori untuk setiap pengujian. Anda mungkin mengalami tmpdir dalam proyek pytest yang ada, tetapi untuk kode baru, lebih suka tmp_path. Secara default, pytest menyimpan folder sementara dari beberapa eksekusi pengujian terakhir, jadi melihat folder sementara yang baru dikelola pytest setelah dijalankan adalah hal yang diharapkan. Untuk informasi selengkapnya, lihat dokumentasi pytest untuk direktori dan file sementara.
Peran monkeypatching dalam pengujian
Perangkat sebelumnya dibuat dan digunakan untuk membersihkan sumber daya pengujian. Penggunaan perlengkapan umum lainnya adalah modifikasi status sementara dalam pengujian yang perlu mengisolasi kode dari status eksternal. Untuk kasus-kasus tersebut, pytest menyediakan perlengkapan bawaan monkeypatch , yang merupakan perlengkapan pytest dan contoh yang baik dari teardown otomatis.
Menguji kode yang bergantung pada sumber daya eksternal, konfigurasi global, atau status sistem operasi bisa menjadi tantangan. Fikstur monkeypatch memungkinkan pengubahan sementara atribut, nilai kamus, variabel lingkungan, direktori kerja saat ini, atau sys.path untuk pengujian. Pytest secara otomatis membatalkan perubahan tersebut setelah fungsi pengujian atau perlengkapan selesai.
Berikut adalah contoh yang menggantikan atribut fungsi pada modul proyek kecil untuk satu pengujian. Patching kode aplikasi dengan cakupan sempit (alih-alih fungsi pustaka standar yang digunakan oleh pytest itu sendiri) menjaga pengujian tetap fokus dan menghindari kesalahan yang tidak disengaja pada perilaku pytest sendiri.
# project_module.py
def get_user_id():
# Imagine this calls a remote service.
return "real-user"
# test_project.py
import project_module
def test_user_id(monkeypatch):
monkeypatch.setattr(project_module, "get_user_id", lambda: "test-user")
assert project_module.get_user_id() == "test-user"
Anda juga dapat menentukan target sebagai string jalur impor bertitik. Pytest mengatasi dan mengimpor modul yang telah diberi nama saat menerapkan patch, yang memudahkan ketika file pengujian belum memerlukan modul tersebut.
import project_module Kemudian dalam contoh ini hanya agar pengujian dapat mereferensikan fungsi yang di-patch untuk pernyataan:
# test_project.py
def test_user_id_by_path(monkeypatch):
monkeypatch.setattr("project_module.get_user_id", lambda: "test-user")
import project_module
assert project_module.get_user_id() == "test-user"
Selain mengatur atribut dan mengambil alih metode dengan setattr(), monkeypatch perlengkapan dapat menghapus atribut dengan delattr(), mengatur dan menghapus variabel lingkungan (setenv, delenv), mengatur dan menghapus item kamus (setitem, delitem), prepend ke sys.path (syspath_prepend), mengubah direktori kerja saat ini (chdir), dan membuat cakupan patching berlapis dengan context(). Perilaku default raising=True untuk setattr() dan delattr() membantu mendeteksi target yang salah eja. Gunakan monkeypatch.context() saat patch harus diurungkan sebelum pengujian atau pemecahan masalah berlanjut. Untuk informasi selengkapnya, lihat dokumentasi pytest untuk monkeypatching.
Meskipun monkeypatch kuat, gunakan dengan hati-hati:
-
Kejelasan dan pemeliharaan kode: Penggunaan
monkeypatchberlebihan atau menggunakannya dengan cara yang kompleks dapat membuat pengujian lebih sulit dipahami dan dipertahankan. - Validitas pengujian: Monkeypatching dapat membuat pengujian yang lulus dalam kondisi buatan yang sangat berbeda dari perilaku produksi.
- Overdependence pada detail implementasi: Pengujian yang mengubah detail implementasi dapat menjadi rapuh ketika kode yang mendasari berubah.
-
Kompleksitas pemecahan masalah: Pengujian yang menggunakan
monkeypatchbisa lebih sulit ketika patch mengubah perilaku mendasar aplikasi.
Gunakan monkeypatch saat membantu mengisolasi perilaku yang berfokus, dan menjaga setiap patch sesedikit mungkin dan eksplisit.