Saya menggunakan pernyataan pernyataan Python untuk mencocokkan perilaku aktual dan yang diharapkan. Saya tidak memiliki kontrol atas ini seolah-olah ada tes kasus kesalahan dibatalkan. Saya ingin mengendalikan kesalahan pernyataan dan ingin menentukan apakah saya ingin membatalkan testcase pada pernyataan kegagalan atau tidak.
Saya juga ingin menambahkan sesuatu seperti jika ada kesalahan pernyataan maka test case harus dijeda dan pengguna dapat melanjutkan kapan saja.
Saya tidak tahu bagaimana melakukan ini
Contoh kode, kami menggunakan pytest di sini
import pytest
def test_abc():
a = 10
assert a == 10, "some error message"
Below is my expectation
Ketika menegaskan melempar assertionError, saya harus memiliki opsi untuk menjeda testcase dan dapat men-debug dan kemudian melanjutkan. Untuk jeda dan melanjutkan, saya akan menggunakan tkinter
modul. Saya akan membuat fungsi yang menegaskan seperti di bawah ini
import tkinter
import tkinter.messagebox
top = tkinter.Tk()
def _assertCustom(assert_statement, pause_on_fail = 0):
#assert_statement will be something like: assert a == 10, "Some error"
#pause_on_fail will be derived from global file where I can change it on runtime
if pause_on_fail == 1:
try:
eval(assert_statement)
except AssertionError as e:
tkinter.messagebox.showinfo(e)
eval (assert_statement)
#Above is to raise the assertion error again to fail the testcase
else:
eval (assert_statement)
Ke depan saya harus mengubah setiap pernyataan dengan fungsi ini sebagai
import pytest
def test_abc():
a = 10
# Suppose some code and below is the assert statement
_assertCustom("assert a == 10, 'error message'")
Ini terlalu banyak usaha bagi saya karena saya harus membuat perubahan di ribuan tempat di mana saya telah menggunakan menegaskan. Apakah ada cara mudah untuk melakukannyapytest
Summary:
Saya membutuhkan sesuatu di mana saya dapat menjeda testcase pada kegagalan dan kemudian melanjutkan setelah debugging. Saya tahu tkinter
dan itulah alasan saya menggunakannya. Gagasan lain akan disambut
Note
: Kode di atas belum diuji. Mungkin ada kesalahan sintaksis kecil juga
Sunting: Terima kasih atas jawabannya. Memperpanjang pertanyaan ini sedikit di depan sekarang. Bagaimana jika saya ingin mengubah perilaku tegas. Saat ini ketika ada kesalahan testcase pernyataan keluar. Bagaimana jika saya ingin memilih apakah saya perlu keluar testcase pada kegagalan tertentu atau tidak. Saya tidak ingin menulis fungsi penegasan khusus seperti yang disebutkan di atas karena cara ini saya harus berubah di sejumlah tempat
assert
tetapi tulislah fungsi pemeriksaan Anda sendiri yang melakukan apa yang Anda inginkan.pytest
pada kasus uji Anda. Mendukung menggunakan menegaskan dan melompat-lompat tes bersama dengan lebih banyak fitur yang membuat tulisan test suite lebih mudah.assert cond, "msg"
kode Anda_assertCustom("assert cond, 'msg'")
? Mungkinsed
one-liner bisa melakukannya.Jawaban:
Anda menggunakan
pytest
, yang memberi Anda banyak pilihan untuk berinteraksi dengan tes gagal. Ini memberi Anda opsi baris perintah dan dan beberapa kait untuk memungkinkan ini. Saya akan menjelaskan cara menggunakan masing-masing dan di mana Anda dapat membuat penyesuaian agar sesuai dengan kebutuhan debugging khusus Anda.Saya juga akan membahas opsi-opsi yang lebih eksotis yang memungkinkan Anda untuk melewatkan pernyataan spesifik sepenuhnya, jika Anda benar-benar merasa harus melakukannya.
Tangani pengecualian, bukan menegaskan
Perhatikan bahwa tes gagal biasanya tidak menghentikan pytest; hanya jika Anda mengaktifkannya, katakan secara eksplisit untuk keluar setelah sejumlah kegagalan . Juga, tes gagal karena pengecualian dimunculkan;
assert
menimbulkanAssertionError
tetapi itu bukan satu-satunya pengecualian yang akan menyebabkan tes gagal! Anda ingin mengontrol bagaimana pengecualian ditangani, bukan mengubahassert
.Namun, pernyataan yang gagal akan mengakhiri tes individu. Itu karena sekali pengecualian dimunculkan di luar
try...except
blok, Python membuka kerangka fungsi saat ini, dan tidak ada akan kembali pada itu.Saya tidak berpikir bahwa itulah yang Anda inginkan, menilai dari deskripsi Anda tentang
_assertCustom()
upaya Anda untuk menjalankan kembali pernyataan itu, tetapi saya akan membahas opsi Anda lebih jauh ke bawah.Debugging post-mortem di pytest dengan pdb
Untuk berbagai opsi untuk menangani kegagalan dalam debugger, saya akan mulai dengan
--pdb
saklar baris perintah , yang membuka prompt debugging standar ketika tes gagal (keluaran dielompokan untuk singkatnya):Dengan saklar ini, ketika tes gagal, pytest memulai sesi debugging post-mortem . Ini pada dasarnya tepat seperti yang Anda inginkan; untuk menghentikan kode pada titik pengujian yang gagal dan buka debugger untuk melihat keadaan pengujian Anda. Anda dapat berinteraksi dengan variabel lokal tes, global, dan lokal dan global dari setiap frame di stack.
Di sini pytest memberi Anda kontrol penuh apakah akan keluar atau tidak setelah titik ini: jika Anda menggunakan
q
perintah berhenti maka pytest juga keluar dari proses, menggunakanc
for continue akan mengembalikan kontrol ke pytest dan tes berikutnya dijalankan.Menggunakan debugger alternatif
Anda tidak terikat dengan
pdb
debugger untuk ini; Anda dapat mengatur debugger yang berbeda dengan--pdbcls
sakelar. Setiap implementasi yangpdb.Pdb()
kompatibel akan bekerja, termasuk implementasi debugger IPython , atau sebagian besar debugger Python lainnya ( debugger pudb mengharuskan-s
switch digunakan, atau plugin khusus ). Switch mengambil modul dan kelas, misalnya untuk menggunakanpudb
Anda bisa menggunakan:Anda bisa menggunakan fitur ini untuk menulis kelas wrapper sekitar Anda sendiri
Pdb
yang hanya mengembalikan segera jika kegagalan tertentu bukanlah sesuatu yang Anda tertarik.pytest
MenggunakanPdb()
persis sepertipdb.post_mortem()
melakukan :Di sini,
t
adalah objek traceback . Ketikap.interaction(None, t)
kembali,pytest
lanjutkan dengan tes berikutnya, kecualip.quitting
diatur keTrue
(pada titik mana pytest kemudian keluar).Berikut adalah contoh implementasi yang mencetak bahwa kami menolak untuk debug dan segera kembali, kecuali jika tes dinaikkan
ValueError
, disimpan sebagaidemo/custom_pdb.py
:Ketika saya menggunakan ini dengan demo di atas, ini adalah output (sekali lagi, elided for brevity):
Introspeksi di atas
sys.last_type
untuk menentukan apakah kegagalan itu 'menarik'.Namun, saya tidak bisa merekomendasikan opsi ini kecuali Anda ingin menulis debugger Anda sendiri menggunakan tkInter atau yang serupa. Perhatikan bahwa itu adalah tugas besar.
Kegagalan penyaringan; pilih dan pilih kapan harus membuka debugger
Tingkat berikutnya adalah pytest debugging dan interaksi kait ; ini adalah poin kait untuk penyesuaian perilaku, untuk menggantikan atau meningkatkan bagaimana pytest biasanya menangani hal-hal seperti menangani pengecualian atau memasukkan debugger melalui
pdb.set_trace()
ataubreakpoint()
(Python 3.7 atau yang lebih baru).Implementasi internal dari hook ini juga bertanggung jawab untuk mencetak
>>> entering PDB >>>
banner di atas, jadi menggunakan hook ini untuk mencegah debugger berjalan berarti Anda tidak akan melihat output ini sama sekali. Anda dapat memiliki hook sendiri kemudian mendelegasikan ke hook asli ketika kegagalan tes 'menarik', dan karenanya kegagalan pengujian filter terlepas dari debugger yang Anda gunakan! Anda dapat mengakses implementasi internal dengan mengaksesnya dengan nama ; plugin kait internal untuk ini bernamapdbinvoke
. Untuk mencegahnya berjalan Anda harus membatalkan registrasi tetapi menyimpan referensi apakah kami dapat memanggilnya langsung sesuai kebutuhan.Berikut adalah contoh implementasi dari pengait tersebut; Anda dapat menempatkan ini di salah satu lokasi tempat plugin diambil ; Saya memasukkannya ke
demo/conftest.py
:Plugin di atas menggunakan internal
TerminalReporter
Plugin untuk menulis baris ke terminal; ini membuat output lebih bersih ketika menggunakan format status uji kompak standar, dan memungkinkan Anda menulis hal-hal ke terminal bahkan dengan menangkap keluaran diaktifkan.Contoh mendaftarkan objek plugin dengan
pytest_exception_interact
kait melalui kait lainpytest_configure()
, tetapi pastikan itu berjalan cukup terlambat (menggunakan@pytest.hookimpl(trylast=True)
) untuk dapat membatalkan pendaftaranpdbinvoke
plugin internal . Ketika kait disebut, contoh menguji terhadapcall.exceptinfo
objek ; Anda juga dapat memeriksa simpul atau laporan juga.Dengan kode contoh di atas diterapkan
demo/conftest.py
,test_ham
kegagalan pengujian diabaikan, hanyatest_spam
kegagalan pengujian, yang menimbulkanValueError
, menghasilkan pembukaan prompt debug:Untuk mengulanginya, pendekatan di atas memiliki keuntungan tambahan yang bisa Anda gabungkan dengan debugger apa pun yang bekerja dengan pytest , termasuk pudb, atau debugger IPython:
Ini juga memiliki lebih banyak konteks tentang tes apa yang sedang dijalankan (melalui
node
argumen) dan akses langsung ke pengecualian yang diajukan (viacall.excinfo
ExceptionInfo
instance).Perhatikan bahwa plugin debugger pytest tertentu (seperti
pytest-pudb
ataupytest-pycharm
) mendaftarkanpytest_exception_interact
hooksp mereka sendiri . Implementasi yang lebih lengkap harus mengulang semua plugin di manajer-plugin untuk mengganti plugin yang sewenang-wenang, secara otomatis, menggunakanconfig.pluginmanager.list_name_plugin
danhasattr()
untuk menguji setiap plugin.Membuat kegagalan hilang sama sekali
Meskipun ini memberi Anda kendali penuh atas debugging pengujian yang gagal, ini tetap meninggalkan pengujian sebagai gagal bahkan jika Anda memilih untuk tidak membuka debugger untuk tes yang diberikan. Jika Anda ingin membuat kegagalan pergi sama sekali, Anda dapat menggunakan hook yang berbeda:
pytest_runtest_call()
.Saat pytest menjalankan tes, itu akan menjalankan tes melalui hook di atas, yang diharapkan untuk mengembalikan
None
atau menaikkan pengecualian. Dari sini laporan dibuat, secara opsional entri log dibuat, dan jika tes gagal,pytest_exception_interact()
kait yang disebut. Jadi yang perlu Anda lakukan adalah mengubah apa yang dihasilkan oleh hook ini; bukannya pengecualian itu seharusnya tidak mengembalikan apa-apa sama sekali.Cara terbaik untuk melakukannya adalah dengan menggunakan pembungkus kait . Pembungkus kail tidak harus melakukan pekerjaan yang sebenarnya, tetapi sebaliknya diberi kesempatan untuk mengubah apa yang terjadi pada hasil kail. Yang harus Anda lakukan adalah menambahkan baris:
dalam implementasi pembungkus kait Anda dan Anda mendapatkan akses ke hasil kait , termasuk pengecualian tes via
outcome.excinfo
. Atribut ini diatur ke tuple of (type, instance, traceback) jika pengecualian dimunculkan dalam tes. Atau, Anda bisa meneleponoutcome.get_result()
dan menggunakantry...except
penanganan standar .Jadi, bagaimana Anda membuat lulus ujian yang gagal? Anda memiliki 3 opsi dasar:
pytest.xfail()
bungkusnya.pytest.skip()
.outcome.force_result()
metode ini ; atur hasilnya ke daftar kosong di sini (artinya: kait terdaftar hanya menghasilkan apa-apaNone
), dan pengecualian dihapus seluruhnya.Apa yang Anda gunakan terserah Anda. Pastikan untuk memeriksa hasilnya untuk tes yang dilewati dan yang diperkirakan gagal terlebih dahulu karena Anda tidak perlu menangani kasus tersebut seolah-olah tes gagal. Anda dapat mengakses pengecualian khusus yang dimunculkan oleh opsi ini melalui
pytest.skip.Exception
danpytest.xfail.Exception
.Berikut ini contoh implementasi yang menandai tes gagal yang tidak naik
ValueError
, seperti yang dilewati :Ketika dimasukkan ke dalam
conftest.py
output menjadi:Saya menggunakan
-r a
bendera untuk membuatnya lebih jelas yangtest_ham
dilewati sekarang.Jika Anda mengganti
pytest.skip()
panggilan denganpytest.xfail("[XFAIL] ignoring everything but ValueError")
, tes ditandai sebagai kegagalan yang diharapkan:dan menggunakan
outcome.force_result([])
tanda itu sebagaimana diteruskan:Terserah Anda mana yang Anda rasa paling cocok untuk digunakan. Untuk
skip()
danxfail()
saya meniru format pesan standar (diawali dengan[NOTRUN]
atau[XFAIL]
) tetapi Anda bebas menggunakan format pesan lain yang Anda inginkan.Dalam ketiga kasus, pytest tidak akan membuka debugger untuk pengujian yang hasilnya Anda ubah menggunakan metode ini.
Mengubah pernyataan pernyataan individu
Jika Anda ingin mengubah
assert
tes dalam suatu tes , maka Anda mempersiapkan diri untuk pekerjaan yang jauh lebih banyak. Ya, ini secara teknis memungkinkan, tetapi hanya dengan menulis ulang kode yang akan dieksekusi oleh Python pada waktu kompilasi .Ketika Anda menggunakan
pytest
, ini sebenarnya sudah dilakukan . Pytest menulis ulangassert
pernyataan untuk memberi Anda lebih banyak konteks ketika pernyataan Anda gagal ; lihat posting blog ini untuk ikhtisar yang baik tentang apa yang sedang dilakukan, serta_pytest/assertion/rewrite.py
kode sumbernya . Perhatikan bahwa modul itu panjangnya lebih dari 1k, dan mengharuskan Anda memahami cara kerja sintaksis abstrak Python . Jika ya, Anda dapat melakukan monkeypatch pada modul tersebut untuk menambahkan modifikasi Anda sendiri di sana, termasuk mengelilingiassert
dengantry...except AssertionError:
handler.Namun , Anda tidak bisa hanya menonaktifkan atau mengabaikan pernyataan secara selektif, karena pernyataan selanjutnya dapat dengan mudah bergantung pada status (pengaturan objek tertentu, variabel yang ditetapkan, dll.) Yang dinyatakan tidak dilewati untuk mencegahnya. Jika tes yang menyatakan
foo
tidakNone
, maka pernyataan yang kemudian bergantung padafoo.bar
ada, maka Anda hanya akan mengalami diAttributeError
sana, dll. Tetaplah untuk meningkatkan kembali pengecualian, jika Anda harus pergi rute ini.Saya tidak akan masuk ke perincian lebih lanjut tentang penulisan ulang di
asserts
sini, karena saya tidak berpikir ini layak untuk diusahakan, tidak diberi jumlah pekerjaan yang terlibat, dan dengan debugging post-mortem memberi Anda akses ke keadaan tes di titik kegagalan pernyataan pula .Perhatikan bahwa jika Anda ingin melakukan ini, Anda tidak perlu menggunakan
eval()
(yang tidak akan berhasil,assert
adalah pernyataan, jadi Anda harus menggunakanexec()
sebagai gantinya), atau Anda harus menjalankan pernyataan dua kali (yang dapat menyebabkan masalah jika ekspresi yang digunakan dalam pernyataan diubah diubah). Anda akan menanamkanast.Assert
simpul di dalamast.Try
simpul, dan melampirkan pengendali kecuali yang menggunakanast.Raise
simpul kosong memunculkan kembali pengecualian yang tertangkap.Menggunakan debugger untuk melewati pernyataan pernyataan.
Debugger Python sebenarnya memungkinkan Anda melewati pernyataan , menggunakan perintah
j
/jump
. Jika Anda tahu di muka bahwa pernyataan spesifik akan gagal, Anda dapat menggunakan ini untuk memotongnya. Anda dapat menjalankan tes Anda dengan--trace
, yang membuka debugger di awal setiap tes , lalu mengeluarkan aj <line after assert>
untuk melewatinya ketika debugger dijeda sebelum pernyataan tersebut.Anda bahkan dapat mengotomatisasi ini. Menggunakan teknik di atas Anda bisa membangun plugin kustom debugger itu
pytest_testrun_call()
pengait untuk menangkapAssertionError
pengecualianPdb
subclass yang menetapkan breakpoint pada baris sebelum menegaskan, dan secara otomatis mengeksekusi lompatan ke yang kedua ketika breakpoint terkena, diikuti olehc
melanjutkan.Atau, alih-alih menunggu pernyataan gagal, Anda dapat mengotomatiskan pengaturan breakpoints untuk masing-masing yang
assert
ditemukan dalam tes (sekali lagi menggunakan analisis kode sumber, Anda dapat dengan mudah mengekstraksi nomor baris untukast.Assert
node dalam AST tes), jalankan tes yang dinyatakan menggunakan perintah skrip debugger, dan gunakanjump
perintah untuk melewati pernyataan itu sendiri. Anda harus melakukan tradeoff; jalankan semua tes di bawah debugger (yang lambat karena penerjemah harus memanggil fungsi jejak untuk setiap pernyataan) atau hanya menerapkan ini pada tes yang gagal dan membayar harga menjalankan kembali tes-tes tersebut dari awal.Plugin semacam itu akan banyak pekerjaan yang harus dibuat, saya tidak akan menulis contoh di sini, sebagian karena itu tidak cocok dengan jawaban, dan sebagian karena saya tidak berpikir itu sepadan dengan waktu . Saya baru saja membuka debugger dan melakukan lompatan secara manual. Pernyataan gagal menunjukkan bug baik dalam tes itu sendiri atau kode-dalam-tes, jadi Anda mungkin juga hanya fokus pada debugging masalah.
sumber
Anda dapat mencapai apa yang Anda inginkan tanpa benar-benar modifikasi kode apa pun dengan pytest --pdb .
Dengan contoh Anda:
Jalankan dengan --pdb:
Segera setelah tes gagal, Anda dapat men-debug dengan debugger python bawaan. Jika Anda selesai debugging, Anda bisa
continue
dengan sisa tes.sumber
Jika Anda menggunakan PyCharm maka Anda dapat menambahkan Exception Breakpoint untuk menjeda eksekusi setiap kali suatu pernyataan gagal. Pilih View Breakpoints (CTRL-SHIFT-F8) dan tambahkan pengendali pengecualian yang sedang naik untuk AssertionError. Perhatikan bahwa ini dapat memperlambat pelaksanaan tes.
Jika tidak, jika Anda tidak keberatan berhenti pada akhir setiap tes gagal (tepat sebelum kesalahan) daripada pada titik pernyataan gagal, maka Anda memiliki beberapa opsi. Namun perlu dicatat bahwa pada titik ini berbagai kode pembersihan, seperti menutup file yang dibuka dalam pengujian, mungkin sudah dijalankan. Opsi yang memungkinkan adalah:
Anda bisa memberi tahu pytest untuk menjatuhkan Anda ke kesalahan debug menggunakan opsi --pdb .
Anda dapat menentukan dekorator berikut dan menghias setiap fungsi tes yang relevan dengannya. (Selain mencatat pesan, Anda juga dapat memulai pdb.post_mortem pada titik ini, atau bahkan kode interaktif. Berinteraksi dengan penduduk lokal frame tempat pengecualian berasal, seperti dijelaskan dalam jawaban ini .)
sumber
pause_on_assert
untuk membaca dari file untuk memutuskan apakah akan berhenti sebentar atau tidak.Salah satu solusi sederhana, jika Anda bersedia menggunakan Visual Studio Code, bisa dengan menggunakan breakpoint bersyarat .
Ini akan memungkinkan Anda untuk mengatur pernyataan Anda, misalnya:
Kemudian tambahkan breakpoint kondisional di baris pernyataan Anda yang hanya akan rusak ketika pernyataan Anda gagal:
sumber