Lebih sering daripada tidak, perangkat lunak yang kami tulis berinteraksi langsung dengan apa yang kami beri label sebagai layanan "kotor". Dalam istilah awam. layanan yang sangat penting untuk aplikasi kita, tetapi interaksinya memiliki efek samping yang disengaja tetapi tidak diinginkan—yaitu, tidak diinginkan dalam konteks uji coba otonom. Sebagai contoh. mungkin kami sedang menulis aplikasi sosial dan ingin menguji 'fitur Posting ke Facebook' baru kami, tetapi tidak ingin benar-benar memposting ke Facebook setiap kali kami menjalankan rangkaian pengujian kami. Pustaka _7 Python menyertakan subpaket bernama 8—atau jika Anda mendeklarasikannya sebagai dependensi, cukup 9—yang menyediakan sarana yang sangat kuat dan berguna untuk mengejek dan mematikan efek samping yang tidak diinginkan ini Show
Oleh Naftuli Kay Dari membangun server TCP khusus hingga aplikasi keuangan berskala besar, pengalaman Naftuli yang luas membuatnya menjadi dev dan sysadmin kelas atas BAGIKAN BAGIKAN Cara Menjalankan Tes Unit dengan Python Tanpa Menguji Kesabaran AndaLebih sering daripada tidak, perangkat lunak yang kami tulis berinteraksi langsung dengan apa yang kami beri label sebagai layanan "kotor". Dalam istilah awam. layanan yang sangat penting untuk aplikasi kita, tetapi interaksinya memiliki efek samping yang disengaja tetapi tidak diinginkan—yaitu, tidak diinginkan dalam konteks uji coba otonom Sebagai contoh. mungkin kami sedang menulis aplikasi sosial dan ingin menguji 'fitur Posting ke Facebook' baru kami, tetapi tidak ingin benar-benar memposting ke Facebook setiap kali kami menjalankan rangkaian pengujian kami Pustaka _7 Python menyertakan subpaket bernama 8—atau jika Anda mendeklarasikannya sebagai dependensi, cukup 9—yang menyediakan sarana yang sangat kuat dan berguna untuk mengejek dan mematikan efek samping yang tidak diinginkan iniCatatan. _9 baru saja disertakan dalam pustaka standar pada Python 3. 3; Panggilan Sistem vs. Mengejek PythonUntuk memberi Anda contoh lain, dan yang akan kami jalankan untuk sisa artikel ini, pertimbangkan panggilan sistem. Tidak sulit untuk melihat bahwa ini adalah kandidat utama untuk diolok-olok. apakah Anda sedang menulis skrip untuk mengeluarkan drive CD, server web yang menghapus file cache kuno dari 4, atau server soket yang mengikat ke port TCP, panggilan ini semua menampilkan efek samping yang tidak diinginkan dalam konteks unit Anda- Sebagai pengembang, Anda lebih peduli bahwa pustaka Anda berhasil memanggil fungsi sistem untuk mengeluarkan CD daripada membuat baki CD Anda terbuka setiap kali pengujian dijalankan Sebagai pengembang, Anda lebih peduli bahwa pustaka Anda berhasil memanggil fungsi sistem untuk mengeluarkan CD (dengan argumen yang benar, dll. ) daripada benar-benar mengalami baki CD Anda terbuka setiap kali tes dijalankan. (Atau lebih buruk lagi, berkali-kali, karena beberapa pengujian mereferensikan kode eject selama menjalankan pengujian unit tunggal. ) Demikian pula, menjaga unit-test Anda tetap efisien dan berkinerja berarti menjaga sebanyak mungkin "kode lambat" dari proses pengujian otomatis, yaitu sistem file dan akses jaringan. Untuk contoh pertama kami, kami akan memfaktorkan ulang test case Python standar dari bentuk aslinya menjadi bentuk menggunakan 9. Kami akan mendemonstrasikan bagaimana menulis kasus pengujian dengan tiruan akan membuat pengujian kami lebih cerdas, lebih cepat, dan dapat mengungkapkan lebih banyak tentang cara kerja perangkat lunakFungsi Hapus SederhanaKita semua perlu menghapus file dari sistem file kita dari waktu ke waktu, jadi mari kita menulis sebuah fungsi dengan Python yang akan membuat skrip kita lebih mudah melakukannya _Jelas, metode _6 kami pada saat ini tidak memberikan lebih dari metode 7 yang mendasarinya, tetapi basis kode kami akan meningkat, memungkinkan kami untuk menambahkan lebih banyak fungsi di siniMari kita tulis sebuah test case tradisional, mis. e. , tanpa tiruan
Kasing uji kami cukup sederhana, tetapi setiap kali dijalankan, file sementara dibuat dan kemudian dihapus. Selain itu, kami tidak memiliki cara untuk menguji apakah metode 6 kami dengan benar meneruskan argumen ke panggilan 7. Kita dapat berasumsi bahwa itu berdasarkan tes di atas, tetapi masih banyak yang harus diinginkanRefactoring dengan Python MocksMari refactor test case kita menggunakan 9
Dengan pemfaktoran ulang ini, kami telah mengubah secara mendasar cara pengoperasian pengujian. Sekarang, kami memiliki orang dalam, objek yang dapat kami gunakan untuk memverifikasi fungsionalitas objek lain Potensi Jebakan Mocking PythonSalah satu hal pertama yang harus menonjol adalah bahwa kami menggunakan dekorator metode 1 untuk meniru objek yang terletak di 2, dan menyuntikkan tiruan itu ke dalam metode uji kasus kami. Bukankah lebih masuk akal untuk hanya mengejek 3 itu sendiri, daripada merujuknya di 2?Nah, Python adalah ular yang licik dalam hal mengimpor dan mengelola modul. Saat runtime, modul _5 memiliki 3 sendiri yang diimpor ke dalam lingkup lokalnya sendiri dalam modul. Jadi, jika kita mengejek _3, kita tidak akan melihat efek tiruan di modul 5Mantra untuk terus diulang adalah ini
Jika Anda perlu meniru modul _9 untuk 0, Anda mungkin perlu menerapkan tiruan ke 1, karena setiap modul menyimpan impornya sendiriDengan jebakan itu, mari kita terus mengejek Menambahkan Validasi ke 'rm'Metode _6 yang didefinisikan sebelumnya terlalu disederhanakan. Kami ingin memvalidasi bahwa jalur ada dan merupakan file sebelum mencoba menghapusnya secara membabi buta. Mari refactor _6 menjadi sedikit lebih pintar
Besar. Sekarang, mari kita sesuaikan test case kita untuk menjaga cakupan
Paradigma pengujian kami telah berubah total. Kami sekarang dapat memverifikasi dan memvalidasi fungsionalitas internal metode tanpa efek samping apa pun Penghapusan File sebagai Layanan dengan Mock PatchSejauh ini, kami hanya bekerja dengan menyediakan tiruan untuk fungsi, tetapi tidak untuk metode pada objek atau kasus di mana tiruan diperlukan untuk mengirimkan parameter. Mari kita bahas metode objek terlebih dahulu Kita akan mulai dengan refactor metode 6 ke dalam kelas layanan. Sebenarnya tidak ada kebutuhan yang dapat dibenarkan, per se, untuk mengenkapsulasi fungsi sederhana seperti itu ke dalam sebuah objek, tetapi setidaknya akan membantu kami mendemonstrasikan konsep kunci dalam 9. Mari refactor
Anda akan melihat bahwa tidak banyak yang berubah dalam kasus pengujian kami
Bagus, sekarang kita tahu bahwa 6 berfungsi sesuai rencana. Mari buat layanan lain yang mendeklarasikannya sebagai dependensi
Karena kami sudah memiliki cakupan pengujian pada _6, kami tidak akan memvalidasi fungsi internal metode 6 dalam pengujian kami terhadap 9. Sebaliknya, kami hanya akan menguji (tanpa efek samping, tentu saja) yang 9 memanggil metode 1, yang kami tahu "hanya berfungsi ™" dari kasus pengujian kami sebelumnyaAda dua cara untuk melakukannya
Karena kedua metode tersebut sering kali penting dalam pengujian unit, kami akan meninjau keduanya Pilihan 1. Metode Mocking InstancePustaka 9 memiliki dekorator metode khusus untuk mengejek metode dan properti instance objek, dekorator 5
Besar. Kami telah memvalidasi bahwa _9 berhasil memanggil metode 6 instance kami. Perhatikan sesuatu yang menarik di sana? . Artinya, kita benar-benar dapat memeriksa instans itu sendiri. Jika Anda ingin melihat lebih banyak, coba masukkan breakpoint dalam kode tiruan Anda untuk memahami cara kerja mekanisme penambalanPerangkap Mock Patch. Pesanan DekoratorSaat menggunakan banyak dekorator pada metode pengujian Anda, urutan itu penting, dan agak membingungkan. Pada dasarnya, saat memetakan dekorator ke parameter metode,. Pertimbangkan contoh ini
Perhatikan bagaimana parameter kita dicocokkan dengan urutan dekorator terbalik? . Dengan beberapa dekorator metode, inilah urutan eksekusi dalam pseudocode 0Karena tambalan ke ________ 35 ______ 0 adalah tambalan terluar, tambalan akan dieksekusi terakhir, menjadikannya parameter terakhir dalam argumen metode pengujian yang sebenarnya. Catat ini dengan baik dan gunakan debugger saat menjalankan pengujian Anda untuk memastikan bahwa parameter yang tepat dimasukkan dalam urutan yang benar pilihan 2. Membuat Mock InstanceAlih-alih mengolok-olok metode contoh tertentu, kami malah bisa menyediakan contoh tiruan ke 9 dengan konstruktornya. Saya lebih suka opsi 1 di atas, karena jauh lebih tepat, tetapi ada banyak kasus di mana opsi 2 mungkin efisien atau perlu. Mari refactor pengujian kita lagi _1Dalam contoh ini, kami bahkan tidak perlu menambal fungsionalitas apa pun, kami cukup membuat spek otomatis untuk kelas 6, lalu menyuntikkan instance ini ke 9 kami untuk memvalidasi fungsionalitasMetode ini membuat instance yang setara secara fungsional ke kelas yang disediakan. Artinya, secara praktis, adalah bahwa ketika instance yang dikembalikan berinteraksi, itu akan menimbulkan pengecualian jika digunakan dengan cara ilegal. Lebih khusus lagi, jika suatu metode dipanggil dengan jumlah argumen yang salah, pengecualian akan dimunculkan. Ini sangat penting karena refaktor terjadi. Saat perpustakaan berubah, tes berhenti dan itu diharapkan. Tanpa menggunakan spek otomatis, pengujian kami akan tetap lulus meskipun implementasi yang mendasarinya rusak Batu sandungan. Kelas #!/usr/bin/env python
# -*- coding: utf-8 -*-
from mymodule import RemovalService
import mock
import unittest
class RemovalServiceTestCase(unittest.TestCase):
@mock.patch('mymodule.os.path')
@mock.patch('mymodule.os')
def test_rm(self, mock_os, mock_path):
# instantiate our service
reference = RemovalService()
# set up the mock
mock_path.isfile.return_value = False
reference.rm("any path")
# test that the remove call was NOT called.
self.assertFalse(mock_os.remove.called, "Failed to not remove the file if not present.")
# make the file 'exist'
mock_path.isfile.return_value = True
reference.rm("any path")
mock_os.remove.assert_called_with("any path")
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from mymodule import RemovalService
import mock
import unittest
class RemovalServiceTestCase(unittest.TestCase):
@mock.patch('mymodule.os.path')
@mock.patch('mymodule.os')
def test_rm(self, mock_os, mock_path):
# instantiate our service
reference = RemovalService()
# set up the mock
mock_path.isfile.return_value = False
reference.rm("any path")
# test that the remove call was NOT called.
self.assertFalse(mock_os.remove.called, "Failed to not remove the file if not present.")
# make the file 'exist'
mock_path.isfile.return_value = True
reference.rm("any path")
mock_os.remove.assert_called_with("any path")
|