Apakah mungkin untuk mengubah perilaku pernyataan pernyataan PyTest di Python

18

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 tkintermodul. 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 tkinterdan 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

Nitesh
sumber
3
Bisakah Anda memberi kami contoh kode tentang apa yang ingin Anda lakukan?
mrblewog
1
Jangan gunakan asserttetapi tulislah fungsi pemeriksaan Anda sendiri yang melakukan apa yang Anda inginkan.
molbdnilo
Mengapa Anda tidak memasukkan assert di blok try dan pesan kesalahan kecuali ?
Prathik Kini
1
Kedengarannya seperti apa yang benar-benar Anda inginkan untuk digunakan pytestpada kasus uji Anda. Mendukung menggunakan menegaskan dan melompat-lompat tes bersama dengan lebih banyak fitur yang membuat tulisan test suite lebih mudah.
blubberdiblub
1
Bukankah cukup mudah untuk menulis alat sederhana yang secara mekanis akan menggantikan setiap assert cond, "msg"kode Anda _assertCustom("assert cond, 'msg'")? Mungkin sedone-liner bisa melakukannya.
NPE

Jawaban:

23

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; assertmenimbulkan AssertionErrortetapi itu bukan satu-satunya pengecualian yang akan menyebabkan tes gagal! Anda ingin mengontrol bagaimana pengecualian ditangani, bukan mengubah assert.

Namun, pernyataan yang gagal akan mengakhiri tes individu. Itu karena sekali pengecualian dimunculkan di luar try...exceptblok, 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 --pdbsaklar baris perintah , yang membuka prompt debugging standar ketika tes gagal (keluaran dielompokan untuk singkatnya):

$ mkdir demo
$ touch demo/__init__.py
$ cat << EOF > demo/test_foo.py
> def test_ham():
>     assert 42 == 17
> def test_spam():
>     int("Vikings")
> EOF
$ pytest demo/test_foo.py --pdb
[ ... ]
test_foo.py:2: AssertionError
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> /.../demo/test_foo.py(2)test_ham()
-> assert 42 == 17
(Pdb) q
Exit: Quitting debugger
[ ... ]

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 qperintah berhenti maka pytest juga keluar dari proses, menggunakan cfor continue akan mengembalikan kontrol ke pytest dan tes berikutnya dijalankan.

Menggunakan debugger alternatif

Anda tidak terikat dengan pdbdebugger untuk ini; Anda dapat mengatur debugger yang berbeda dengan --pdbclssakelar. Setiap implementasi yang pdb.Pdb()kompatibel akan bekerja, termasuk implementasi debugger IPython , atau sebagian besar debugger Python lainnya ( debugger pudb mengharuskan -sswitch digunakan, atau plugin khusus ). Switch mengambil modul dan kelas, misalnya untuk menggunakan pudbAnda bisa menggunakan:

$ pytest -s --pdb --pdbcls=pudb.debugger:Debugger

Anda bisa menggunakan fitur ini untuk menulis kelas wrapper sekitar Anda sendiri Pdbyang hanya mengembalikan segera jika kegagalan tertentu bukanlah sesuatu yang Anda tertarik. pytestMenggunakan Pdb()persis seperti pdb.post_mortem()melakukan :

p = Pdb()
p.reset()
p.interaction(None, t)

Di sini, tadalah objek traceback . Ketika p.interaction(None, t)kembali, pytestlanjutkan dengan tes berikutnya, kecuali p.quitting diatur ke True(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 sebagai demo/custom_pdb.py:

import pdb, sys

class CustomPdb(pdb.Pdb):
    def interaction(self, frame, traceback):
        if sys.last_type is not None and not issubclass(sys.last_type, ValueError):
            print("Sorry, not interested in this failure")
            return
        return super().interaction(frame, traceback)

Ketika saya menggunakan ini dengan demo di atas, ini adalah output (sekali lagi, elided for brevity):

$ pytest test_foo.py -s --pdb --pdbcls=demo.custom_pdb:CustomPdb
[ ... ]
    def test_ham():
>       assert 42 == 17
E       assert 42 == 17

test_foo.py:2: AssertionError
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Sorry, not interested in this failure
F
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> traceback >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    def test_spam():
>       int("Vikings")
E       ValueError: invalid literal for int() with base 10: 'Vikings'

test_foo.py:4: ValueError
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> /.../test_foo.py(4)test_spam()
-> int("Vikings")
(Pdb)

Introspeksi di atas sys.last_typeuntuk 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()atau breakpoint()(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 bernama pdbinvoke. 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:

import pytest

@pytest.hookimpl(trylast=True)
def pytest_configure(config):
    # unregister returns the unregistered plugin
    pdbinvoke = config.pluginmanager.unregister(name="pdbinvoke")
    if pdbinvoke is None:
        # no --pdb switch used, no debugging requested
        return
    # get the terminalreporter too, to write to the console
    tr = config.pluginmanager.getplugin("terminalreporter")
    # create or own plugin
    plugin = ExceptionFilter(pdbinvoke, tr)

    # register our plugin, pytest will then start calling our plugin hooks
    config.pluginmanager.register(plugin, "exception_filter")

class ExceptionFilter:
    def __init__(self, pdbinvoke, terminalreporter):
        # provide the same functionality as pdbinvoke
        self.pytest_internalerror = pdbinvoke.pytest_internalerror
        self.orig_exception_interact = pdbinvoke.pytest_exception_interact
        self.tr = terminalreporter

    def pytest_exception_interact(self, node, call, report):
        if not call.excinfo. errisinstance(ValueError):
            self.tr.write_line("Sorry, not interested!")
            return
        return self.orig_exception_interact(node, call, report)

Plugin di atas menggunakan internal TerminalReporterPlugin 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_interactkait melalui kait lain pytest_configure(), tetapi pastikan itu berjalan cukup terlambat (menggunakan @pytest.hookimpl(trylast=True)) untuk dapat membatalkan pendaftaran pdbinvokeplugin internal . Ketika kait disebut, contoh menguji terhadap call.exceptinfoobjek ; Anda juga dapat memeriksa simpul atau laporan juga.

Dengan kode contoh di atas diterapkan demo/conftest.py, test_hamkegagalan pengujian diabaikan, hanya test_spamkegagalan pengujian, yang menimbulkan ValueError, menghasilkan pembukaan prompt debug:

$ pytest demo/test_foo.py --pdb
[ ... ]
demo/test_foo.py F
Sorry, not interested!

demo/test_foo.py F
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> traceback >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    def test_spam():
>       int("Vikings")
E       ValueError: invalid literal for int() with base 10: 'Vikings'

demo/test_foo.py:4: ValueError
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> /.../demo/test_foo.py(4)test_spam()
-> int("Vikings")
(Pdb) 

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:

$ pytest demo/test_foo.py --pdb --pdbcls=IPython.core.debugger:Pdb
[ ... ]
demo/test_foo.py F
Sorry, not interested!

demo/test_foo.py F
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> traceback >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    def test_spam():
>       int("Vikings")
E       ValueError: invalid literal for int() with base 10: 'Vikings'

demo/test_foo.py:4: ValueError
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> /.../demo/test_foo.py(4)test_spam()
      1 def test_ham():
      2     assert 42 == 17
      3 def test_spam():
----> 4     int("Vikings")

ipdb>

Ini juga memiliki lebih banyak konteks tentang tes apa yang sedang dijalankan (melalui nodeargumen) dan akses langsung ke pengecualian yang diajukan (via call.excinfo ExceptionInfoinstance).

Perhatikan bahwa plugin debugger pytest tertentu (seperti pytest-pudbatau pytest-pycharm) mendaftarkan pytest_exception_interacthooksp mereka sendiri . Implementasi yang lebih lengkap harus mengulang semua plugin di manajer-plugin untuk mengganti plugin yang sewenang-wenang, secara otomatis, menggunakan config.pluginmanager.list_name_plugindan hasattr()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 Noneatau 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:

outcome = yield

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 menelepon outcome.get_result()dan menggunakan try...exceptpenanganan standar .

Jadi, bagaimana Anda membuat lulus ujian yang gagal? Anda memiliki 3 opsi dasar:

  • Anda dapat menandai tes sebagai kegagalan yang diharapkan , dengan memanggil pytest.xfail()bungkusnya.
  • Anda dapat menandai item sebagai dilewati , yang berpura-pura bahwa tes tidak pernah berjalan sejak awal, dengan menelepon pytest.skip().
  • Anda dapat menghapus pengecualian, dengan menggunakan outcome.force_result()metode ini ; atur hasilnya ke daftar kosong di sini (artinya: kait terdaftar hanya menghasilkan apa-apa None), 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.Exceptiondan pytest.xfail.Exception.

Berikut ini contoh implementasi yang menandai tes gagal yang tidak naik ValueError, seperti yang dilewati :

import pytest

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_call(item):
    outcome = yield
    try:
        outcome.get_result()
    except (pytest.xfail.Exception, pytest.skip.Exception, pytest.exit.Exception):
        raise  # already xfailed,  skipped or explicit exit
    except ValueError:
        raise  # not ignoring
    except (pytest.fail.Exception, Exception):
        # turn everything else into a skip
        pytest.skip("[NOTRUN] ignoring everything but ValueError")

Ketika dimasukkan ke dalam conftest.pyoutput menjadi:

$ pytest -r a demo/test_foo.py
============================= test session starts =============================
platform darwin -- Python 3.8.0, pytest-3.10.0, py-1.7.0, pluggy-0.8.0
rootdir: ..., inifile:
collected 2 items

demo/test_foo.py sF                                                      [100%]

=================================== FAILURES ===================================
__________________________________ test_spam ___________________________________

    def test_spam():
>       int("Vikings")
E       ValueError: invalid literal for int() with base 10: 'Vikings'

demo/test_foo.py:4: ValueError
=========================== short test summary info ============================
FAIL demo/test_foo.py::test_spam
SKIP [1] .../demo/conftest.py:12: [NOTRUN] ignoring everything but ValueError
===================== 1 failed, 1 skipped in 0.07 seconds ======================

Saya menggunakan -r abendera untuk membuatnya lebih jelas yang test_hamdilewati sekarang.

Jika Anda mengganti pytest.skip()panggilan dengan pytest.xfail("[XFAIL] ignoring everything but ValueError"), tes ditandai sebagai kegagalan yang diharapkan:

[ ... ]
XFAIL demo/test_foo.py::test_ham
  reason: [XFAIL] ignoring everything but ValueError
[ ... ]

dan menggunakan outcome.force_result([])tanda itu sebagaimana diteruskan:

$ pytest -v demo/test_foo.py  # verbose to see individual PASSED entries
[ ... ]
demo/test_foo.py::test_ham PASSED                                        [ 50%]

Terserah Anda mana yang Anda rasa paling cocok untuk digunakan. Untuk skip()dan xfail()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 asserttes 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 ulang assertpernyataan 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.pykode 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 mengelilingi assertdengan try...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 footidak None, maka pernyataan yang kemudian bergantung pada foo.barada, maka Anda hanya akan mengalami di AttributeErrorsana, dll. Tetaplah untuk meningkatkan kembali pengecualian, jika Anda harus pergi rute ini.

Saya tidak akan masuk ke perincian lebih lanjut tentang penulisan ulang di assertssini, 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, assertadalah pernyataan, jadi Anda harus menggunakan exec()sebagai gantinya), atau Anda harus menjalankan pernyataan dua kali (yang dapat menyebabkan masalah jika ekspresi yang digunakan dalam pernyataan diubah diubah). Anda akan menanamkan ast.Assertsimpul di dalam ast.Trysimpul, dan melampirkan pengendali kecuali yang menggunakan ast.Raisesimpul 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 a j <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

  • menggunakan pytest_testrun_call()pengait untuk menangkap AssertionErrorpengecualian
  • mengekstrak nomor baris 'menyinggung' dari traceback, dan mungkin dengan beberapa analisis kode sumber menentukan nomor baris sebelum dan setelah pernyataan yang diperlukan untuk menjalankan lompatan yang berhasil
  • menjalankan tes lagi , tetapi kali ini menggunakan Pdbsubclass yang menetapkan breakpoint pada baris sebelum menegaskan, dan secara otomatis mengeksekusi lompatan ke yang kedua ketika breakpoint terkena, diikuti oleh cmelanjutkan.

Atau, alih-alih menunggu pernyataan gagal, Anda dapat mengotomatiskan pengaturan breakpoints untuk masing-masing yang assertditemukan dalam tes (sekali lagi menggunakan analisis kode sumber, Anda dapat dengan mudah mengekstraksi nomor baris untuk ast.Assertnode dalam AST tes), jalankan tes yang dinyatakan menggunakan perintah skrip debugger, dan gunakan jumpperintah 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.

Martijn Pieters
sumber
7

Anda dapat mencapai apa yang Anda inginkan tanpa benar-benar modifikasi kode apa pun dengan pytest --pdb .

Dengan contoh Anda:

import pytest
def test_abc():
    a = 9
    assert a == 10, "some error message"

Jalankan dengan --pdb:

py.test --pdb
collected 1 item

test_abc.py F
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> traceback >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    def test_abc():
        a = 9
>       assert a == 10, "some error message"
E       AssertionError: some error message
E       assert 9 == 10

test_abc.py:4: AssertionError
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> /private/tmp/a/test_abc.py(4)test_abc()
-> assert a == 10, "some error message"
(Pdb) p a
9
(Pdb)

Segera setelah tes gagal, Anda dapat men-debug dengan debugger python bawaan. Jika Anda selesai debugging, Anda bisa continuedengan sisa tes.

gnvk
sumber
Apakah ini akan berhenti ketika uji kasus gagal atau langkah uji gagal
Nitesh
Silakan periksa dokumen yang ditautkan: doc.pytest.org/en/latest/…
gnvk
Ide bagus Tetapi jika menggunakan --pdb, testcase akan berhenti pada setiap kegagalan. dapatkah saya memutuskan pada saat runtime di mana kegagalan saya ingin menghentikan uji kasus
Nitesh
5

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:

  1. Anda bisa memberi tahu pytest untuk menjatuhkan Anda ke kesalahan debug menggunakan opsi --pdb .

  2. 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 .)

from functools import wraps

def pause_on_assert(test_func):
    @wraps(test_func)
    def test_wrapper(*args, **kwargs):
        try:
            test_func(*args, **kwargs)
        except AssertionError as e:
            tkinter.messagebox.showinfo(e)
            # re-raise exception to make the test fail
            raise
    return test_wrapper

@pause_on_assert
def test_abc()
    a = 10
    assert a == 2, "some error message"
  1. Jika Anda tidak ingin menghiasi setiap fungsi pengujian secara manual, Anda dapat mendefinisikan fixture autouse yang memeriksa sys.last_value :
import sys

@pytest.fixture(scope="function", autouse=True)
def pause_on_assert():
    yield
    if hasattr(sys, 'last_value') and isinstance(sys.last_value, AssertionError):
        tkinter.messagebox.showinfo(sys.last_value)
Uri Granta
sumber
Saya suka jawabannya dengan dekorator tetapi itu tidak bisa dilakukan secara dinamis. Saya ingin mengontrol secara dinamis ketika saya ingin pause_on_assert atau tidak. Apakah ada solusi untuk ini?
Nitesh
Dinamis dengan cara apa? Seperti dalam satu saklar untuk mengaktifkan / menonaktifkannya di mana saja? Atau beberapa cara untuk mengendalikannya untuk setiap tes?
Uri Granta
Misalkan saya menjalankan beberapa testcases. Di tengah saya perlu berhenti pada kegagalan. Saya akan mengaktifkan sakelar. Nanti setiap saat saya merasa bahwa saya perlu menonaktifkan saklar.
Nitesh
Penghias Anda sebagai jawaban: 2 tidak akan bekerja untuk saya karena test case saya akan memiliki beberapa menegaskan
Nitesh
Mengenai 'switch', Anda dapat memperbarui implementasi pause_on_assertuntuk membaca dari file untuk memutuskan apakah akan berhenti sebentar atau tidak.
Uri Granta
4

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:

import pytest
def test_abc():
    a = 10
    assert a == 10, "some error message"

Kemudian tambahkan breakpoint kondisional di baris pernyataan Anda yang hanya akan rusak ketika pernyataan Anda gagal:

masukkan deskripsi gambar di sini

Nick Martin
sumber
#Nitesh - Saya pikir solusi ini menyelesaikan semua masalah Anda, Anda hanya istirahat ketika pernyataan gagal, Anda dapat men-debug kode di sana dan kemudian dan Anda dapat melanjutkan dengan tes yang tersisa setelah itu ... Meskipun itu sedikit lebih rumit untuk mengatur awalnya
Nick Martin