Mengejek dengan Python: unittest.mock, patch dan Kesalahan yang Membuat Tes Berbohong
Mocks memungkinkan Anda menguji kode tanpa jaringan, database, atau jam nyata. Jika digunakan sembarangan, mereka juga membiarkan kode yang rusak lewat. Berikut adalah cara menggunakan unittest.mock dengan baik, dengan setiap contoh dijalankan.
- mock mewakili objek nyata, seperti klien API, sehingga pengujian berjalan cepat dan offline.
- Gunakan
return_valueuntuk jawaban tetap danside_effectuntuk kesalahan atau urutan. - Tambal nama di tempat penggunaannya, bukan di tempat yang ditentukan. Ini adalah bug yang paling umum.
- Gunakan
specatauautospecagar kesalahan ketik dan argumen yang salah gagal, bukan diteruskan secara diam-diam.
Fungsi Anda memanggil API cuaca. Pengujian Anda tidak boleh bergantung pada internet, kunci API, atau cuaca besok. unittest.mock bawaan Python memungkinkan Anda mengganti panggilan itu dengan pengganti yang mengembalikan apa yang Anda pilih.
Kami menulis modul weather kecil dan sembilan pengujian, semuanya dijalankan dengan unittest Python 3.13. Kesembilannya lolos, dan hasil cetakan di bawah ini berasal dari proses tersebut.
Mengapa mengejek?
- Kecepatan: tidak ada panggilan jaringan atau pengaturan basis data.
- Keandalan: pengujian tidak gagal karena layanan pihak ketiga tidak berfungsi.
- Kontrol: Anda dapat menyimulasikan batas waktu, kesalahan, dan data ganjil sesuai permintaan.
- Isolasi: Anda menguji logika Anda, bukan server orang lain.
Mock sebagian besar termasuk dalam pengujian unit. Tes integrasi harus tetap berbicara tentang ketergantungan yang nyata atau realistis.
Dasar-dasar tiruan
from unittest.mock import Mock
api = Mock()
api.get_price.return_value = 499
print(api.get_price("pen")) # 499
api.get_price.assert_called_once_with("pen")
print(api.get_price.call_args_list) # [call('pen')]
print(api.anything_at_all()) # <Mock name='mock.anything_at_all()' ...>
A Mock mencatat setiap panggilan, sehingga Anda dapat memeriksa cara penggunaannya. Namun perhatikan baris terakhir. Mock biasa menciptakan atribut apa pun yang Anda minta. Fleksibilitas itu nyaman, dan juga berbahaya, seperti yang akan kita lihat.
MagicMock adalah ide yang sama tetapi juga mendukung metode “ajaib” seperti __len__ dan __iter__. patch() menggunakan MagicMock secara default.
side_effect: kesalahan dan urutan
side_effect mengontrol apa yang terjadi pada setiap panggilan. Setel ke pengecualian untuk menyimulasikan kegagalan:
api.fetch.side_effect = ConnectionError("down")
api.fetch() # raises ConnectionError
Atur ke daftar untuk memberikan hasil berbeda pada panggilan berturut-turut. Ini sempurna untuk menguji logika coba lagi:
def fetch_with_retry(api, attempts=3):
for _ in range(attempts):
try:
return api.fetch()
except ConnectionError:
continue
raise ConnectionError("all attempts failed")
api = Mock()
api.fetch.side_effect = [ConnectionError("busy"), {"ok": True}]
print(fetch_with_retry(api)) # {'ok': True}
print(api.fetch.call_count) # 2
Panggilan pertama gagal, panggilan kedua berhasil, dan pengujian membuktikan bahwa fungsi tersebut dicoba ulang tepat satu kali. Mensimulasikan hal ini terhadap layanan nyata hampir mustahil.
patch(): ganti sesuatu untuk sementara
Seringkali ketergantungan tidak diteruskan. Ketergantungan diimpor ke dalam modul. patch() menukarnya selama durasi pengujian dan memulihkannya setelahnya:
# weather.py
from http_client import get as requests_get
def report(city):
data = requests_get(f"https://api.example.com/weather?city={city}")
return f"{city}: {data['temp_c']}°C"
from unittest.mock import patch
with patch("weather.requests_get") as fake_get:
fake_get.return_value = {"temp_c": 31}
print(weather.report("Pune")) # Pune: 31°C
fake_get.assert_called_once_with(
"https://api.example.com/weather?city=Pune")
Anda dapat menggunakan patch sebagai pengelola konteks seperti ini, atau sebagai dekorator, yang meneruskan tiruan sebagai argumen.
Tambal di tempat yang digunakan, bukan di tempat yang ditentukan
Ini adalah kesalahan yang pernah dilakukan hampir semua orang. Fungsi get yang sebenarnya ada di http_client, jadi rasanya wajar untuk menambalnya di sana:
with patch("http_client.get") as fake_get:
fake_get.return_value = {"temp_c": 99}
weather.report("Pune")
# ConnectionError: no network in tests: https://api.example.com/weather?city=Pune
Fungsi sebenarnya berjalan. Ketika weather.py melakukan from http_client import get as requests_get, ia membuat namanya sendiri yang menunjuk pada fungsi aslinya. Menambal http_client.get mengubah nama yang berbeda, jadi weather tidak pernah melihat tiruannya. http_client.get kami menaikkannya dengan sengaja, jadi kesalahannya besar. Dalam proyek nyata, ini mungkin secara diam-diam memanggil API langsung.
Aturan: tempelkan nama pada modul yang diuji, ditulis sebagai "module_under_test.name". Jika modul melakukan import http_client dan memanggil http_client.get(), maka patching "http_client.get" sudah benar, karena pencarian terjadi pada waktu panggilan.
spec dan autospec: membuat tiruan menjadi ketat
Mock biasa menerima apa pun, termasuk kesalahan ketik. Dengan spesifikasi, ini hanya mengizinkan atribut yang dimiliki kelas sebenarnya:
api = Mock(spec=weather.WeatherClient)
api.get_temprature("Pune")
# AttributeError: Mock object has no attribute 'get_temprature'
loose = Mock()
loose.get_temprature("Pune") # returns a Mock, no error at all
Tiruan longgar dengan senang hati menerima metode salah eja, sehingga pengujian yang menggunakannya dapat lulus meskipun kode sebenarnya akan mogok. autospec melangkah lebih jauh dan memeriksa tanda tangan metode:
with patch("weather.WeatherClient", autospec=True) as Fake:
client = Fake.return_value
client.get_temperature("Pune", "extra", "args")
# TypeError: too many positional arguments
Pilih autospec=True atau create_autospec() saat mengejek kelas Anda sendiri. Itu membuat tiruan tetap jujur saat kode sebenarnya berubah.
Mengejek tanggal dan waktu
Kode yang bergantung pada tanggal hari ini sulit untuk diuji. Bungkus jam dalam fungsi kecil dan tempelkan itu:
def today():
return datetime.date.today()
def is_weekend():
return today().weekday() >= 5
@patch("weather.today")
def test_date(self, fake_today):
fake_today.return_value = datetime.date(2026, 11, 7)
print(weather.is_weekend()) # True (a Saturday)
Anda tidak dapat dengan mudah menambal datetime.date.today secara langsung, karena tipe bawaan tidak mengizinkan atributnya disetel. Perpustakaan seperti freezegun dan mesin waktu menangani hal ini dengan lebih lengkap.
Pernyataan yang mengolok-olok
| Metode | Cek |
|---|---|
assert_called() |
Ditelepon setidaknya sekali |
assert_called_once() |
Dipanggil tepat satu kali |
assert_called_with(...) |
Panggilan terakhir menggunakan argumen ini |
assert_called_once_with(...) |
Tepat satu panggilan, dengan argumen berikut |
assert_any_call(...) |
Setiap panggilan menggunakan argumen ini |
assert_not_called() |
Tidak pernah menelepon |
Ketika suatu pernyataan gagal, pesannya jelas. Kami memanggil assert_called_once() metode yang tidak pernah berjalan:
AssertionError: Expected 'save' to have been called once. Called 0 times.
Versi Python yang lebih lama membiarkan metode pernyataan yang salah eja, seperti assret_called_once, lolos secara diam-diam. Versi modern memunculkan kesalahan untuk nama yang dimulai dengan assert atau kesalahan ejaan umum, namun spesifikasi masih menjadi pelindung yang lebih kuat.
pytest dan mengolok-olok
Segala sesuatu di atas juga berfungsi di pytest. Banyak pengguna pytest juga menggunakan perlengkapan monkeypatch bawaan untuk penggantian sederhana, atau plugin pytest-mock, yang menyediakan perlengkapan mocker yang membatalkan patch secara otomatis. Aturan “tambalan di tempat digunakan” berlaku untuk semuanya. Lihat tutorial pytest kami untuk perlengkapan dan pengaturan.
Kapan tidak mengejek
- Jangan mengejek apa yang Anda uji. Jika tiruan berhasil, pengujian tidak membuktikan apa pun.
- Hindari mengejek objek bernilai sederhana. Gunakan yang asli.
- Jangan mengejek semuanya. Pengujian yang penuh dengan tiruan sering kali rusak setiap kali kode difaktorkan ulang, bahkan ketika perilakunya benar.
- Pertahankan beberapa pengujian integrasi nyata. Mock mengkodekan asumsi Anda tentang suatu layanan, dan asumsi tersebut bisa saja salah.
Mock juga memperluas cakupan kode dengan mudah, karena jalur tiruan berjalan tanpa perilaku nyata apa pun di baliknya.
Kesalahan umum yang mengejek
- Menambal nama yang salah. Telah dibahas di atas, dan masih menjadi penyebab nomor satu “tiruan saya diabaikan”.
- Lupa menyetel return_value. Kode yang diuji kemudian menerima objek Mock alih-alih data. Ini mungkin langsung melewati fungsi Anda dan membuat pengujian lolos secara tidak sengaja, misalnya saat Anda hanya memeriksa apakah hasilnya benar.
- Menegaskan tiruan alih-alih hasilnya. Memeriksa apakah
save()dipanggil lebih lemah daripada memeriksa apa yang disimpan. Gunakanassert_called_once_with(...)dengan argumen nyata, atau lebih baik, tegaskan hasil fungsi. - Tambalan bocor. Memanggil
patcher.start()tanpastop()akan meninggalkan tambalan palsu untuk pengujian selanjutnya. Gunakan pengelola konteks, dekorator, atauaddCleanup(patcher.stop). - Dekorator bertumpuk dalam urutan yang salah. Dengan beberapa
@patchdekorator, dekorator terbawah diteruskan sebagai argumen pertama. Mencampurnya secara diam-diam akan mengkonfigurasi tiruan yang salah. - Mengejek perpustakaan yang bukan milik Anda di banyak pengujian. Bungkus itu dalam kelas kecil Anda sendiri dan tirulah itu. Pengujian Anda kemudian menjelaskan kebutuhan Anda, bukan API perpustakaan.
Ejekan, bertopik, dan palsu
Kata-kata ini sering digunakan secara longgar, namun perbedaannya membantu ketika membahas tes. A stub baru saja mengembalikan jawaban terekam. mock juga mencatat panggilan sehingga Anda dapat memverifikasi cara penggunaannya. palsu adalah implementasi yang berfungsi dan disederhanakan, seperti repositori dalam memori, bukan database. Mock Python dapat bertindak sebagai rintisan atau tiruan. Kelas palsu adalah kelas biasa yang Anda tulis sendiri, dan sering kali kelas tersebut menghasilkan pengujian yang lebih tahan terhadap pemfaktoran ulang, karena kelas tersebut memeriksa perilaku dibandingkan panggilan persisnya.
Aturan praktis yang baik adalah mengejek bagian tepi sistem Anda: jaringan, jam, nomor acak, sistem file, dan layanan pihak ketiga berbayar. Di dalam tepian itu, lebih suka benda nyata. Pengujian yang dibuat dengan cara ini tetap cepat, dan pengujian tersebut tetap menggunakan kode yang sebenarnya Anda tulis.
Penutup
Mocking adalah alat yang tepat: ganti dependensi yang lambat atau tidak dapat diprediksi, kendalikan apa yang dikembalikannya, dan periksa bagaimana kode Anda menggunakannya. Dua kebiasaan mencegah sebagian besar masalah. Tambal nama dalam modul yang sedang diuji, dan gunakan autospec sehingga panggilan penolakan tiruan Anda akan ditolak oleh objek sebenarnya.
Pertanyaan yang sering diajukan
Apa yang mengejek dengan Python?
Mocking menggantikan objek nyata, seperti klien API atau database, dengan objek palsu yang Anda kendalikan selama pengujian. Python memasukkan ini ke dalam modul unittest.mock.
Apa perbedaan antara Mock dan MagicMock?
MagicMock adalah Mock yang juga mendukung metode ajaib seperti __len__, __iter__ dan __enter__. patch() membuat MagicMock secara default.
Mengapa tambalan saya tidak berfungsi?
Biasanya karena Anda menambal tempat objek didefinisikan, bukan tempat penggunaannya. Tambal nama di dalam modul yang sedang diuji.
Apa perbedaan antara return_value dan side_effect?
return_value menetapkan hasil tetap. side_effect dapat memunculkan pengecualian, mengembalikan nilai dari daftar secara berurutan, atau memanggil fungsi.
Apa yang dilakukan spesifikasi otomatis?
Itu membuat tiruan dari objek sebenarnya, sehingga atribut yang hilang dan argumen yang salah menimbulkan kesalahan, sama seperti yang terjadi pada kode sebenarnya.
Teruslah belajar
Butuh bantuan dengan pengujian, WordPress atau SEO?
Kami membantu tim mengirimkan perangkat lunak yang lebih baik dan mengembangkan situs mereka. Beritahu kami apa yang sedang Anda kerjakan dan kami akan menyarankan langkah selanjutnya yang jelas.
Panduan lebih lanjut tentang pengujian, AI & pengembangan modern
Menikmati artikel ini? Pilih topik dan langsung masuk ke tutorial terbaru kami.
Penjelasan Halusinasi AI: Mengapa Model Bahasa Memperbaiki Masalah dan Cara Menguranginya
Halusinasi AI menjelaskan: apa itu, mengapa model bahasa memproduksinya, tipe umum, cara menguranginya dengan landasan dan petunjuk, dan pemeriksaan…
Baca artikel →DevOpsPenjelasan Variabel Lingkungan: File .env, Rahasia, dan Konfigurasi Dilakukan dengan Benar
Variabel lingkungan dijelaskan dengan contoh Python yang kami jalankan: membaca dan mengaturnya, mengapa setiap nilai berupa string, file .env, pewa…
Baca artikel →Pengembangan perangkat lunakPenjelasan Pembatasan Kecepatan API: Algoritma, Header, dan Penanganan 429 Kesalahan
Pembatasan laju API dijelaskan dengan simulasi yang kami jalankan: keranjang token, jendela tetap, jendela geser, respons 429, header batas laju, pe…
Baca artikel →Otomatisasi pengujianMengejek dengan Python: unittest.mock, patch dan Kesalahan yang Membuat Tes Berbohong
Mengejek dengan Python dengan unittest.mock, menguji: Mock dan return_value, side_effect, menambal di mana nama digunakan, spesifikasi dan autospec,…
Baca artikel →Pengujian perangkat lunakPenjelasan Cakupan Kode: Cakupan Jalur vs Cabang dan Apa Arti 100% Sebenarnya
Cakupan kode dijelaskan dengan coverage.py yang sebenarnya dijalankan: cakupan garis, cabang dan kondisi, membaca laporan, mengapa cakupan 100% masi…
Baca artikel →Basis data & SQLDELETE vs TRUNCATE vs DROP dalam SQL: Perbedaan, Rollback dan Kapan Menggunakan Masing-Masing
DELETE vs TRUNCATE vs DROP dijelaskan dengan SQL yang diuji: apa yang dihapus, rollback, pemicu, reset identitas, kecepatan, dan perbedaan PostgreSQ…
Baca artikel →Basis data & SQLPenjelasan Tampilan SQL: Membuat, Menggunakan, dan Menghindari Kesalahan, Dengan Contoh
Tampilan SQL dijelaskan dengan kueri yang kami jalankan: membuat tampilan, hasil langsung, menyembunyikan kolom, kinerja, tampilan yang dapat diperb…
Baca artikel →Pengembangan perangkat lunakAntarmuka TypeScript vs Tipe: Perbedaan Nyata, Diuji Dengan Kompiler
Antarmuka TypeScript vs tipe, diperiksa dengan kompiler sebenarnya: apa yang bisa dilakukan keduanya, apa yang hanya bisa dilakukan satu orang, peng…
Baca artikel →Pengembangan perangkat lunakPenjelasan Python *args dan **kwargs: Fungsi Fleksibel Dengan Contoh Jelas
Python *args dan **kwargs dijelaskan dengan kode yang kami jalankan: pengepakan dan pembongkaran, urutan parameter, argumen hanya kata kunci dan pos…
Baca artikel →Pengembangan perangkat lunakFungsi Python Lambda: Sintaks, Kegunaan Sebenarnya, dan Kapan Tidak Digunakan
Fungsi python lambda dijelaskan dengan kode yang kami jalankan: sintaksis, pengurutan dengan kunci, peta dan filter, penutupan, jebakan pengikatan a…
Baca artikel →Kecerdasan buatanApa Itu Agen AI? Cara Kerjanya, Dengan Agent Loop Sederhana yang Dapat Anda Jalankan
Apa itu agen AI? Panduan sederhana tentang loop agen, alat, memori, dan pagar pembatas, dengan loop Python kecil yang kami jalankan, risiko nyata da…
Baca artikel →DevOpsPemantauan vs Observabilitas: Perbedaan Nyata, Dengan Log, Metrik, dan Jejak
Penjelasan pemantauan vs observabilitas: kegagalan yang diketahui dan tidak diketahui, log, metrik dan jejak, penelusuran insiden checkout, sinyal e…
Baca artikel →DevOpsVolume Docker vs Bind Mounts: Pertahankan Data Kontainer dengan Cara yang Benar
Volume Docker vs pengikatan pengikat dijelaskan: mengapa data kontainer menghilang, volume yang diberi nama, pengikatan pengikatan, tmpfs, contoh Tu…
Baca artikel →Otomatisasi pengujianTDD vs BDD: Perbedaan Utama, Satu Fitur Dibangun dengan Dua Cara dan Kapan Menggunakan Masing-masing
TDD vs BDD dijelaskan dengan satu fitur diskon yang dibuat dengan dua cara: refactor merah-hijau dengan kode yang kami jalankan, skenario Mengingat-…
Baca artikel →Pengembangan perangkat lunakGit merge vs rebase: perbedaan, contoh, dan kapan memakai masing-masing
Git merge vs rebase dijelaskan dengan keluaran log nyata, tabel perbandingan, aturan emas rebase, dan alur kerja tim yang aman.
Baca artikel →Pengembangan perangkat lunakList Comprehension Python: Sintaks, Contoh, dan Kapan Harus Dihindari
List comprehension Python dijelaskan: sintaks, filter, if-else, perulangan bersarang, waktu yang diukur, dan kapan perulangan for biasa lebih baik.
Baca artikel →Basis data & SQLPenjelasan SQL JOIN dengan Contoh (Inner, Left, Right, Full)
Pahami SQL JOIN langkah demi langkah dengan tabel dan query contoh: INNER, LEFT, RIGHT, FULL, CROSS, dan SELF, plus kesalahan umum dan tips performa.
Baca artikel →