Tes dengan Python: semua pendekatan utama, pro dan kontra. Laporan Yandex

Sebelum Anda adalah laporan Maria Zelenova zelma- pengembang di Foodil. Selama satu jam, Masha memberi tahu apa itu program pengujian, pengujian apa itu, mengapa menulisnya. Dengan menggunakan contoh sederhana, Anda dapat mempelajari tentang pustaka untuk menguji kode Python (unittest, pytest, mock), cara kerjanya, dan perbedaan di antara keduanya.





- Selamat malam, nama saya Masha, saya bekerja di departemen analisis data Eddila, dan hari ini kami ada kuliah tentang pengujian dengan Anda.







Pertama, kita akan membahas dengan Anda jenis pengujian secara umum, dan saya akan mencoba meyakinkan Anda mengapa Anda perlu menulis tes. Kemudian kita akan berbicara tentang apa yang kita miliki dengan Python untuk bekerja secara langsung dengan tes, dengan modul penulisan dan tambahannya. Pada akhirnya, saya akan memberi tahu Anda sedikit tentang CI - bagian kehidupan yang tak terhindarkan di perusahaan besar.







Saya ingin memulai dengan sebuah contoh. Saya akan mencoba menjelaskan dengan contoh yang sangat menakutkan mengapa tes menulis itu layak.



Ini adalah antarmuka program THERAC 25. Itu adalah nama perangkat untuk terapi radiasi pasien kanker, dan semuanya berjalan sangat buruk dengannya. Pertama-tama, antarmuka itu buruk. Melihatnya, Anda sudah dapat memahami bahwa dia tidak terlalu baik: tidak nyaman bagi dokter untuk mengemudi dengan semua angka ini. Hasilnya, mereka menyalin data dari catatan pasien sebelumnya dan mencoba mengedit hanya yang perlu diedit.



Jelas bahwa mereka lupa mengoreksi setengah dan salah. Akibatnya, pasien diperlakukan tidak tepat. UI juga layak untuk diuji, tidak pernah ada terlalu banyak tes.



Namun selain antarmuka yang buruk, masih banyak lagi masalah di backend. Saya telah mengidentifikasi dua yang menurut saya paling mengerikan:



  • . , . , . , .
  • C . THERAC , โ€” , . . , , , - , - โ€” .


Akan bermanfaat untuk menulis tes. Karena berakhir dengan lima kematian yang tercatat, dan tidak jelas berapa banyak lagi orang yang menderita karena diberi terlalu banyak obat.







Ada contoh lain yang, dalam beberapa situasi, tes menulis dapat menghemat banyak uang. Ini adalah Mars Climate Orbiter - perangkat yang seharusnya mengukur atmosfer di atmosfer Mars, untuk melihat apa yang terjadi di sana dengan iklimnya.



Tetapi modul, yang berada di atas tanah, memberikan perintah dalam sistem SI, dalam sistem metrik. Dan modul di orbit Mars mengira itu adalah sistem pengukuran Inggris, menafsirkannya secara salah.



Akibatnya, modul memasuki atmosfer pada sudut yang salah dan roboh. 125 juta dolar hanya dibuang ke tempat sampah, meskipun tampaknya mungkin untuk mensimulasikan situasi pada pengujian dan menghindari hal ini. Tapi itu tidak berhasil.



Sekarang saya akan berbicara tentang alasan yang lebih biasa mengapa Anda harus menulis tes. Mari kita bicarakan setiap item secara terpisah:



  • Pengujian memastikan kode Anda berfungsi, dan sedikit menenangkan Anda. Dalam kasus di mana Anda menulis pengujian, Anda dapat yakin bahwa kodenya berfungsi - jika, tentu saja, Anda menulisnya dengan baik. Tidur lebih baik. Ini sangat penting.
  • . . , , , . , . , .



    , - , โ€” , - . , . , , . , , , git blame, , , , .
  • . , . , . , , . - - , - - . , , , . - .
  • , . ? , , , , : , . 500 -, . . .
  • : โ€” . , , . , , .



    , , , . , , .
  • . โ€” , . , , . , .



    : - . , , . , , , - , .


Sekarang saya ingin berbicara sedikit tentang apa saja klasifikasi jenis pengujian. Ada banyak sekali. Saya hanya akan menyebutkan beberapa.



Proses pengujian dibagi menjadi pengujian kotak hitam, pengujian putih dan abu-abu.







Pengujian kotak hitam adalah proses ketika penguji tidak tahu apa-apa tentang apa yang ada di dalamnya. Dia, seperti pengguna biasa, melakukan sesuatu tanpa mengetahui spesifikasi implementasi.



Pengujian kotak putih berarti bahwa penguji memiliki akses ke informasi apa pun yang dia butuhkan, termasuk kode sumber. Kami berada dalam situasi seperti itu ketika kami menulis tes pada kode kami sendiri.



Pengujian kotak abu-abu adalah sesuatu di antaranya. Ini adalah saat Anda mengetahui beberapa detail implementasi, tetapi tidak semuanya.



Selain itu, proses pengujian dapat dibagi menjadi manual, semi-otomatis dan otomatis. Pengujian manual dilakukan oleh seseorang. Misalkan dia mengklik tombol di browser, mengklik di suatu tempat, melihat untuk melihat apa yang rusak atau tidak. Pengujian semi-otomatis adalah saat penguji menjalankan skrip pengujian. Kami dapat mengatakan bahwa kami berada dalam situasi seperti itu ketika kami menjalankan dan menjalankan pengujian kami secara lokal. Pengujian otomatis tidak melibatkan partisipasi manusia: pengujian harus dijalankan secara otomatis, bukan dengan tangan.



Selain itu, tes dapat dibagi berdasarkan tingkat detail. Di sini mereka biasanya dibagi menjadi tes unit dan integrasi. Mungkin ada perbedaan. Ada orang yang menyebut tes unit tes otomatis. Tetapi pembagian yang lebih klasik adalah seperti ini.



Tes unit memeriksa operasi masing-masing komponen sistem, dan tes integrasi memeriksa bundel dari beberapa modul. Terkadang ada juga pengujian sistem yang memeriksa pengoperasian seluruh sistem secara keseluruhan. Tetapi tampaknya ini lebih merupakan varian besar dari uji integrasi.



Tes untuk kode kami adalah tes unit dan integrasi. Ada orang yang percaya bahwa hanya tes integrasi yang harus ditulis. Saya bukan salah satu dari mereka, saya pikir semuanya harus secukupnya, dan kedua pengujian unit, saat Anda menguji satu komponen, dan pengujian integrasi, saat Anda menguji sesuatu yang besar, berguna.



Mengapa saya berpikir demikian? Karena tes unit biasanya lebih cepat. Ketika Anda perlu men-tweak sesuatu, Anda akan sangat kesal karena Anda mengklik tombol "run test", dan kemudian menunggu tiga menit untuk database dimulai, migrasi dibuat, sesuatu yang lain terjadi. Untuk kasus seperti itu, pengujian unit berguna. Mereka dapat dijalankan dengan cepat dan nyaman, dijalankan satu per satu. Tetapi ketika Anda telah memperbaiki pengujian unit, bagus, mari kita perbaiki pengujian integrasi.



Tes integrasi juga merupakan hal yang sangat diperlukan, nilai tambah yang besar adalah tes tersebut lebih banyak tentang sistem. Nilai tambah besar lainnya: mereka lebih tahan terhadap pemfaktoran ulang kode. Jika Anda lebih cenderung menulis ulang beberapa fungsi kecil, maka Anda tidak mungkin mengubah keseluruhan pipa dengan frekuensi yang sama.







Ada lebih banyak klasifikasi yang berbeda. Saya akan segera membahas apa yang telah saya tulis di sini, tetapi saya tidak akan membahas secara detail, ini adalah kata-kata yang dapat Anda dengar di tempat lain.



Tes asap adalah tes untuk fungsionalitas kritis, tes pertama dan paling sederhana. Jika rusak, Anda tidak perlu lagi mengujinya, tetapi Anda harus pergi untuk memperbaikinya. Katakanlah aplikasi dimulai, tidak macet - bagus, uji asap berhasil.



Ada tes regresi - tes untuk fungsionalitas lama. Katakanlah Anda menggulung rilis baru dan harus memeriksa bahwa tidak ada yang rusak di rilis lama. Ini adalah tugas uji regresi.



Ada tes kompatibilitas, tes instalasi. Mereka memeriksa bahwa semuanya berfungsi dengan benar untuk Anda di OS yang berbeda dan versi OS yang berbeda, di browser yang berbeda dan versi browser yang berbeda.



Tes penerimaan adalah tes penerimaan. Saya sudah berbicara tentang mereka, mereka berbicara tentang apakah perubahan Anda dapat dimasukkan ke dalam produksi atau tidak.



Ada juga pengujian alfa dan beta. Kedua konsep ini lebih berkaitan dengan produk. Biasanya, jika Anda memiliki versi rilis yang kurang lebih siap, tetapi tidak semuanya diperbaiki di sana, Anda dapat memberikannya kepada orang eksternal bersyarat, atau orang eksternal, sukarelawan, sehingga mereka menemukan bug untuk Anda, laporkan, dan Anda dapat merilis versi yang sangat bagus. Semakin sedikit yang selesai adalah versi alfa, semakin banyak yang selesai adalah versi beta. Dalam pengujian beta, hampir semuanya akan baik-baik saja sekarang.



Lalu ada tes kinerja dan stres, pengujian beban. Mereka memeriksa, misalnya, bagaimana aplikasi Anda menangani beban. Ada beberapa kode. Anda telah menghitung berapa banyak pengguna, permintaan yang dimilikinya, RPS apa, berapa banyak permintaan yang akan datang per detik. Kami mensimulasikan situasi ini, meluncurkannya, melihat - bertahan, tidak bertahan. Jika tidak tahan, pikirkan tentang apa yang harus dilakukan selanjutnya. Mungkin untuk mengoptimalkan kode atau menambah jumlah perangkat keras, ada solusi berbeda.



Tes tegangan hampir sama, hanya bebannya lebih tinggi dari yang diharapkan. Jika tes kinerja memberikan tingkat beban yang Anda harapkan, maka dalam tes stres Anda dapat menambah beban hingga rusak.



Linters agak terpisah di sini. Saya akan memberi tahu Anda tentang linter nanti, ini adalah tes pemformatan kode, panduan gaya. Dengan Python, kami beruntung memiliki PEP8, panduan gaya langsung yang harus diikuti semua orang. Dan ketika Anda menulis sesuatu, Anda biasanya kesulitan mengikuti kodenya. Misalkan Anda lupa memberi garis kosong, atau membuat garis ekstra, atau meninggalkan garis yang terlalu panjang. Ini menghalangi, karena Anda terbiasa dengan fakta bahwa kode Anda ditulis dengan gaya yang sama. Linters memungkinkan Anda menangkap hal-hal seperti itu secara otomatis.



Dengan teori, semuanya, maka saya akan berbicara tentang apa itu Python.







Berikut adalah daftar dari beberapa perpustakaan. Saya tidak akan menjelaskan secara rinci tentang semuanya, tetapi saya akan membahas sebagian besar dari mereka. Tentu saja, kita akan berbicara tentang unittest dan pytest. Ini adalah pustaka yang digunakan secara langsung untuk menulis tes. Mock adalah perpustakaan pembantu untuk membuat objek tiruan. Kami juga akan membicarakannya. doctest adalah modul untuk menguji dokumentasi, flake8 adalah linter, kita juga akan melihatnya. Saya tidak akan berbicara tentang pilama dan racun. Jika Anda tertarik, Anda bisa melihatnya sendiri. Pylama juga merupakan linter, bahkan sebuah metalinter, ini menggabungkan beberapa paket, sangat nyaman dan bagus. Dan pustaka tox diperlukan jika Anda perlu menguji kode Anda di lingkungan yang berbeda - misalnya, dengan versi Python yang berbeda atau dengan versi pustaka yang berbeda. Tox banyak membantu dalam hal ini.



Tetapi sebelum berbicara tentang perpustakaan yang berbeda, saya akan mulai dengan banalitas. Jangan ragu untuk menggunakan assert dalam kode Anda. Ini tidak memalukan. Seringkali membantu untuk memahami apa yang sedang terjadi.







Misalkan ada fungsi yang menghitung statistik ordinal, dua pernyataan tertulis padanya. Assert harus ditulis dalam fungsi dalam kasus di mana itu benar-benar omong kosong yang tidak boleh ada dalam kode. Ini adalah kasus yang sangat ekstrim, kemungkinan besar, Anda bahkan tidak akan menemuinya dalam produksi. Artinya, jika Anda mengacaukan kode, kemungkinan besar kode tersebut akan gagal dalam pengujian Anda.



Assert membantu saat Anda membuat prototipe, Anda belum memiliki kode produksi, Anda dapat tetap menggunakan assert di mana saja - dalam fungsi yang dipanggil, di mana saja. Ini tidak baik untuk proyek yang serius, tetapi cukup bagus pada tahap pembuatan prototipe.



Misalkan Anda ingin menonaktifkan assert karena suatu alasan - misalnya, Anda ingin agar assert tidak pernah aktif dalam produksi. Python memiliki opsi khusus untuk ini.







Saya akan memberitahu Anda apa itu doctest. Ini adalah modul, pustaka standar Python untuk dokumentasi pengujian. Kenapa enak? Dokumentasi yang ditulis dalam kode cenderung sangat sering rusak. Ada fungsi mainan yang sangat kecil di sini, Anda dapat melihat semuanya. Tetapi ketika Anda memiliki kode yang besar, banyak parameter, dan Anda telah menambahkan sesuatu di akhir, maka dengan kemungkinan yang sangat tinggi Anda akan lupa untuk mengoreksi docstrings. Doctest menghindari hal-hal ini. Anda memperbaiki sesuatu, jangan perbarui di sini, jalankan doctest, dan itu akan macet untuk Anda. Jadi Anda akan mengingat apa yang sebenarnya tidak Anda perbaiki, lanjutkan dan perbaiki.



Seperti apa bentuknya? Doctest mencari pohon Natal ini di docstrings, lalu mengeksekusinya dan membandingkan apa yang diperoleh.







Berikut adalah contoh menjalankan doctest. Kami meluncurkannya, kami melihat bahwa kami memiliki dua tes dan salah satunya jatuh - sepenuhnya pada kasing. Bagus, kami melihat beberapa informasi jelas yang bagus tentang kesalahan tersebut.





Tautan dari slide



Doctest memiliki beberapa petunjuk berguna yang mungkin berguna. Saya tidak akan membicarakan semuanya, tetapi beberapa yang menurut saya paling umum, saya taruh di slide. Petunjuk SKIP memungkinkan Anda untuk tidak menjalankan tes pada contoh yang ditandai. Perintah IGNORE_EXCEPTION_DETAIL mengabaikan pengujian EXCEPTION. ELLIPSIS memungkinkan Anda untuk menulis elipsis daripada di manapun dalam output. FAIL_FAST berhenti setelah tes pertama yang gagal. Yang lainnya bisa dibaca di dokumentasi, ada banyak. Lebih baik saya tunjukkan dengan sebuah contoh.







Contoh ini memiliki arahan ELLIPSIS dan arahan IGNORE_EXCEPTION_DETAIL. Anda lihat dalam statistik ordinal K-th arahan ELLIPSIS, dan kami mengharapkan sesuatu yang akan datang, dimulai dengan sembilan dan diakhiri dengan sembilan. Mungkin ada apa saja di tengah. Tes semacam itu tidak akan gagal.



Di bawah ini adalah arahan IGNORE_EXCEPTION_DETAIL, ini hanya akan memeriksa apa yang ada di AssertionError. Lihat, kami menulis blah blah blah di sana. Tes akan lulus, tidak akan membandingkan blah blah blah dengan iterable yang diharapkan seperti argumen pertama. Ini hanya akan membandingkan AssertionError dengan AssertionError. Ini adalah hal-hal berguna yang dapat Anda gunakan.







Maka rencananya adalah ini: Saya akan memberi tahu Anda tentang unittest, lalu tentang pytest. Saya akan segera mengatakan bahwa saya mungkin tidak tahu kelebihan dari unittest, selain itu bagian dari perpustakaan standar. Saya tidak melihat situasi yang akan memaksa saya untuk menggunakan unittest sekarang. Tetapi ada proyek yang menggunakannya, dalam hal apa pun itu berguna untuk mengetahui seperti apa sintaks itu dan apa itu.



Poin lain: tes yang ditulis dalam unittest tahu bagaimana menjalankan pytest langsung dari kotak. Dia tidak peduli. (โ€ฆ)



Unittest terlihat seperti ini. Ada kelas yang dimulai dengan tes kata. Di dalamnya, ada fungsi yang dimulai dengan tes kata. Kelas pengujian mewarisi dari unittest.TestCase. Saya harus segera mengatakan bahwa satu tes di sini ditulis dengan benar, dan tes lainnya salah.



Pengujian teratas, tempat pernyataan normal ditulis, akan gagal, tetapi akan terlihat aneh. Mari kita lihat.







Mulai perintah. Anda dapat menulis main unittest dalam kode itu sendiri, Anda dapat memanggilnya dari Python.







Kami menjalankan pengujian ini dan kami melihat bahwa ia menulis sebuah AssertionError, tetapi ia tidak menulis di mana ia jatuh - tidak seperti pengujian berikutnya, yang menggunakan self.assertEqual. Jelas tertulis di sini: tiga tidak sama dengan dua.







Ini harus diperbaiki, tentu saja. Tapi kemudian keluaran ajaib ini tidak terlihat di layar.



Mari kita lihat lagi. Dalam kasus pertama, kami menulis assert, di kasus kedua, self.assertEqual. Sayangnya, ini satu-satunya cara unittest. Ada fungsi khusus - self.assertEqual, self.assertnotEqual dan 100.500 fungsi lainnya yang perlu Anda gunakan jika Anda ingin melihat pesan kesalahan yang memadai.



Mengapa ini terjadi? Karena assert adalah pernyataan yang menerima bool dan mungkin string, tetapi dalam hal ini bool. Dan dia melihat bahwa dia benar atau salah, dan dia tidak punya tempat untuk mengambil sisi kiri dan kanan. Oleh karena itu, unittest memiliki fungsi khusus yang akan menampilkan pesan kesalahan dengan benar.



Ini sangat tidak nyaman menurut saya. Lebih tepatnya, ini sama sekali tidak nyaman, karena ini adalah beberapa metode khusus yang hanya ada di pustaka ini. Mereka berbeda dari yang biasa kita gunakan dalam bahasa biasa.







Anda tidak perlu mengingat ini - kita akan membicarakan pytest nanti, dan saya harap Anda sebagian besar akan menulis di dalamnya. Unittest memiliki banyak fungsi untuk digunakan jika Anda ingin menguji sesuatu dan mendapatkan pesan kesalahan yang bagus.



Selanjutnya, mari kita bicara tentang cara menulis perlengkapan secara unittest. Tetapi untuk melakukan itu, pertama-tama saya harus memberi tahu Anda perlengkapan apa itu. Ini adalah fungsi yang dipanggil sebelum atau setelah pengujian dijalankan. Mereka diperlukan jika pengujian perlu melakukan pengaturan khusus - buat file sementara setelah pengujian, hapus file sementara; buat database, hapus database; membuat database, menulis sesuatu padanya. Secara umum, apapun. Mari kita lihat tampilannya secara unittest.







Unittest memiliki metode khusus untuk mengatur dan membongkar untuk menulis perlengkapan. Mengapa mereka masih belum ditulis menurut PEP8 adalah misteri besar bagi saya. (...)



SetUp adalah apa yang dilakukan sebelum pengujian, tearDown adalah apa yang dilakukan setelah pengujian. Menurut saya ini adalah desain yang sangat tidak nyaman. Mengapa? Karena, pertama-tama, tangan saya tidak bangkit untuk menulis nama-nama ini: Saya sudah hidup di dunia yang masih ada PEP8. Kedua, Anda memiliki file temp, yang tidak memiliki apa pun dalam argumen pengujian itu sendiri. Dari mana dia datang? Tidak begitu jelas mengapa itu ada dan tentang apa itu semua.



Ketika kami memiliki kelas kecil yang menempel di layar, itu keren, itu bisa ditangkap dengan melihat. Dan ketika Anda memiliki lembaran besar ini, Anda disiksa untuk mencari apa itu dan mengapa dia seperti itu, mengapa dia berperilaku seperti itu.



Ada fitur lain yang tidak terlalu nyaman dengan perlengkapan yang tidak terpakai. Misalkan kita memiliki satu kelas pengujian yang membutuhkan file sementara dan kelas pengujian lainnya yang membutuhkan database. Luar biasa. Anda menulis satu kelas, melakukan setUp, tearDown, membuat / menghapus file sementara. Kami menulis kelas lain, di dalamnya kami juga menulis setUp, tearDown, membuat / menghapus database di dalamnya.



Pertanyaan. Ada kelompok tes ketiga yang membutuhkan keduanya. Apa yang harus dilakukan dengan semua ini? Saya melihat dua opsi. Atau ambil dan salin-tempel kode, tetapi itu sangat tidak nyaman. Atau buat kelas baru, yang diturunkan dari dua kelas sebelumnya, panggil super. Secara umum ini akan bekerja juga, tetapi terlihat seperti berlebihan untuk pengujian.







Oleh karena itu, saya ingin Anda terbiasa dengan unittest tetap seperti ini, pada level teoretis. Selanjutnya kita akan berbicara tentang cara yang lebih nyaman untuk menulis tes, perpustakaan yang lebih nyaman, ini pytest.



Pertama, saya akan mencoba memberi tahu Anda mengapa pytest nyaman.





Tautan dari slide



Poin pertama: di pytest, menegaskan biasanya berfungsi, yang biasa Anda gunakan, dan mereka memberikan informasi normal tentang kesalahan. Kedua: ada dokumentasi yang bagus untuk pytest, di mana banyak contoh dibongkar, dan apa pun yang Anda inginkan, semua yang Anda tidak mengerti dapat dilihat.



Ketiga, tes hanyalah fungsi yang dimulai dengan test_. Artinya, Anda tidak memerlukan kelas tambahan, Anda cukup menulis fungsi biasa, sebut saja test_ dan itu akan dijalankan melalui pytest. Ini nyaman karena semakin mudah menulis tes, semakin besar kemungkinan Anda menulis tes daripada menilai.



Pytest memiliki banyak fitur praktis. Anda dapat menulis tes berparameter, akan lebih mudah untuk menulis perlengkapan dari level yang berbeda, ada juga beberapa kesenangan yang dapat Anda gunakan: xfail, raises, skip, dan beberapa lainnya. Ada banyak plugin di pytest, plus Anda bisa membuatnya sendiri.







Mari kita lihat contohnya. Seperti inilah tampilan tes yang ditulis dalam pytest. Artinya sama dengan on unittest, hanya saja tampilannya jauh lebih ringkas. Tes pertama umumnya dua baris.







Jalankan perintah python -m pytest. Luar biasa. Dua tes berlalu, semuanya baik-baik saja, kami dapat melihat apa yang mereka lulus dan kapan.







Sekarang mari kita hancurkan satu tes dan membuatnya sehingga kita memiliki informasi tentang kesalahannya. Cetak menegaskan 3 == 2 dan kesalahan. Yaitu, kita melihat: terlepas dari fakta bahwa kita menulis assert biasa, kita dengan benar menampilkan informasi tentang kesalahan, meskipun sebelumnya dalam unittest kita mengatakan bahwa assert menerima bool dalam string atau bool, jadi bermasalah untuk menampilkan informasi tentang kesalahan.



Orang mungkin bertanya-tanya mengapa ini semua berhasil? Karena di pytest mereka mencoba dan merapikan bagian yang jelek untuk antarmukanya. Pytest first mem-parsing kode Anda, dan muncul sebagai semacam struktur pohon, pohon sintaks abstrak. Dalam struktur ini, Anda memiliki operator di simpul, dan operan di daun. Tegaskan adalah operator. Itu berdiri di atas pohon, dan pada saat ini, sebelum memberikan segalanya kepada penerjemah, Anda dapat mengganti pernyataan ini dengan fungsi internal yang melakukan introspeksi dan memahami apa yang ada di sisi kiri dan kanan Anda. Bahkan, ini sudah diumpankan ke penerjemah, dengan menegaskan diganti.



Saya tidak akan menjelaskan secara detail, ada tautan, di atasnya Anda dapat membaca bagaimana mereka melakukannya. Tapi saya suka semuanya bekerja di bawah tenda. pengguna tidak melihat ini. Dia menulis menegaskan, seperti yang biasa dia lakukan, perpustakaan itu sendiri melakukan sisanya. Anda bahkan tidak perlu memikirkannya.



Lebih jauh dalam pytest untuk tipe standar, Anda akan memiliki informasi kesalahan yang baik pula. Karena pytest tahu bagaimana menampilkan informasi kesalahan ini. Tapi Anda bisa membandingkan tipe data khusus dalam pengujian Anda, misalnya pohon atau sesuatu yang kompleks, dan pytest mungkin tidak tahu cara menampilkan informasi kesalahan untuk mereka. Untuk kasus seperti itu, Anda dapat menambahkan hook khusus - ini adalah bagian dalam dokumentasi - dan di hook ini tulis bagaimana informasi kesalahan seharusnya terlihat. Semuanya sangat fleksibel dan nyaman.







Mari kita lihat bagaimana perlengkapan terlihat di pytest. Jika dalam unittest perlu menulis setUp dan tearDown, maka di sini panggil fungsi biasa apa pun yang Anda suka. Kami menulis dekorator pytest.fixture di atas - bagus, ini fixture.



Dan ini bukan contoh yang paling sederhana. Perlengkapan dapat melakukan pengembalian, mengembalikan sesuatu, itu akan dianalogikan dengan penyiapan. Dalam hal ini, itu akan membuat semacam tearDown, yaitu, di sini, setelah akhir pengujian, itu akan memanggil tutup, dan file sementara akan dihapus.



Sepertinya nyaman. Anda memiliki fungsi arbitrer yang dapat memberi nama apa pun yang Anda inginkan. Anda secara eksplisit meneruskannya ke ujian. Lulus fill_file, Anda tahu apa itu. Tidak ada yang istimewa dari Anda. Secara umum, gunakan itu. Ini jauh lebih nyaman daripada unittest.







Sedikit lebih banyak tentang perlengkapan. Dalam pytest, sangat mudah untuk membuat perlengkapan dengan cakupan yang berbeda. Secara default, perlengkapan dibuat dengan tingkat fungsi. Ini berarti itu akan dipanggil untuk setiap tes yang Anda lulus. Artinya, jika ada hasil atau hal lain yang mirip dengan penurunan, ini juga akan terjadi setelah setiap pengujian.



Anda dapat mendeklarasikan scope = 'module' dan kemudian fixture akan dijalankan satu kali per modul. Katakanlah Anda ingin membuat database satu kali dan tidak ingin melepaskan dan menggulung semua migrasi setelah setiap pengujian.



Juga dalam fixture dimungkinkan untuk menentukan autouse = True argument, dan fixture akan dipanggil terlepas dari apakah Anda memintanya atau tidak. Tampaknya opsi ini tidak boleh digunakan, atau harus digunakan, tetapi sangat hati-hati, karena ini adalah hal yang tersirat. Yang tersirat sebaiknya dihindari.







Kami menjalankan kode ini - mari kita lihat apa yang terjadi. Ada satu tes yang tergantung pada fixture, hubungi saya sekali gunakan saat diperlukan, hubungi saya setiap saat. Pada saat yang sama, hubungi saya setelah digunakan bila diperlukan adalah perlengkapan tingkat modul. Kami melihat bahwa pertama kali kami memanggil perlengkapan memanggil saya sekali digunakan saat diperlukan, hubungi saya setiap kali, yang mengeluarkan ini, tetapi perlengkapan dengan autouse juga dipanggil, karena tidak peduli, itu selalu dipanggil.



Tes kedua tergantung pada perlengkapan yang sama. Kami melihat bahwa kedua kalinya kami memanggil saya sekali digunakan saat diperlukan tidak dicetak, karena berada pada tingkat modul, sudah dipanggil sekali dan tidak akan dipanggil lagi.



Selain itu, dari contoh ini, Anda dapat melihat bahwa pytest tidak memiliki masalah seperti yang kita bicarakan di unittest, ketika dalam satu pengujian Anda mungkin memerlukan database, di lain - file sementara. Bagaimana cara mengumpulkannya biasanya tidak jelas. Inilah jawaban untuk pertanyaan ini di pytest. Jika dua pertandingan dilewati, maka akan ada dua pertandingan di dalamnya.



Luar biasa, sangat nyaman, tidak masalah. Perlengkapan sangat fleksibel dan dapat bergantung pada perlengkapan lainnya. Tidak ada kontradiksi dalam hal ini, dan pytest sendiri akan memanggilnya dalam urutan yang benar.







Faktanya, di dalamnya Anda dapat mewarisi perlengkapan dari perlengkapan lain, membuatnya berbeda dalam cakupan, dan autouse tanpa autouse. Dia sendiri akan mengaturnya dalam urutan yang benar dan memanggil mereka.



Di sini kita memiliki tes pertama, tes satu, yang bergantung pada rare_dependency_for_test_one, di mana perlengkapan ini bergantung pada perlengkapan lain - dan satu lagi. Mari kita lihat apa yang terjadi pada knalpot.







Kita telah melihat bahwa mereka dipanggil menurut urutan warisan. Ada semua perlengkapan tingkat fungsi, jadi semuanya dipanggil untuk setiap pengujian. Tes kedua bergantung pada rare_dependency dan rare_dependency bergantung pada some_common_dependency. Kami melihat knalpot dan melihat bahwa dua perlengkapan dipanggil sebelum tes.



Pytest memiliki file konfigurasi khusus conftest.py di mana Anda dapat meletakkan semua perlengkapan, dan ada baiknya jika Anda meletakkannya: biasanya, ketika seseorang melihat kode orang lain, dia biasanya pergi untuk melihat perlengkapan di conftest.



Itu tidak wajib. Jika ada fixture yang hanya Anda perlukan di file ini, dan Anda tahu pasti bahwa itu spesifik, dapat diterapkan secara sempit, dan Anda tidak akan membutuhkannya di file lain, maka Anda dapat mendeklarasikannya di file. Atau buat banyak conftest dan semuanya akan bekerja pada level yang berbeda.







Mari kita bahas fitur-fitur yang ada di pytest. Seperti yang saya katakan, sangat mudah untuk membuat parameter pengujian. Di sini kita melihat pengujian yang memiliki tiga set parameter: dua masukan dan satu yang diharapkan. Kami meneruskannya ke argumen fungsi dan melihat apakah apa yang kami berikan ke input cocok dengan yang diharapkan.







Mari kita lihat tampilannya. Kami melihat ada tiga tes. Artinya, menurut pytest, ini adalah tiga tes. Dua berlalu, satu jatuh. Apa yang bagus di sini? Untuk pengujian yang gagal, kami melihat argumennya, kami melihat pada set parameter mana yang jatuh.



Sekali lagi, ketika Anda memiliki fungsi kecil dan parametrize mengatakan tiga, Anda dapat melihat dengan mata Anda apa yang sebenarnya jatuh. Tetapi ketika ada banyak set dalam parameter, Anda tidak akan melihatnya dengan mata Anda. Sebaliknya, Anda akan melihat, tetapi itu akan sangat sulit bagi Anda. Dan sangat mudah bahwa pytest menampilkan semuanya dengan cara ini - Anda dapat langsung melihat dalam hal mana pengujian gagal.







Parametrize adalah hal yang baik. Dan ketika Anda telah menulis tes sekali, dan kemudian melakukan banyak, banyak set parameter, ini adalah praktik yang baik. Jangan membuat banyak varian kode untuk pengujian serupa, tetapi tulis pengujian sekali, lalu buat sejumlah besar parameter, dan itu akan berhasil.



Ada banyak hal yang lebih berguna di pytest. Kalau dibicarakan, kuliahnya jelas tidak cukup, jadi saya akan tunjukkan, sekali lagi, hanya sedikit. Pengujian pertama menggunakan pytest.raises () untuk menunjukkan bahwa Anda mengharapkan pengecualian. Artinya, dalam kasus ini, jika AssertionError dimunculkan, pengujian akan lulus. Anda harus mendapatkan pengecualian.



Hal berguna kedua adalah xfail. Ini adalah dekorator yang memungkinkan pengujian gagal. Katakanlah Anda memiliki banyak tes, banyak kode. Anda memfaktorkan ulang sesuatu, pengujian mulai gagal. Pada saat yang sama, Anda memahami bahwa itu tidak kritis, atau akan sangat mahal untuk memperbaikinya. Dan Anda seperti ini: oke, saya akan gantung dekorator di atasnya, itu akan menjadi hijau, saya akan memperbaikinya nanti. Atau misalkan tes mulai membanjiri. Jelas bahwa ini adalah kesepakatan dengan hati nurani sendiri, tetapi terkadang perlu. Selain itu, xfail dalam formulir ini akan berwarna hijau, terlepas dari tesnya gagal atau tidak. Anda masih dapat meneruskannya ke parameter Strict = True, maka akan menjadi situasi yang sedikit berbeda, pytest akan menunggu tes gagal. Jika tes berhasil, pesan kesalahan akan dikembalikan, dan sebaliknya.



Hal berguna lainnya adalah skipif. Hanya ada lompatan yang tidak akan menjalankan tes. Dan ada skipif. Jika Anda bertahan di dekorator ini, tes tidak akan berjalan dalam kondisi tertentu.



Dalam hal ini, ada tertulis bahwa jika saya memiliki platform Mac, maka jangan mulai, karena pengujian untuk beberapa alasan jatuh. Itu terjadi. Namun secara umum, ada pengujian khusus platform yang akan selalu gagal di platform tertentu. Maka itu berguna.







Mari kita memulainya. Kami melihat huruf X, kami melihat S. X kami mengacu pada xfail, S - ke skipif. Artinya, pytest menunjukkan tes mana yang benar-benar kami lewatkan dan mana yang kami jalankan, tetapi kami tidak melihat hasilnya.







Ada banyak opsi berguna yang berbeda di pytest itu sendiri. Saya tentu saja tidak akan bisa menampilkannya di sini, Anda bisa melihatnya di dokumentasi. Tetapi saya akan memberi tahu Anda tentang beberapa.



Ini adalah opsi yang berguna --collect-only. Ini menampilkan daftar tes yang ditemukan. Ada opsi -k - memfilter berdasarkan nama pengujian. Ini adalah salah satu opsi favorit saya: jika satu pengujian gagal, terutama jika rumit dan Anda belum tahu cara memperbaikinya, filter dan jalankan.



Anda ingin menghemat waktu dan mungkin tidak asyik menjalankan 15 tes lainnya - Anda tahu tes tersebut lulus atau gagal, tetapi Anda belum melakukannya. Jalankan pengujian yang macet, perbaiki, dan lanjutkan.



Ada juga opsi yang sangat bagus -s, ini memungkinkan keluaran dari stdout dan stderr dalam pengujian. Secara default, pytest hanya akan mengeluarkan stdout dan stderr untuk pengujian yang gagal. Tetapi ada kalanya, biasanya pada tahap debugging, ketika Anda ingin mengeluarkan sesuatu dalam pengujian dan tidak tahu apakah pengujian akan gagal. Ini mungkin tidak jatuh, tetapi Anda ingin melihat dalam pengujian itu sendiri apa yang datang di sana dan keluaran. Kemudian jalankan dengan -s dan Anda akan melihat apa yang Anda inginkan.



-v adalah opsi verbose standar, tingkatkan verbositas.



--lf, --last-failed adalah opsi yang memungkinkan Anda untuk memulai ulang hanya pengujian yang gagal pada proses terakhir. --sw, --stepwise juga merupakan fungsi yang berguna seperti -k. Jika Anda memperbaiki pengujian secara berurutan, maka Anda menjalankan dengan --langkah-langkah, ini melewati yang hijau, dan segera setelah melihat pengujian yang gagal, itu berhenti. Dan ketika Anda menjalankan --sw lagi, itu dimulai dengan tes yang gagal ini. Jika jatuh lagi, ia akan berhenti lagi; jika tidak jatuh, ia akan terus bergerak hingga musim gugur berikutnya.





Link dari slide



Dalam pytest ada file konfigurasi utama pytest.ini. Di dalamnya, Anda dapat mengubah perilaku default pytest. Saya telah memberikan di sini opsi yang sangat sering ditemukan di file konfigurasi.



Jalur pengujian adalah jalur yang akan dicari oleh pytest untuk pengujian. addopts adalah apa yang ditambahkan ke baris perintah saat startup. Di sini saya telah menambahkan plugin flake8 dan cakupan ke addopts. Kami akan melihatnya nanti.





Link dari slide



Ada banyak plugin berbeda di pytest. Saya menulis yang, sekali lagi, digunakan di mana-mana. flake8 adalah linter, cakupan kode cakupan oleh tes. Lalu ada satu set lengkap plugin yang membuatnya lebih mudah untuk bekerja dengan kerangka kerja tertentu: pytest-flask, pytest-django, pytest-twisted, pytest-tornado. Mungkin ada hal lain.



Plugin xdist digunakan jika Anda ingin menjalankan tes secara paralel. Plugin waktu tunggu memungkinkan Anda membatasi waktu uji coba: ini berguna. Anda menggantung dekorator batas waktu pada tes, dan jika tes memakan waktu lebih lama, tes gagal.







Mari kita lihat. Saya menambahkan cakupan dan flake8 ke pytest.ini. Cakupan memberi saya laporan, saya memiliki file dengan tes di sana, sesuatu darinya tidak memanggil, tapi tidak apa-apa :)



Ini adalah file k_stat.py, itu berisi sebanyak lima pernyataan. Ini kira-kira sama dengan lima baris kode. Dan cakupannya 100%, tapi itu karena file saya sangat kecil.



Padahal, cakupan biasanya tidak seratus persen, terlebih lagi tidak boleh dicapai dengan segala cara. Secara subyektif, tampaknya cakupan tes 60-70% cukup dan normal untuk pekerjaan.



Cakupan adalah metrik yang, meskipun seratus persen, tidak berarti Anda hebat. Fakta bahwa Anda telah memanggil kode ini tidak berarti Anda telah memeriksa sesuatu. Anda juga dapat menulis assert True di bagian akhir. Anda perlu mendekati cakupan secara wajar, karena cakupan pengujian 100% ada yang memudar dan robot, tetapi orang tidak perlu melakukannya.



Di pytest.ini saya telah menghubungkan satu plugin lagi. Di sini Anda dapat melihat --flake8, ini adalah linter yang menunjukkan kesalahan gaya saya, dan beberapa lainnya, bukan dari PEP8, tetapi dari pyflakes.







Di sini, di knalpot ditulis nomor kesalahan di PEP8 atau di pyflakes. Secara umum, semuanya jelas. Garis terlalu panjang, untuk redefinisi Anda membutuhkan dua baris kosong, Anda membutuhkan satu baris kosong di akhir file. Di bagian akhir dikatakan bahwa CitizenImport tidak digunakan untuk saya. Secara umum, linter memungkinkan Anda menemukan kesalahan besar dan kesalahan dalam desain kode.







Kami sudah berbicara tentang plugin batas waktu, ini memungkinkan Anda membatasi waktu uji coba. Untuk beberapa perftests, run time itu penting. Dan Anda dapat membatasinya di dalam pengujian dengan time.time dan timeit. Atau menggunakan plugin batas waktu, yang juga sangat nyaman. Jika tes bekerja terlalu banyak, dapat diprofilkan dengan cara yang berbeda, misalnya cProfile, tetapi Yura akan membicarakannya dalam kuliahnya .







Jika Anda menggunakan IDE, dan layak menggunakan alat bantu, yang saya miliki di sini, khususnya, PyCharm, maka pengujian sangat mudah dijalankan langsung darinya.







Masih berbicara tentang tiruan. Katakanlah kita memiliki modul A, kita ingin mengujinya dan ada modul lain yang tidak ingin kita uji. Salah satunya masuk ke jaringan, yang lainnya ke database, dan yang ketiga adalah modul sederhana yang sama sekali tidak mengganggu kita. Dalam kasus seperti itu, ejekan akan membantu kita. Sekali lagi, jika kita menulis pengujian integrasi, kemungkinan besar kita akan memunculkan database pengujian, menulis klien pengujian, dan itu juga bagus. Ini hanya tes integrasi.



Ada kalanya kita ingin melakukan unittest ketika kita hanya ingin menguji satu potong. Lalu kita butuh tiruan.







Mock adalah kumpulan benda-benda yang dapat digunakan untuk menggantikan benda yang sebenarnya. Pada setiap panggilan ke metode, ke atribut, ini juga mengembalikan tiruan.







Dalam contoh ini, kami memiliki modul sederhana. Kami akan meninggalkannya, dan mengganti beberapa yang lebih kompleks dengan tiruan. Sekarang kita akan melihat cara kerjanya.







Ini ditunjukkan di sini dengan jelas. Kami mengimpornya, kami mengatakan bahwa m adalah tiruan. Disebut kembali mengejek. Mereka mengatakan bahwa m memiliki metode f. Disebut kembali mengejek. Mereka mengatakan bahwa m adalah atribut is_alive. Bagus, tiruan lain sudah kembali. Dan kita melihat bahwa m dan f dipanggil sekali. Artinya, ini adalah objek yang sangat rumit, di dalamnya metode getattr ditulis ulang.







Mari kita lihat contoh yang lebih jelas. Katakanlah ada AliveChecker. Dia menggunakan semacam http_session, dia membutuhkan target, dan dia memiliki fungsi do_check yang mengembalikan True atau false, tergantung pada apa yang dia terima: 200 atau tidak 200. Ini adalah contoh yang sedikit artifisial. Tetapi misalkan di dalam do_check Anda dapat menyelesaikan logika yang kompleks.



Katakanlah kita tidak ingin menguji apa pun tentang sesi, kita tidak ingin tahu apa-apa tentang metode get. Kami hanya ingin menguji do_check. Hebat, mari kita uji.







Anda bisa melakukannya seperti ini. Mock http_session, ini yang disebut pseudo_client. Kami mengejek metode get-nya, kami mengatakan bahwa get adalah tiruan yang mengembalikan 200. Kami meluncurkan, membuat AliveChecker dari ini, meluncurkannya. Tes ini akan berhasil.



Selain itu, mari kita periksa bahwa get dipanggil sekali dan dengan argumen yang persis sama seperti yang tertulis. Artinya, kami memanggil do_check tanpa mengetahui apa pun tentang sesi itu atau apa metodenya. Kami hanya membekukan mereka. Satu-satunya hal yang kita tahu adalah bahwa ia mengembalikan 200.







Contoh lain. Ini sangat mirip dengan yang sebelumnya. Satu-satunya hal di sini adalah side_effect daripada return_value. Tapi itu adalah sesuatu yang dilakukan tiruan. Dalam hal ini, ini membuat pengecualian. Baris pernyataan telah diubah untuk menegaskan bukan AliveChecker.do_check (). Artinya, kami melihat bahwa cek tersebut tidak akan lulus.



Ini adalah dua contoh cara menguji fungsi do_check tanpa mengetahui apa pun tentang apa yang masuk dari atas, apa yang masuk ke kelas ini.







Contoh, tentu saja, terlihat artifisial: tidak sepenuhnya jelas mengapa check, 200 atau tidak 200, hanya ada logika minimum. Tapi mari kita bayangkan melakukan sesuatu yang rumit tergantung pada kode pengembaliannya. Dan kemudian tes semacam itu mulai tampak jauh lebih bermakna. Kami melihat bahwa 200 datang, dan kemudian kami memeriksa logika pemrosesan. Jika tidak 200 - sama.







Anda juga dapat menambal pustaka dengan tiruan. Katakanlah Anda sudah memiliki perpustakaan dan perlu mengubah sesuatu di dalamnya. Berikut ini contohnya, kami telah menambal sinus. Sekarang dia selalu mengembalikan deuce. Luar biasa.



Kami juga melihat bahwa m telah dipanggil dua kali. Mock, tentu saja, tidak tahu apa-apa tentang API internal metode yang Anda tiru dan, secara umum, tidak diwajibkan untuk mencocokkannya. Tetapi mock memungkinkan Anda untuk memeriksa apa yang Anda panggil, berapa kali, dan dengan argumen apa. Dalam pengertian ini, ini membantu untuk menguji kode.







Saya ingin memperingatkan Anda tentang kasus di mana ada satu modul dan tiruan besar. Harap dekati semuanya dengan wajar. Jika Anda memiliki hal-hal sederhana, jangan basahi. Semakin banyak ejekan yang Anda miliki dalam pengujian, semakin Anda menjauh dari kenyataan: API Anda mungkin tidak cocok, dan secara umum, ini bukanlah yang Anda uji. Anda tidak perlu merendam semuanya jika tidak perlu. Dekati prosesnya dengan cerdas.







Kami memiliki bagian kecil terakhir tentang Integrasi Berkelanjutan. Saat Anda mengembangkan proyek hewan peliharaan sendiri, Anda dapat menjalankan pengujian secara lokal, dan tidak apa-apa, itu akan berhasil.



Segera setelah proyek berkembang dan ada lebih dari satu pengembang di dalamnya, proyek itu berhenti bekerja. Pertama, separuh tidak akan menjalankan pengujian secara lokal. Kedua, mereka akan menjalankannya pada versi mereka. Akan ada konflik di suatu tempat, semuanya akan terus rusak.



Untuk itu, ada Continuous Integration, sebuah praktik pembangunan yang melibatkan dengan cepat memasukkan kandidat ke dalam arus utama. Tetapi pada saat yang sama, mereka harus melalui semacam perakitan otomatis atau autotest dalam sistem khusus. Anda memiliki kode di repositori, komit yang ingin Anda gabungkan ke cabang proyek utama Anda. Pada komit ini, pengujian dilewatkan dalam sistem khusus. Jika tes berwarna hijau, maka komit dituangkan dengan sendirinya, atau Anda memiliki kesempatan untuk menuangkannya.



Skema seperti itu, tentu saja, memiliki kekurangan, dan juga segalanya. Paling tidak, Anda memerlukan perangkat keras tambahan - bukan fakta bahwa CI akan gratis. Tetapi di perusahaan yang lebih atau kurang besar, dan bukan perusahaan besar juga, Anda tidak dapat pergi ke mana pun tanpa CI.







Sebagai contoh - tangkapan layar dari TeamCity, salah satu CI. Ada perakitan, itu selesai dengan sukses. Ada banyak perubahan di dalamnya, itu diluncurkan pada agen ini dan itu pada saat ini dan itu. Ini adalah contoh ketika semuanya baik dan bisa diinfuskan.



Ada banyak sistem CI yang berbeda. Saya menulis daftar, jika tertarik, lihat: AppVeyor, Jenkins, Travis, CircleCI, GoCD, Buildbot. Terima kasih.






Ceramah lain dari kursus video tentang Python ada di postingan di Habrรฉ .



All Articles