Pengujian Python dengan pytest. BAB 3 Jadwal Terbaik

Kembali Selanjutnya


Buku ini adalah bab yang hilang yang hilang dari setiap buku Python yang komprehensif.


Frank ruiz
Insinyur Keandalan Situs Utama, Box, Inc.



Contoh-contoh dalam buku ini ditulis menggunakan Python 3.6 dan pytest 3.2. pytest 3.2 mendukung Python 2.6, 2.7 dan Python 3.3+


Kode sumber untuk proyek Tugas, serta untuk semua tes yang ditunjukkan dalam buku ini, tersedia di tautan di halaman web buku di pragprog.com . Anda tidak perlu mengunduh kode sumber untuk memahami kode uji; kode uji disajikan dalam bentuk yang mudah dalam contoh. Tetapi untuk mengikuti tugas-tugas proyek, atau mengadaptasi contoh uji untuk menguji proyek Anda sendiri (tangan Anda tidak terikat!), Anda harus pergi ke halaman web buku dan mengunduh karya. Di sana, di halaman web buku, ada tautan untuk pesan errata dan forum diskusi .

Di bawah spoiler adalah daftar artikel dalam seri ini.



Sekarang Anda telah melihat dasar-dasar pytest, mari kita mengalihkan perhatian kita ke fixture, yang diperlukan untuk menyusun kode uji untuk hampir semua sistem perangkat lunak non-sepele. Fixture adalah fungsi yang dilakukan oleh pytest sebelum (dan kadang-kadang setelah) fungsi tes yang sebenarnya. Kode fixture dapat melakukan apa pun yang Anda butuhkan. Anda dapat menggunakan Fixtures untuk mendapatkan dataset untuk pengujian. Anda dapat menggunakan Jadwal untuk membuat sistem dalam kondisi yang diketahui sebelum menjalankan tes. Fixture juga digunakan untuk mendapatkan data untuk beberapa tes.


Berikut adalah contoh perlengkapan sederhana yang mengembalikan nomor:


test_fixtures.py / test_fixtures.py

 import pytest @pytest.fixture() def some_data(): """Return answer to ultimate question.""" return 42 def test_some_data(some_data): """Use fixture return value in a test.""" assert some_data == 42 

@pytest.fixture() digunakan untuk memberi tahu pytest bahwa fungsinya adalah fixture. Saat Anda memasukkan nama fixture dalam daftar parameter fungsi tes, pytest tahu cara menjalankannya sebelum menjalankan tes. Perlengkapan dapat melakukan pekerjaan, atau mereka dapat mengembalikan data ke fungsi tes.


Tes test_some_data() memiliki nama test_some_data() some_data sebagai parameter. pytest akan mendeteksi ini dan menemukan fixture dengan nama itu. Namanya signifikan di pytest. pytest akan mencari perlengkapan dengan nama itu di modul tes. Dia juga akan mencari di conftest.py jika dia tidak menemukannya dalam hal ini.


Sebelum kita memulai penelitian kita tentang fixture (dan file conftest.py), saya perlu mempertimbangkan fakta bahwa fixture istilah memiliki banyak arti dalam komunitas pemrograman dan pengujian dan bahkan dalam komunitas Python. Saya menggunakan fixture , fixture function , dan fixture method bergantian untuk merujuk ke fungsi @pytest.fixture() dijelaskan dalam bab ini. Fixture juga dapat digunakan untuk menunjukkan sumber daya yang dirujuk oleh fungsi fixture. Fungsi-fungsi fixture seringkali mengatur atau mengambil beberapa data yang dapat digunakan oleh tes ini. Terkadang data ini dianggap sebagai perlengkapan. Sebagai contoh, komunitas Django sering menggunakan perlengkapan untuk menunjukkan beberapa data mentah yang dimuat ke dalam database di awal aplikasi.


Terlepas dari arti lain, di pytest dan dalam buku ini, perlengkapan pengujian merujuk pada mekanisme yang disediakan pytest untuk memisahkan kode "bersiap-siap" dan "membersihkan setelah" dari fungsi pengujian Anda.


perlengkapan pytest adalah salah satu fitur unik yang menaikkan pytest dari lingkungan pengujian lainnya dan merupakan alasan mengapa banyak orang yang dihormati beralih ke ... dan tetap menggunakan pytest. Namun, perlengkapan di pytest berbeda dari perlengkapan di Django dan berbeda dari pengaturan dan prosedur teardown ditemukan di unittest dan hidung. Ada banyak fitur dan nuansa dalam hal perlengkapan. Begitu Anda mendapatkan model mental yang baik tentang bagaimana mereka bekerja, Anda akan merasa lebih baik. Namun, Anda perlu bermain dengan mereka untuk sementara waktu untuk mengemudi, jadi mari kita mulai.


Berbagi perlengkapan melalui conftest.py


Anda dapat meletakkan fixture dalam file tes yang terpisah, tetapi untuk membagikan fixture dalam beberapa file uji, lebih baik menggunakan file conftest.py di suatu tempat di tempat yang umum, terpusat untuk semua tes. Untuk proyek tugas, semua perlengkapan akan berada di tasks_proj/tests/conftest.py .


Dari sana, perlengkapan dapat dibagi dengan adonan apa pun. Anda dapat menempatkan fixture dalam file pengujian terpisah jika Anda ingin fixture hanya digunakan dalam pengujian file ini. Demikian pula, Anda dapat memiliki file conftest.py lainnya di subdirektori dari direktori tes teratas . Jika Anda melakukannya, perlengkapan yang ditentukan dalam file-file conftest.py tingkat rendah ini akan tersedia untuk pengujian dalam direktori dan subdirektori ini. Namun, sampai sekarang perlengkapan dalam proyek Tugas telah dirancang untuk pengujian apa pun. Oleh karena itu, menggunakan semua alat kami di file conftest.py di root tes, tasks_proj/tests , masuk akal.


Meskipun conftest.py adalah modul Python, itu tidak boleh diimpor oleh file uji. Jangan mengimpor conftest kapan! File conftest.py dibaca oleh pytest dan dianggap sebagai plug-in lokal, yang akan menjadi jelas ketika kita mulai berbicara tentang plug-in di bab 5 “Plug-in” di halaman 95. Untuk saat ini, pertimbangkan tests/conftest.py sebagai tempat di mana kita dapat menempatkan perlengkapan untuk digunakan semua tes di direktori tes. Lalu mari kita ulang beberapa tes kami untuk task_proj menggunakan perlengkapan dengan benar.


Menggunakan Perlengkapan untuk Pengaturan dan Teardown


Sebagian besar tes dalam proyek Tugas mengasumsikan bahwa database Tugas sudah dikonfigurasikan, berjalan, dan siap. Dan kita harus menghapus beberapa entri pada akhirnya, jika ada kebutuhan untuk pembersihan. Dan Anda mungkin juga perlu memutuskan koneksi dari database. Untungnya, sebagian besar diurus dalam kode tugas dengan tasks.start_tasks_db(<directory to store db\>, 'tiny' or 'mongo') dan tasks.stop_tasks_db() ; kita hanya perlu memanggil mereka pada waktu yang tepat, dan kita juga memerlukan direktori sementara.


Untungnya, pytest menyertakan perlengkapan yang sangat baik yang disebut tmpdir. Kita bisa menggunakannya untuk pengujian dan tidak perlu khawatir tentang pembersihan. Ini bukan sihir, hanya praktik pengkodean yang baik dari orang yang paling ingin tahu. (Jangan khawatir; kami akan menganalisis tmpdir dan menuliskannya lebih detail menggunakan tmpdir_factory di bagian “Menggunakan tmpdir dan tmpdir_factory” di halaman 71.)


Dengan semua komponen ini, fixture ini berfungsi dengan baik:


ch3 / a / tasks_proj /tests/conftest.py

 import pytest import tasks from tasks import Task @pytest.fixture() def tasks_db(tmpdir): """    ,  .""" # Setup : start db tasks.start_tasks_db(str(tmpdir), 'tiny') yield #    # Teardown : stop db tasks.stop_tasks_db() 

Nilai tmpdir bukan string - itu adalah objek yang mewakili direktori. Namun, ini mengimplementasikan __str__ , jadi kita bisa menggunakan str() untuk mendapatkan string yang lewat ke start_tasks_db() . Untuk saat ini, kami masih menggunakan tiny untuk TinyDB.


Fungsi fixture dijalankan sebelum tes yang menggunakannya. Namun, jika fungsi memiliki hasil , maka akan berhenti di sana, kontrol akan diteruskan ke tes dan baris berikutnya setelah hasil dijalankan setelah tes selesai. Oleh karena itu, pikirkan kode di atas hasil sebagai "pengaturan", dan kode setelah hasil sebagai "teardown". Kode setelah menghasilkan "teardown" akan dijalankan terlepas dari apa yang terjadi selama pengujian. Kami tidak mengembalikan data dengan output di fixture ini. Tapi kamu bisa.


Mari kita modifikasi salah satu dari tasks.add() tes untuk menggunakan fixture ini:


ch3 / a / tasks_proj / tes / func / test_add .py

 import pytest import tasks from tasks import Task def test_add_returns_valid_id(tasks_db): """tasks.add(<valid task>)    .""" # GIVEN    # WHEN    # THEN  task_id  int new_task = Task('do something') task_id = tasks.add(new_task) assert isinstance(task_id, int) 

Perubahan utama di sini adalah bahwa perlengkapan tambahan dalam file telah dihapus, dan kami menambahkan tasks_db ke daftar parameter pengujian. Saya suka menyusun tes dalam format GIVEN / WHEN / THEN (DANO / WHEN / SETELAH), menggunakan komentar, terutama jika ini tidak jelas dari kode apa yang terjadi. Saya pikir ini berguna dalam kasus ini. Semoga GIVEN memulai tugas db akan membantu mencari tahu mengapa tasks_db digunakan sebagai alat untuk pengujian.




Pastikan Tugas diinstal.




Kami masih menulis tes untuk proyek Tugas di bab ini, yang pertama kali diinstal di bab 2. Jika Anda melewatkan bab ini, pastikan untuk menginstal tugas dengan kode cd; pip install ./tasks_proj/ .




Melacak Eksekusi Fixture dengan –setup-show


Jika Anda menjalankan tes dari bagian terakhir, Anda tidak akan melihat perlengkapan mana yang berjalan:


 $ cd /path/to/code/ $ pip install ./tasks_proj/ #      $ cd /path/to/code/ch3/a/tasks_proj/tests/func $ pytest -v test_add.py -k valid_id ===================== test session starts ====================== collected 3 items test_add.py::test_add_returns_valid_id PASSED ====================== 2 tests deselected ====================== ============ 1 passed, 2 deselected in 0.02 seconds ============ 

Ketika saya mendesain perlengkapan, saya perlu melihat apa yang berhasil dan kapan. Untungnya, pytest menyediakan flag baris perintah seperti itu, -- setup-show , yang tidak hanya itu:


 $ pytest --setup-show test_add.py -k valid_id ============================= test session starts ============================= collected 3 items / 2 deselected test_add.py SETUP S tmpdir_factory SETUP F tmpdir (fixtures used: tmpdir_factory) SETUP F tasks_db (fixtures used: tmpdir) func/test_add.py::test_add_returns_valid_id (fixtures used: tasks_db, tmpdir, tmpdir_factory). TEARDOWN F tasks_db TEARDOWN F tmpdir TEARDOWN S tmpdir_factory =================== 1 passed, 2 deselected in 0.18 seconds ==================== 

Tes kami ada di tengah, dan pytest menunjuk bagian SETUP dan TEARDOWN untuk setiap fixture. Dimulai dengan test_add_returns_valid_id , Anda melihat bahwa tmpdir bekerja sebelum tes. Dan sebelum itu tmpdir_factory . tmpdir tampaknya menggunakannya sebagai perlengkapan.


F dan S di depan nama fixture menunjukkan area. F untuk cakupan dan S untuk cakupan sesi. Saya akan membahas ruang lingkup di bagian "Spesifikasi Ruang Lingkup" di halaman 56.


Menggunakan Fixture untuk Data Uji


Perlengkapan adalah tempat yang tepat untuk menyimpan data untuk pengujian. Anda dapat mengembalikan apa pun. Berikut adalah fixture yang mengembalikan tuple tipe campuran:


ch3 / test_fixtures.py

 @pytest.fixture() def a_tuple(): """ -  """ return (1, 'foo', None, {'bar': 23}) def test_a_tuple(a_tuple): """Demo the a_tuple fixture.""" assert a_tuple[3]['bar'] == 32 

Karena test_a_tuple() harus gagal (23! = 32) , kita akan melihat apa yang terjadi ketika tes fixture gagal:


 $ cd /path/to/code/ch3 $ pytest test_fixtures.py::test_a_tuple ============================= test session starts ============================= collected 1 item test_fixtures.py F [100%] ================================== FAILURES =================================== ________________________________ test_a_tuple _________________________________ a_tuple = (1, 'foo', None, {'bar': 23}) def test_a_tuple(a_tuple): """Demo the a_tuple fixture.""" > assert a_tuple[3]['bar'] == 32 E assert 23 == 32 test_fixtures.py:38: AssertionError ========================== 1 failed in 0.17 seconds =========================== 

Bersama-sama dengan bagian stack stack, pytest menampilkan parameter nilai dari fungsi yang menyebabkan pengecualian atau pernyataan yang gagal. Dalam kasus pengujian, fixture adalah parameter untuk pengujian, sehingga mereka dilaporkan menggunakan stack stack. Apa yang terjadi jika pernyataan (atau pengecualian) terjadi pada fixture?


 $ pytest -v test_fixtures.py::test_other_data ============================= test session starts ============================= test_fixtures.py::test_other_data ERROR [100%] =================================== ERRORS ==================================== ______________________ ERROR at setup of test_other_data ______________________ @pytest.fixture() def some_other_data(): """Raise an exception from fixture.""" x = 43 > assert x == 42 E assert 43 == 42 test_fixtures.py:21: AssertionError =========================== 1 error in 0.13 seconds =========================== 

Beberapa hal terjadi. Jejak tumpukan dengan benar menunjukkan bahwa pernyataan terjadi dalam fungsi fixture. Selain itu, test_other_data dilaporkan bukan sebagai GAGAL , tetapi sebagai KESALAHAN . Ini perbedaan utama. Jika tes tiba-tiba gagal, Anda tahu bahwa kegagalan terjadi dalam tes itu sendiri, dan tidak tergantung pada beberapa perlengkapan.


Tetapi bagaimana dengan proyek Tugas? Untuk proyek Tugas, kami mungkin dapat menggunakan beberapa perlengkapan data, daftar tugas yang mungkin berbeda dengan properti yang berbeda:


ch3 / a / task_proj / tes / conftest.py

 #    Task constructor # Task(summary=None, owner=None, done=False, id=None) # summary    # owner  done   # id    @pytest.fixture() def tasks_just_a_few(): """    .""" return ( Task('Write some code', 'Brian', True), Task("Code review Brian's code", 'Katie', False), Task('Fix what Brian did', 'Michelle', False)) @pytest.fixture() def tasks_mult_per_owner(): """     .""" return ( Task('Make a cookie', 'Raphael'), Task('Use an emoji', 'Raphael'), Task('Move to Berlin', 'Raphael'), Task('Create', 'Michelle'), Task('Inspire', 'Michelle'), Task('Encourage', 'Michelle'), Task('Do a handstand', 'Daniel'), Task('Write some books', 'Daniel'), Task('Eat ice cream', 'Daniel')) 

Anda dapat menggunakannya langsung dari tes, atau dari perlengkapan lain. Mari kita buat database yang tidak kosong untuk pengujian dengan bantuan mereka.


Menggunakan Berbagai Perlengkapan


Anda telah melihat bahwa tmpdir menggunakan tmpdir_factory. Dan Anda menggunakan tmpdir di fixture task_db kami. Mari kita lanjutkan rantai dan tambahkan beberapa perlengkapan khusus untuk pangkalan tugas proyek yang tidak kosong:


ch3 / a / task_proj / tes / conftest.py

 @pytest.fixture() def db_with_3_tasks(tasks_db, tasks_just_a_few): """   3 ,  .""" for t in tasks_just_a_few: tasks.add(t) @pytest.fixture() def db_with_multi_per_owner(tasks_db, tasks_mult_per_owner): """   9 , 3 owners,  3   .""" for t in tasks_mult_per_owner: tasks.add(t) 

Semua perlengkapan ini menyertakan dua perlengkapan dalam daftar parameter mereka: tasks_db dan kumpulan data. Dataset digunakan untuk menambahkan tugas ke database. Sekarang tes dapat menggunakannya jika Anda ingin tes dimulai dengan database yang tidak kosong, misalnya:


ch3 / a / task_proj / tes / func / test_add.py

 def test_add_increases_count(db_with_3_tasks): """Test tasks.add()    tasks.count().""" # GIVEN db  3  # WHEN     tasks.add(Task('throw a party')) # THEN    1 assert tasks.count() == 4 

Ini juga menunjukkan salah satu alasan utama untuk menggunakan fixture: untuk memfokuskan tes pada apa yang sebenarnya Anda uji, daripada apa yang harus Anda lakukan untuk mempersiapkan tes. Saya suka menggunakan komentar untuk GIVEN / KETIKA / KEMUDIAN dan mencoba untuk mendorong sebanyak mungkin data (DIBERIKAN) ke dalam perlengkapan karena dua alasan. Pertama, itu membuat tes lebih mudah dibaca dan karena itu lebih dapat dipertahankan. Kedua, pernyataan atau pengecualian dalam fixture menghasilkan kesalahan (ERROR), sedangkan pernyataan atau pengecualian dalam fungsi tes menghasilkan kesalahan (GAGAL). Saya tidak ingin test_add_increases_count() jika inisialisasi database gagal. Itu membingungkan. Saya ingin kegagalan (GAGAL) dari test_add_increases_count() menjadi mungkin hanya jika add () benar-benar tidak dapat mengubah penghitung. Mari kita jalankan dan lihat bagaimana semua perlengkapan bekerja:


 $ cd /path/to/code/ch3/a/tasks_proj/tests/func $ pytest --setup-show test_add.py::test_add_increases_count ============================= test session starts ============================= collected 1 item test_add.py SETUP S tmpdir_factory SETUP F tmpdir (fixtures used: tmpdir_factory) SETUP F tasks_db (fixtures used: tmpdir) SETUP F tasks_just_a_few SETUP F db_with_3_tasks (fixtures used: tasks_db, tasks_just_a_few) func/test_add.py::test_add_increases_count (fixtures used: db_with_3_tasks, tasks_db, tasks_just_a_few, tmpdir, tmpdir_factory). TEARDOWN F db_with_3_tasks TEARDOWN F tasks_just_a_few TEARDOWN F tasks_db TEARDOWN F tmpdir TEARDOWN S tmpdir_factory ========================== 1 passed in 0.20 seconds =========================== 

Kami mendapat banyak Fs dan Ss lagi untuk area fungsi dan sesi. Mari kita lihat apa itu.


Spesifikasi Ruang Lingkup


Perlengkapan termasuk parameter opsional yang disebut lingkup , yang menentukan seberapa sering perlengkapan menerima pengaturan dan pembongkaran. Parameter lingkup untuk @ pytest.fixture() dapat memiliki nilai fungsi, kelas, modul, atau sesi. Lingkup adalah fungsi secara default. Pengaturan task_db dan semua perlengkapan belum menentukan area. Dengan demikian, mereka adalah perlengkapan fungsional.


Berikut ini adalah deskripsi singkat dari setiap nilai Lingkup :


  • scope = 'function'


    Ini dilakukan sekali untuk setiap fungsi tes. Bagian pengaturan dijalankan sebelum setiap tes menggunakan fixture. Bagian teardown dimulai setelah setiap tes menggunakan fixture. Ini adalah area default jika parameter lingkup tidak ditentukan.


  • scope = 'class'


    Ini dijalankan sekali untuk setiap kelas tes, terlepas dari jumlah metode pengujian di kelas.


  • scope = 'module'


    Ini dijalankan satu kali untuk setiap modul, terlepas dari berapa banyak fungsi pengujian atau metode atau perlengkapan lain yang digunakan saat menggunakan modul.


  • scope = 'session'


    Ini dilakukan sekali per sesi. Semua metode dan fungsi pengujian yang menggunakan perlengkapan lingkup sesi menggunakan pengaturan tunggal dan panggilan teardown.



Begini seperti apa nilai lingkup dalam tindakan:


ch3 / test_scope.py

 """Demo fixture scope.""" import pytest @pytest.fixture(scope='function') def func_scope(): """A function scope fixture.""" @pytest.fixture(scope='module') def mod_scope(): """A module scope fixture.""" @pytest.fixture(scope='session') def sess_scope(): """A session scope fixture.""" @pytest.fixture(scope='class') def class_scope(): """A class scope fixture.""" def test_1(sess_scope, mod_scope, func_scope): """   ,   .""" def test_2(sess_scope, mod_scope, func_scope): """     .""" @pytest.mark.usefixtures('class_scope') class TestSomething(): """Demo class scope fixtures.""" def test_3(self): """Test using a class scope fixture.""" def test_4(self): """Again, multiple tests are more fun.""" 

Mari kita gunakan --setup-show untuk menunjukkan bahwa jumlah fixture dan panggilan setup dipasangkan dengan teardown dilakukan tergantung pada area:


 $ cd /path/to/code/ch3/ $ pytest --setup-show test_scope.py ============================= test session starts ============================= collected 4 items test_scope.py SETUP S sess_scope SETUP M mod_scope SETUP F func_scope test_scope.py::test_1 (fixtures used: func_scope, mod_scope, sess_scope). TEARDOWN F func_scope SETUP F func_scope test_scope.py::test_2 (fixtures used: func_scope, mod_scope, sess_scope). TEARDOWN F func_scope SETUP C class_scope test_scope.py::TestSomething::()::test_3 (fixtures used: class_scope). test_scope.py::TestSomething::()::test_4 (fixtures used: class_scope). TEARDOWN C class_scope TEARDOWN M mod_scope TEARDOWN S sess_scope ========================== 4 passed in 0.11 seconds =========================== 

Sekarang Anda dapat melihat tidak hanya F dan S untuk fungsi dan sesi, tetapi juga C dan M untuk kelas dan modul.


Lingkup didefinisikan menggunakan perlengkapan. Saya tahu ini jelas dari kode, tetapi ini adalah poin penting untuk memastikan bahwa Anda benar-benar mengerang. mengerti "). Ruang lingkup didefinisikan dalam definisi fixture, dan bukan di tempat panggilannya. Fungsi pengujian yang menggunakan perlengkapan tidak mengontrol seberapa sering (SETUP) dan waktu istirahat (TEARDOWN).


Perlengkapan hanya dapat bergantung pada perlengkapan lain dari lingkup yang sama atau lebih diperluas. Dengan demikian, fixture lingkup fungsi dapat bergantung pada fixture lingkup fungsi lainnya (secara default dan masih digunakan dalam proyek Tugas). fixture lingkup fungsi mungkin juga tergantung pada kelas, modul, dan perlengkapan dari area sesi, tetapi tidak pernah dalam urutan terbalik.


Ubah Lingkup untuk Jadwal Proyek Tugas


Dengan pengetahuan tentang ruang lingkup ini, sekarang mari kita mengubah ruang lingkup beberapa perlengkapan proyek Tugas.


Sejauh ini, kami belum memiliki masalah dengan waktu pengujian. Tapi, Anda harus mengakui bahwa tidak berguna untuk membuat direktori sementara dan koneksi database baru untuk setiap tes. Selama kami dapat menyediakan database kosong, bila perlu, ini sudah cukup.


Untuk menggunakan sesuatu seperti tasks_db sebagai ruang lingkup sesi, Anda harus menggunakan tmpdir_factory , karena tmpdir adalah ruang lingkup fungsi dan tmpdir_factory adalah ruang lingkup sesi. Untungnya, ini hanya satu baris perubahan kode (yah, dua jika Anda mempertimbangkan tmpdir->tmpdir_factory dalam daftar parameter):


ch3 / b / task_proj / tes / conftest.py

 """Define some fixtures to use in the project.""" import pytest import tasks from tasks import Task @pytest.fixture(scope='session') def tasks_db_session(tmpdir_factory): """Connect to db before tests, disconnect after.""" temp_dir = tmpdir_factory.mktemp('temp') tasks.start_tasks_db(str(temp_dir), 'tiny') yield tasks.stop_tasks_db() @pytest.fixture() def tasks_db(tasks_db_session): """An empty tasks db.""" tasks.delete_all() 

Di sini kami mengubah tasks_db tergantung pada tasks_db_session , dan kami menghapus semua entri untuk memastikannya kosong. Karena kami belum mengubah namanya, tidak ada satu pun perlengkapan atau tes yang sudah termasuk yang boleh berubah.


Perlengkapan data hanya mengembalikan nilai, sehingga benar-benar tidak ada alasan bagi mereka untuk bekerja sepanjang waktu. Satu kali per sesi sudah cukup:


ch3 / b / task_proj / tes / conftest.py

 # Reminder of Task constructor interface # Task(summary=None, owner=None, done=False, id=None) # summary is required # owner and done are optional # id is set by database @pytest.fixture(scope='session') def tasks_just_a_few(): """All summaries and owners are unique.""" return ( Task('Write some code', 'Brian', True), Task("Code review Brian's code", 'Katie', False), Task('Fix what Brian did', 'Michelle', False)) @pytest.fixture(scope='session') def tasks_mult_per_owner(): """Several owners with several tasks each.""" return ( Task('Make a cookie', 'Raphael'), Task('Use an emoji', 'Raphael'), Task('Move to Berlin', 'Raphael'), Task('Create', 'Michelle'), Task('Inspire', 'Michelle'), Task('Encourage', 'Michelle'), Task('Do a handstand', 'Daniel'), Task('Write some books', 'Daniel'), Task('Eat ice cream', 'Daniel')) 

Sekarang mari kita lihat apakah semua perubahan ini akan bekerja dengan pengujian kami:


 $ cd /path/to/code/ch3/b/tasks_proj $ pytest ===================== test session starts ====================== collected 55 items tests/func/test_add.py ... tests/func/test_add_variety.py ............................ tests/func/test_add_variety2.py ............ tests/func/test_api_exceptions.py ....... tests/func/test_unique_id.py . tests/unit/test_task.py .... ================== 55 passed in 0.17 seconds =================== 

Segalanya tampak teratur. Mari kita lihat jadwal untuk satu file tes untuk melihat bagaimana area yang berbeda bekerja sesuai dengan harapan kita:


 $ pytest --setup-show tests/func/test_add.py ============================= test session starts ============================= platform win32 -- Python 3.6.5, pytest-3.9.3, py-1.7.0, pluggy-0.8.0 rootdir: c:\_BOOKS_\pytest_si\bopytest-code\code\ch3\b\tasks_proj\tests, inifile: pytest.ini collected 3 items tests\func\test_add.py SETUP S tmpdir_factory SETUP S tasks_db_session (fixtures used: tmpdir_factory) SETUP F tasks_db (fixtures used: tasks_db_session) func/test_add.py::test_add_returns_valid_id (fixtures used: tasks_db, tasks_db_session, tmpdir_factory). TEARDOWN F tasks_db SETUP F tasks_db (fixtures used: tasks_db_session) func/test_add.py::test_added_task_has_id_set (fixtures used: tasks_db, tasks_db_session, tmpdir_factory). TEARDOWN F tasks_db SETUP S tasks_just_a_few SETUP F tasks_db (fixtures used: tasks_db_session) SETUP F db_with_3_tasks (fixtures used: tasks_db, tasks_just_a_few) func/test_add.py::test_add_increases_count (fixtures used: db_with_3_tasks, tasks_db, tasks_db_session, tasks_just_a_few, tmpdir_factory). TEARDOWN F db_with_3_tasks TEARDOWN F tasks_db TEARDOWN S tasks_db_session TEARDOWN S tmpdir_factory TEARDOWN S tasks_just_a_few ========================== 3 passed in 0.24 seconds =========================== 

Ya . tasks_db_session , task_db .


Specifying Fixtures with usefixtures


, , , . , @pytest.mark.usefixtures('fixture1', 'fixture2') . usefixtures , , . — . :


ch3/test_scope.py

 @pytest.mark.usefixtures('class_scope') class TestSomething(): """Demo class scope fixtures.""" def test_3(self): """Test using a class scope fixture.""" def test_4(self): """Again, multiple tests are more fun.""" 

usefixtures , . , , . , - usefixtures , .


autouse Fixtures That Always Get Used ( )


, , ( usefixtures ). autouse=True , . , , . :


ch3/test_autouse.py

 """ autouse fixtures.""" import pytest import time @pytest.fixture(autouse=True, scope='session') def footer_session_scope(): """    session().""" yield now = time.time() print('--') print('finished : {}'.format(time.strftime('%d %b %X', time.localtime(now)))) print('-----------------') @pytest.fixture(autouse=True) def footer_function_scope(): """     .""" start = time.time() yield stop = time.time() delta = stop - start print('\ntest duration : {:0.3} seconds'.format(delta)) def test_1(): """   .""" time.sleep(1) def test_2(): """    .""" time.sleep(1.23) 

, . :


 $ cd /path/to/code/ch3 $ pytest -v -s test_autouse.py ===================== test session starts ====================== collected 2 items test_autouse.py::test_1 PASSED test duration : 1.0 seconds test_autouse.py::test_2 PASSED test duration : 1.24 seconds -- finished : 25 Jul 16:18:27 ----------------- =================== 2 passed in 2.25 seconds =================== 

autouse . , . , .


, autouse , , tasks_db . Tasks , , , API . . , .


Fixtures


, , , . , pytest name @pytest.fixture() :


ch3/ test_rename_fixture.py

 """ fixture renaming.""" import pytest @pytest.fixture(name='lue') def ultimate_answer_to_life_the_universe_and_everything(): """  .""" return 42 def test_everything(lue): """   .""" assert lue == 42 

lue fixture , fixture_with_a_name_much_longer_than_lue . , --setup-show :


 $ pytest --setup-show test_rename_fixture.py ======================== test session starts ======================== collected 1 items test_rename_fixture.py SETUP F lue test_rename_fixture.py::test_everything (fixtures used: lue). TEARDOWN F lue ===================== 1 passed in 0.01 seconds ====================== 

, lue , pytest --fixtures . , , , :


 $ pytest --fixtures test_rename_fixture.py ======================== test session starts ======================= ... ------------------ fixtures defined from test_rename_fixture ------------------ lue Return ultimate answer. ================= no tests ran in 0.01 seconds ================= 

— . , , , , , . , lue . «Tasks»:


 $ cd /path/to/code/ch3/b/tasks_proj $ pytest --fixtures tests/func/test_add.py ======================== test session starts ======================== ... tmpdir_factory Return a TempdirFactory instance for the test session. tmpdir Return a temporary directory path object which is unique to each test function invocation, created as a sub directory of the base temporary directory. The returned object is a `py.path.local`_ path object. ----------------------- fixtures defined from conftest ------------------------ tasks_db An empty tasks db. tasks_just_a_few All summaries and owners are unique. tasks_mult_per_owner Several owners with several tasks each. db_with_3_tasks Connected db with 3 tasks, all unique. db_with_multi_per_owner Connected db with 9 tasks, 3 owners, all with 3 tasks. tasks_db_session Connect to db before tests, disconnect after. =================== no tests ran in 0.01 seconds ==================== 

! conftest.py . tmpdir tmpdir_factory , .



[Parametrized Testing] , . 42, . . - , , :


ch3/b/tasks_proj/tests/func/test_add_variety2.py

"""Test the tasks.add() API function."""

import pytest
import tasks
from tasks import Task

tasks_to_try = (Task('sleep', done=True),
Task('wake', 'brian'),
Task('breathe', 'BRIAN', True),
Task('exercise', 'BrIaN', False))

task_ids = ['Task({},{},{})'.format(t.summary, t.owner, t.done)
for t in tasks_to_try]

def equivalent(t1, t2):
"""Check two tasks for equivalence."""
return ((t1.summary == t2.summary) and
(t1.owner == t2.owner) and
(t1.done == t2.done))

, , a_task :


ch3/b/tasks_proj/tests/func/ test_add_variety2.py

@pytest.fixture(params=tasks_to_try)
def a_task(request):
""" ."""
return request.param

def test_add_a(tasks_db, a_task):
""" a_task ( ids)."""
task_id = tasks.add(a_task)
t_from_db = tasks.get(task_id)
assert equivalent(t_from_db, a_task)

, fixture, , . . param, , params @pytest.fixture(params=tasks_to_try) .


a_taskrequest.param , . , , :


 $ cd /path/to/code/ch3/b/tasks_proj/tests/func $ pytest -v test_add_variety2.py::test_add_a ===================== test session starts ====================== collected 4 items test_add_variety2.py::test_add_a[a_task0] PASSED test_add_variety2.py::test_add_a[a_task1] PASSED test_add_variety2.py::test_add_a[a_task2] PASSED test_add_variety2.py::test_add_a[a_task3] PASSED =================== 4 passed in 0.03 seconds =================== 

, pytest , () . , :


ch3/b/tasks_proj/tests/func/ test_add_variety2.py

@pytest.fixture(params=tasks_to_try, ids=task_ids)
def b_task(request):
""" ."""
return request.param

def test_add_b(tasks_db, b_task):
""" b_task, ."""
task_id = tasks.add(b_task)
t_from_db = tasks.get(task_id)
assert equivalent(t_from_db, b_task)

:


 $ pytest -v test_add_variety2.py::test_add_b ===================== test session starts ====================== collected 4 items test_add_variety2.py::test_add_b[Task(sleep,None,True)] PASSED test_add_variety2.py::test_add_b[Task(wake,brian,False)] PASSED test_add_variety2.py::test_add_b[Task(breathe,BRIAN,True)] PASSED test_add_variety2.py::test_add_b[Task(exercise,BrIaN,False)] PASSED =================== 4 passed in 0.04 seconds =================== 

ids , , . , :


ch3/b/tasks_proj/tests/func/ test_add_variety2.py

def id_func(fixture_value):
""" ."""
t = fixture_value
return 'Task({},{},{})'.format(t.summary, t.owner, t.done)

@pytest.fixture(params=tasks_to_try, ids=id_func)
def c_task(request):
""" (id_func) ."""
return request.param

def test_add_c(tasks_db, c_task):
""" ."""
task_id = tasks.add(c_task)
t_from_db = tasks.get(task_id)
assert equivalent(t_from_db, c_task)

. Task, id_func() Task , namedtuple Task Task . , , :


 $ pytest -v test_add_variety2.py::test_add_c ===================== test session starts ====================== collected 4 items test_add_variety2.py::test_add_c[Task(sleep,None,True)] PASSED test_add_variety2.py::test_add_c[Task(wake,brian,False)] PASSED test_add_variety2.py::test_add_c[Task(breathe,BRIAN,True)] PASSED test_add_variety2.py::test_add_c[Task(exercise,BrIaN,False)] PASSED =================== 4 passed in 0.04 seconds =================== 

. , , . , !


Fixtures Tasks Project


, Tasks. TinyDB . , . , , , , TinyDB , MongoDB .


( ), , start_tasks_db() tasks_db_session :


ch3/b/tasks_proj/tests/conftest.py

""" ."""

import pytest
import tasks
from tasks import Task

@pytest.fixture(scope='session')
def tasks_db_session(tmpdir_factory):
""" , ."""
temp_dir = tmpdir_factory.mktemp('temp')
tasks.start_tasks_db(str(temp_dir), 'tiny')
yield
tasks.stop_tasks_db()

@pytest.fixture()
def tasks_db(tasks_db_session):
""" tasks."""
tasks.delete_all()

db_type start_tasks_db() . , :


tasks_proj/src/tasks/api.py

  def start_tasks_db(db_path, db_type): # type: (str, str) -None """  API  .""" if not isinstance(db_path, string_types): raise TypeError('db_path must be a string') global _tasksdb if db_type == 'tiny': import tasks.tasksdb_tinydb _tasksdb = tasks.tasksdb_tinydb.start_tasks_db(db_path) elif db_type == 'mongo': import tasks.tasksdb_pymongo _tasksdb = tasks.tasksdb_pymongo.start_tasks_db(db_path) else: raise ValueError("db_type   'tiny'  'mongo'") 

MongoDB, db_type mongo. :


ch3/c/tasks_proj/tests/conftest.py

  import pytest import tasks from tasks import Task # @pytest.fixture(scope='session', params=['tiny',]) @pytest.fixture(scope='session', params=['tiny', 'mongo']) def tasks_db_session(tmpdir_factory, request): """Connect to db before tests, disconnect after.""" temp_dir = tmpdir_factory.mktemp('temp') tasks.start_tasks_db(str(temp_dir), request.param) yield # this is where the testing happens tasks.stop_tasks_db() @pytest.fixture() def tasks_db(tasks_db_session): """An empty tasks db.""" tasks.delete_all() 

params=['tiny',' mongo'] -. request temp_db db_type request.param , "tiny" "mongo".


--verbose -v pytest , pytest . , .




Menginstal MongoDB




MongoDB, , MongoDB pymongo . MongoDB, https://www.mongodb.com/download-center . pymongo pip— pip install pymongo . MongoDB ; 7 .




:


  $ cd /path/to/code/ch3/c/tasks_proj $ pip install pymongo $ pytest -v --tb=no ===================== test session starts ====================== collected 92 items test_add.py::test_add_returns_valid_id[tiny] PASSED test_add.py::test_added_task_has_id_set[tiny] PASSED test_add.py::test_add_increases_count[tiny] PASSED test_add_variety.py::test_add_1[tiny] PASSED test_add_variety.py::test_add_2[tiny-task0] PASSED test_add_variety.py::test_add_2[tiny-task1] PASSED ... test_add.py::test_add_returns_valid_id[mongo] FAILED test_add.py::test_added_task_has_id_set[mongo] FAILED test_add.py::test_add_increases_count[mongo] PASSED test_add_variety.py::test_add_1[mongo] FAILED test_add_variety.py::test_add_2[mongo-task0] FAILED ... ============= 42 failed, 50 passed in 4.94 seconds ============= 

Hm . , , - Mongo. , pdb: , . 125. TinyDB.


Latihan


  1. test_fixtures.py .
    2. fixtures—functions @pytest.fixture() , . , , .
  2. , .
  3. , .
  4. pytest --setup-show test_fixtures.py . ?
  5. scope= 'module' 4.
  6. pytest --setup-show test_fixtures.py . Apa yang berubah?
  7. 6 return <data> yield <data> .
  8. yield .
  9. pytest -s -v test_fixtures.py . ?

Apa selanjutnya


pytest fixture , , building blocks , setup teardown , (, Mongo TinyDB). , , .


pytest, , (builtin) tmpdir tmpdir_factory. (builtin) .


Kembali Selanjutnya

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


All Articles