Buku "The Way of Python. Sabuk hitam untuk pengembangan, penskalaan, pengujian dan penyebaran ”

gambar Hai, habrozhiteli! Jalur Python memungkinkan Anda mengasah keterampilan profesional Anda dan belajar sebanyak mungkin tentang kemampuan bahasa pemrograman paling populer. Anda akan belajar cara menulis kode yang efektif, membuat program terbaik dalam waktu minimal dan menghindari kesalahan umum. Saatnya berkenalan dengan komputasi multi-utas dan memoisasi, dapatkan saran ahli di bidang API dan desain basis data, serta melihat ke dalam Python untuk memperluas pemahaman Anda tentang bahasa. Anda harus memulai proyek, bekerja dengan versi, mengatur pengujian otomatis dan memilih gaya pemrograman untuk tugas tertentu. Kemudian Anda akan melanjutkan untuk mempelajari deklarasi fungsi yang efektif, pilih struktur data dan pustaka yang sesuai, buat program bebas masalah, paket dan optimalkan program pada level bytecode.

Kutipan. Menjalankan tes secara paralel


Menjalankan test suite dapat menghabiskan waktu. Ini adalah kejadian umum dalam proyek besar ketika test suite membutuhkan waktu beberapa menit untuk menyelesaikannya. Secara default, pytest menjalankan tes secara berurutan, dalam urutan tertentu.

Karena sebagian besar komputer memiliki prosesor multi-core, Anda dapat mempercepat jika Anda memisahkan tes untuk dijalankan pada beberapa core.

Untuk ini, pytest memiliki plugin pytest-xdist yang dapat diinstal menggunakan pip. Plugin ini memperluas baris perintah pytest dengan argumen ––numprocesses (disingkat –n), yang mengambil jumlah core yang digunakan sebagai argumen. Menjalankan pytest –n 4 akan menjalankan test suite dalam empat proses paralel, menjaga keseimbangan antara beban core yang tersedia.

Karena kenyataan bahwa jumlah inti dapat bervariasi, plugin juga menerima kata kunci otomatis sebagai nilai. Dalam hal ini, jumlah core yang tersedia akan dikembalikan secara otomatis.

Membuat objek yang digunakan dalam tes menggunakan perlengkapan


Dalam pengujian unit, seringkali diperlukan untuk melakukan serangkaian operasi standar sebelum dan setelah menjalankan tes, dan instruksi ini melibatkan komponen-komponen tertentu. Misalnya, Anda mungkin memerlukan objek yang akan menyatakan kondisi konfigurasi aplikasi, dan harus diinisialisasi sebelum setiap pengujian, dan kemudian mengatur ulang ke nilai awal setelah eksekusi. Demikian pula, jika tes tergantung pada file sementara, file ini harus dibuat sebelum tes dan dihapus setelahnya. Komponen semacam itu disebut fixture . Mereka diinstal sebelum pengujian dan menghilang setelah eksekusi.

Dalam perlengkapan pytest dinyatakan sebagai fungsi sederhana. Fungsi fixture harus mengembalikan objek yang diinginkan sehingga dalam pengujian di mana ia digunakan, objek ini dapat digunakan.

Berikut adalah contoh perlengkapan sederhana:

import pytest @pytest.fixture def database(): return <some database connection> def test_insert(database): database.insert(123) 

Fixture database secara otomatis digunakan oleh tes apa pun yang memiliki argumen database dalam daftar. Fungsi test_insert () akan menerima hasil dari fungsi database () sebagai argumen pertama dan akan menggunakan hasil ini sesuai keinginan. Dengan penggunaan fixture ini, Anda tidak perlu mengulang kode inisialisasi database beberapa kali.

Fitur umum lain dari pengujian kode adalah kemampuan untuk menghapus berlebihan setelah operasi fixture. Misalnya, tutup koneksi basis data. Menerapkan fixture sebagai generator akan menambah fungsionalitas untuk membersihkan objek yang diverifikasi (Listing 6.5).

Listing 6.5. Menghapus Objek Terverifikasi


 import pytest @pytest.fixture def database(): db = <some database connection> yield db db.close() def test_insert(database): database.insert(123) 
Karena kami menggunakan kata kunci hasil dan membuat generator dari basis data, kode setelah pernyataan hasil dijalankan hanya pada akhir pengujian. Kode ini akan menutup koneksi database di akhir tes.

Menutup koneksi database untuk setiap tes dapat menyebabkan pemborosan daya komputasi, karena tes lain dapat menggunakan koneksi yang sudah terbuka. Dalam hal ini, Anda bisa meneruskan argumen cakupan ke dekorator fixture, dengan menentukan cakupannya:

 import pytest @pytest.fixture(scope="module") def database(): db = <some database connection> yield db db.close() def test_insert(database): database.insert(123) 

Dengan menentukan parameter scope = "module", Anda menginisialisasi fixture satu kali untuk seluruh modul, dan sekarang koneksi database terbuka akan tersedia untuk semua fungsi tes yang memintanya.

Anda dapat menjalankan beberapa kode umum sebelum atau setelah pengujian, dengan menetapkan perlengkapan yang secara otomatis digunakan dengan kata kunci autouse, daripada menetapkannya sebagai argumen untuk setiap fungsi pengujian. Mengkonkretkan fungsi pytest.fixture () dengan argumen True, kata kunci autouse, memastikan bahwa perlengkapan dipanggil setiap kali sebelum menjalankan tes dalam modul atau kelas di mana ia dinyatakan.

 import os import pytest @pytest.fixture(autouse=True) def change_user_env(): curuser = os.environ.get("USER") os.environ["USER"] = "foobar" yield os.environ["USER"] = curuser def test_user(): assert os.getenv("USER") == "foobar"</source     .  ,    :     ,      ,       . <h3>  </h3>           ,    ,   ,         .          Gnocchi,    . Gnocchi      <i>storage API</i>.    Python          .       ,      API   .        ,      (    storage API),  ,       .   ,   <i> </i>,     ,        .  6.6          ,    :    mysql,   —  postgresql. <blockquote><h4> 6.6.      </h4> <source lang="python">import pytest import myapp @pytest.fixture(params=["mysql", "postgresql"]) def database(request): d = myapp.driver(request.param) d.start() yield d d.stop() def test_insert(database): database.insert("somedata") 
Fixture driver menerima dua nilai berbeda sebagai parameter - nama driver database yang didukung oleh aplikasi. test_insert dijalankan dua kali: sekali untuk database MySQL, dan yang kedua untuk database PostgreSQL. Ini membuatnya mudah untuk mengikuti tes yang sama, tetapi dengan skenario yang berbeda, tanpa menambahkan baris kode baru.

Tes yang Dikelola dengan Objek Dummy


Objek dummy (atau bertopik, objek tiruan) adalah objek yang meniru perilaku objek aplikasi nyata, tetapi dalam keadaan khusus dan terkontrol. Mereka sangat berguna dalam menciptakan lingkungan yang secara menyeluruh menggambarkan kondisi untuk pengujian. Anda dapat mengganti semua objek kecuali objek yang diuji dengan objek dummy dan mengisolasinya, serta membuat lingkungan untuk pengujian kode.

Satu use case adalah membuat klien HTTP. Hampir mustahil (atau lebih tepatnya, sangat sulit) untuk membuat server HTTP tempat Anda dapat menjalankan semua situasi dan skenario untuk setiap nilai yang mungkin. Klien HTTP sangat sulit untuk menguji skenario kesalahan.

Perpustakaan standar memiliki perintah tiruan untuk membuat objek dummy. Dimulai dengan Python 3.3, mock telah diintegrasikan dengan perpustakaan unittest.mock. Oleh karena itu, Anda dapat menggunakan potongan kode di bawah ini untuk memberikan kompatibilitas mundur antara Python 3.3 dan sebelumnya:

 try: from unittest import mock except ImportError: import mock 

Perpustakaan tiruan sangat mudah digunakan. Setiap atribut yang tersedia untuk objek mock.Mock dibuat secara dinamis saat runtime. Atribut apa pun dapat diberi nilai apa pun. Dalam Listing 6.7, mock digunakan untuk membuat objek dummy untuk atribut dummy.

Listing 6.7. Mengakses atribut mock.Mock


 >>> from unittest import mock >>> m = mock.Mock() >>> m.some_attribute = "hello world" >>> m.some_attribute "hello world" 
Anda juga bisa secara dinamis membuat metode untuk objek yang bisa berubah, seperti di Listing 6.8, di mana Anda membuat metode dummy yang selalu mengembalikan 42 dan mengambil apa pun yang Anda inginkan sebagai argumen.

Listing 6.8. Membuat metode untuk objek dummy mock.Mock


 >>> from unittest import mock >>> m = mock.Mock() >>> m.some_method.return_value = 42 >>> m.some_method() 42 >>> m.some_method("with", "arguments") 42 
Hanya beberapa baris, dan objek mock.Mock sekarang memiliki metode some_method (), yang mengembalikan 42. Dibutuhkan semua jenis argumen, sementara tidak ada verifikasi dari apa argumen itu.

Metode yang dihasilkan secara dinamis juga dapat memiliki efek samping (disengaja). Agar tidak hanya metode boilerplate yang mengembalikan nilai, mereka dapat didefinisikan untuk mengeksekusi kode yang bermanfaat.

Listing 6.9 menciptakan metode dummy yang memiliki efek samping - ini menampilkan string “hello world”.

Listing 6.9. Membuat metode untuk objek mock.Mock dengan efek samping


  >>> from unittest import mock >>> m = mock.Mock() >>> def print_hello(): ... print("hello world!") ... return 43 ... ❶ >>> m.some_method.side_effect = print_hello >>> m.some_method() hello world! 43 ❷ >>> m.some_method.call_count 1 
Kami menetapkan seluruh fungsi ke atribut some_method ❶. Secara teknis, ini memungkinkan Anda untuk menerapkan skenario yang lebih kompleks dalam pengujian, karena Anda dapat memasukkan kode yang diperlukan untuk pengujian dalam objek dummy. Selanjutnya, Anda harus meneruskan objek ini ke fungsi yang mengharapkannya.

Atribut ❷ call_count adalah cara mudah untuk memeriksa berapa kali suatu metode dipanggil.

Perpustakaan tiruan menggunakan pola "aksi-cek": ini berarti bahwa setelah pengujian Anda perlu memastikan bahwa tindakan yang diganti oleh boneka dilakukan dengan benar. Listing 6.10 menerapkan metode assert () ke objek dummy untuk melakukan pemeriksaan ini.

Listing 6.10. Hubungi metode verifikasi


  >>> from unittest import mock >>> m = mock.Mock() ❶ >>> m.some_method('foo', 'bar') <Mock name='mock.some_method()' id='26144272'> ❷ >>> m.some_method.assert_called_once_with('foo', 'bar') >>> m.some_method.assert_called_once_with('foo', ❸mock.ANY) >>> m.some_method.assert_called_once_with('foo', 'baz') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python2.7/dist-packages/mock.py", line 846, in assert_cal led_once_with return self.assert_called_with(*args, **kwargs) File "/usr/lib/python2.7/dist-packages/mock.py", line 835, in assert_cal led_with raise AssertionError(msg) AssertionError: Expected call: some_method('foo', 'baz') Actual call: some_method('foo', 'bar') 
Kami membuat metode dengan argumen foo dan bar sebagai pengujian dengan memanggil metode ❶. Cara mudah untuk memeriksa panggilan ke objek dummy adalah dengan menggunakan metode assert_called (), seperti assert_called_once_with () ❷. Untuk metode ini, Anda harus memberikan nilai yang Anda harapkan untuk digunakan saat memanggil metode dummy. Jika nilai yang diteruskan berbeda dari yang digunakan, maka mock memunculkan eksepsi AssertionError. Jika Anda tidak tahu argumen apa yang dapat diajukan, gunakan mock.ANY sebagai nilai ❸; itu akan menggantikan argumen apa pun yang diteruskan ke metode dummy.

Perpustakaan tiruan juga dapat digunakan untuk mengganti fungsi, metode, atau objek dari modul eksternal. Dalam Listing 6.11, kami mengganti fungsi os.unlink () dengan fungsi dummy kami sendiri.

Listing 6.11. Menggunakan mock.patch


 >>> from unittest import mock >>> import os >>> def fake_os_unlink(path): ... raise IOError("Testing!") ... >>> with mock.patch('os.unlink', fake_os_unlink): ... os.unlink('foobar') ... Traceback (most recent call last): File "<stdin>", line 2, in <module> File "<stdin>", line 2, in fake_os_unlink IOError: Testing! 
Ketika digunakan sebagai manajer konteks, mock.patch () menggantikan fungsi target dengan yang kita pilih. Ini diperlukan agar kode yang dieksekusi dalam konteks menggunakan metode yang dikoreksi. Menggunakan metode mock.patch (), Anda dapat memodifikasi bagian mana pun dari kode eksternal, memaksanya berperilaku sedemikian rupa untuk menguji semua kondisi untuk aplikasi (Listing 6.12).

Listing 6.12. Menggunakan mock.patch () untuk menguji banyak perilaku


  from unittest import mock import pytest import requests class WhereIsPythonError(Exception): passdef is_python_still_a_programming_language(): try: r = requests.get("http://python.org") except IOError: pass else: if r.status_code == 200: return 'Python is a programming language' in r.content raise WhereIsPythonError("Something bad happened") def get_fake_get(status_code, content): m = mock.Mock() m.status_code = status_code m.content = content def fake_get(url): return m return fake_get def raise_get(url): raise IOError("Unable to fetch url %s" % url) ❷ @mock.patch('requests.get', get_fake_get( 200, 'Python is a programming language for sure')) def test_python_is(): assert is_python_still_a_programming_language() is True @mock.patch('requests.get', get_fake_get( 200, 'Python is no more a programming language')) def test_python_is_not(): assert is_python_still_a_programming_language() is False @mock.patch('requests.get', get_fake_get(404, 'Whatever')) def test_bad_status_code(): with pytest.raises(WhereIsPythonError): is_python_still_a_programming_language() @mock.patch('requests.get', raise_get) def test_ioerror(): with pytest.raises(WhereIsPythonError): is_python_still_a_programming_language() 


Listing 6.12 mengimplementasikan kasus uji yang mencari semua instance Python adalah string bahasa pemrograman di python.org ❶. Tidak ada opsi di mana tes tidak akan menemukan baris yang diberikan pada halaman web yang dipilih. Untuk mendapatkan hasil negatif, Anda perlu mengubah halaman, tetapi ini tidak dapat dilakukan. Tetapi dengan bantuan mock, Anda dapat melakukan trik dan mengubah perilaku permintaan sehingga mengembalikan respons dummy dengan halaman fiktif yang tidak berisi string yang diberikan. Ini akan memungkinkan Anda untuk menguji skenario negatif di mana python.org tidak berisi string yang diberikan, dan pastikan bahwa program menangani kasus seperti itu dengan benar.

Contoh ini menggunakan versi dekorator mock.patch () . Perilaku objek dummy tidak berubah, dan lebih mudah untuk memberi contoh dalam konteks fungsi tes.

Menggunakan objek dummy akan membantu mensimulasikan masalah: server mengembalikan kesalahan 404, kesalahan I / O, atau kesalahan penundaan jaringan. Kami dapat memastikan bahwa kode mengembalikan nilai yang benar atau melemparkan pengecualian yang tepat dalam setiap kasus, yang menjamin perilaku yang diharapkan dari kode.

Identifikasi Kode yang Belum Diuji dengan cakupan


Tambahan yang bagus untuk pengujian unit adalah alat cakupan. [Cakupan kode adalah ukuran yang digunakan dalam pengujian. Memperlihatkan persentase kode sumber program yang dieksekusi selama proses pengujian - red. ], yang menemukan potongan kode yang tidak diuji. Ini menggunakan analisis kode dan alat pelacakan untuk mengidentifikasi baris yang dieksekusi. Dalam pengujian unit, ini dapat mengungkapkan bagian mana dari kode yang digunakan kembali dan mana yang tidak digunakan sama sekali. Diperlukan membuat tes, dan kemampuan untuk mengetahui bagian mana dari kode yang Anda lupa selesaikan dengan tes membuat proses ini lebih menyenangkan.

Pasang modul cakupan melalui pip untuk dapat menggunakannya melalui shell Anda.

CATATAN


Perintah ini juga bisa disebut cakupan python jika modul diinstal melalui installer OS Anda. Contohnya adalah OS Debian.


Menggunakan cakupan offline sangat sederhana. Ini menunjukkan bagian-bagian dari program yang tidak pernah memulai dan menjadi "bobot mati" - kode sedemikian rupa sehingga Anda tidak dapat menghapusnya tanpa mengubah fungsionalitas program. Semua alat uji yang dibahas sebelumnya dalam bab ini terintegrasi dengan cakupan.

Saat menggunakan pytest, instal plugin pytest-cov melalui pip instal pytest-pycov dan tambahkan beberapa sakelar untuk menghasilkan keluaran terperinci dari kode yang belum diuji (Listing 6.13).

Listing 6.13. Menggunakan pytest dan cakupan


 $ pytest --cov=gnocchiclient gnocchiclient/tests/unit ---------- coverage: platform darwin, python 3.6.4-final-0 ----------- Name Stmts Miss Branch BrPart Cover --------------------------- gnocchiclient/__init__.py 0 0 0 0 100% gnocchiclient/auth.py 51 23 6 0 49% gnocchiclient/benchmark.py 175 175 36 0 0% --snip-- --------------------------- TOTAL 2040 1868 424 6 8% === passed in 5.00 seconds === 
Opsi --cov memungkinkan output dari laporan pertanggungan di akhir pengujian. Anda harus memberikan nama paket sebagai argumen agar plugin memfilter laporan dengan benar. Output akan berisi baris kode yang belum dieksekusi, yang berarti belum diuji. Yang tersisa untuk Anda adalah membuka editor dan menulis tes untuk kode ini.

Modul cakupan bahkan lebih baik - memungkinkan Anda untuk menghasilkan laporan yang jelas dalam format HTML. Cukup tambahkan -–cov-report-html dan halaman HTML akan muncul di direktori htmlcov dari mana Anda menjalankan perintah. Setiap halaman akan menunjukkan bagian mana dari kode sumber yang sedang atau tidak berjalan.

Jika Anda ingin melangkah lebih jauh, gunakan –-over-fail-under-COVER_MIN_PERCENTAGE, yang akan menyebabkan suite pengujian gagal jika tidak mencakup persentase minimum kode. Meskipun persentase besar cakupan adalah tujuan yang baik, dan alat pengujian berguna untuk memperoleh informasi tentang status cakupan pengujian, persentase itu sendiri tidak terlalu informatif. Gambar 6.1 menunjukkan contoh laporan cakupan yang menunjukkan persen cakupan.

Misalnya, menutup kode dengan pengujian 100% adalah tujuan yang layak, tetapi ini tidak berarti bahwa kode tersebut sepenuhnya diuji. Nilai ini hanya menunjukkan bahwa semua baris kode dalam program terpenuhi, tetapi tidak menunjukkan bahwa semua kondisi telah diuji.

Sebaiknya gunakan informasi cakupan untuk memperluas test suite dan membuatnya untuk kode yang tidak berjalan. Ini menyederhanakan dukungan proyek dan meningkatkan kualitas kode secara keseluruhan.

gambar


Tentang penulis


Julien Danju telah meretas freeware selama sekitar dua puluh tahun, dan telah mengembangkan program Python selama hampir dua belas tahun. Dia saat ini memimpin tim desain untuk platform cloud terdistribusi berbasis OpenStack, yang memiliki database open source Python terbesar yang ada, dengan sekitar dua setengah juta baris kode. Sebelum mengembangkan layanan cloud, Julien menciptakan window manager dan berkontribusi pada pengembangan banyak proyek, seperti Debian dan GNU Emacs.

Tentang Editor Sains


Mike Driscoll telah memprogram dalam Python selama lebih dari satu dekade. Untuk waktu yang lama, ia menulis tentang Python di The Mouse vs. Python . Penulis beberapa buku Python: Python 101, Wawancara Python, dan ReportLab: Pemrosesan PDF dengan Python. Anda dapat menemukan Mike di Twitter dan di GitHub: @driscollis.

»Informasi lebih lanjut tentang buku ini dapat ditemukan di situs web penerbit
» Isi
» Kutipan

Kupon diskon 25% untuk penjaja - Python

Setelah pembayaran versi kertas buku, sebuah buku elektronik dikirim melalui email.

Source: https://habr.com/ru/post/id466027/


All Articles