क्या पायथन में प्यारेस्ट के मुखर बयान व्यवहार को बदलना संभव है


18

मैं वास्तविक और अपेक्षित व्यवहार से मेल करने के लिए पायथन के कथन का उपयोग कर रहा हूं। मेरे पास इन पर नियंत्रण नहीं है जैसे कि एक त्रुटि परीक्षण के मामले हैं। मैं अभिकथन त्रुटि को नियंत्रित करना चाहता हूं और परिभाषित करना चाहता हूं कि क्या मैं असफलता पर परीक्षण को निरस्त करना चाहता हूं या नहीं।

इसके अलावा, मैं कुछ जोड़ना चाहता हूं जैसे कि यदि कोई त्रुटि है तो परीक्षण मामले को रोक दिया जाना चाहिए और उपयोगकर्ता किसी भी क्षण फिर से शुरू कर सकता है।

मुझे नहीं पता कि यह कैसे करना है

कोड उदाहरण, हम यहाँ pytest का उपयोग कर रहे हैं

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

Below is my expectation

जब मुखर एक जोर से फेंकता है, तो मेरे पास टेस्टकेस को रोकने का एक विकल्प होना चाहिए और बाद में फिर से शुरू हो सकता है। ठहराव और फिर से शुरू करने के लिए मैं tkinterमॉड्यूल का उपयोग करूंगा । मैं नीचे के रूप में एक मुखर कार्य करूँगा

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)

आगे बढ़ते हुए मुझे इस फ़ंक्शन के साथ प्रत्येक अभिकथन कथन को बदलना होगा

import pytest
def test_abc():
    a = 10
    # Suppose some code and below is the assert statement 
    _assertCustom("assert a == 10, 'error message'")

यह मेरे लिए बहुत अधिक प्रयास है क्योंकि मुझे उन हजारों स्थानों पर परिवर्तन करना है जहां मैंने मुखर उपयोग किया है। क्या इसमें कोई आसान तरीका हैpytest

Summary:मुझे कुछ ऐसा चाहिए जहां मैं असफलता पर टेस्टकेस को रोक सकूं और फिर डिबगिंग के बाद फिर से शुरू कर सकूं। मुझे इसके बारे में पता है tkinterऔर यही कारण है कि मैंने इसका उपयोग किया है। किसी भी अन्य विचारों का स्वागत किया जाएगा

Note: उपरोक्त कोड अभी तक परीक्षण नहीं किया गया है। छोटी वाक्यविन्यास त्रुटियाँ भी हो सकती हैं

संपादित करें: उत्तर के लिए धन्यवाद। इस सवाल को थोड़ा आगे बढ़ाएं। क्या होगा अगर मैं मुखर के व्यवहार को बदलना चाहता हूं। वर्तमान में जब वहाँ एक जोर त्रुटि परीक्षण बाहर निकलता है। क्या होगा अगर मैं चुनना चाहता हूं कि मुझे विशेष रूप से मुखर विफलता पर टेस्टकेस निकास की आवश्यकता है या नहीं। मैं ऊपर बताए अनुसार कस्टम एस्टर फ़ंक्शन लिखना नहीं चाहता क्योंकि इस तरह से मुझे कई स्थानों पर बदलना होगा


3
क्या आप हमें एक कोड उदाहरण दे सकते हैं कि आप क्या करना चाहते हैं?
mrblewog

1
उपयोग न करें assertलेकिन अपने स्वयं के चेकिंग कार्यों को लिखें जो आप चाहते हैं।
molbdnilo

तुम क्यों सम्मिलित न ज़ोर में कोशिश ब्लॉक और में त्रुटि संदेश को छोड़कर ?
प्रातिक कीनी

1
ऐसा लगता है कि आप वास्तव में जो चाहते हैं वह pytestआपके परीक्षण मामलों के लिए उपयोग करना है । यह कई और विशेषताओं के साथ-साथ मुखर और लंघन परीक्षणों का उपयोग करने का समर्थन करता है जो लेखन परीक्षण को आसान बनाते हैं।
ब्लबरडाइब्लूब

1
क्या यह एक सरल उपकरण लिखने के लिए बहुत सरल नहीं होगा जो यंत्रवत् assert cond, "msg"आपके कोड में हर जगह बदल देगा _assertCustom("assert cond, 'msg'")? संभवतः एक sed-लाइनर ऐसा कर सकता था।
एनपीई

जवाबों:


23

आप उपयोग कर रहे हैं pytest, जो आपको असफल परीक्षणों के साथ बातचीत करने के लिए पर्याप्त विकल्प देता है। यह आपको कमांड लाइन विकल्प और यह संभव बनाने के लिए कई हुक देता है। मैं बताता हूं कि प्रत्येक का उपयोग कैसे करें और आप अपनी विशिष्ट डीबगिंग आवश्यकताओं को फिट करने के लिए कस्टमाइज़ेशन कहां कर सकते हैं।

मैं और भी अधिक विदेशी विकल्पों में जाऊँगा, जो आपको पूरी तरह से विशिष्ट कथनों को छोड़ने की अनुमति देंगे, अगर आपको वास्तव में लगता है कि आपको अवश्य करना चाहिए।

हैंडल अपवाद, मुखर नहीं

ध्यान दें कि एक असफल परीक्षण सामान्य रूप से पाइस्टेस्ट को नहीं रोकता है; यदि आपने स्पष्ट रूप से सक्षम किया है तो केवल निश्चित संख्या में विफलताओं के बाद बाहर निकलने के लिए कहें । इसके अलावा, परीक्षण विफल होते हैं क्योंकि एक अपवाद उठाया जाता है; assertउठाता है, AssertionErrorलेकिन यह एकमात्र अपवाद नहीं है जो परीक्षण को विफल कर देगा! आप नियंत्रित करना चाहते हैं कि अपवादों को कैसे नियंत्रित किया जाए, परिवर्तन नहीं assert

हालांकि, एक असफल अभिकथन व्यक्तिगत परीक्षा को समाप्त कर देगा । ऐसा इसलिए है क्योंकि एक बार एक try...exceptब्लॉक के बाहर एक अपवाद खड़ा हो जाने के बाद , पायथन वर्तमान फ़ंक्शन फ़्रेम को खोल देता है, और उस पर वापस नहीं जाता है।

मुझे नहीं लगता कि जैसा आप चाहते हैं, वैसा ही _assertCustom()प्रयास फिर से चलाने के लिए आपके प्रयासों के विवरण को देखते हुए , लेकिन मैं आपके विकल्पों पर फिर भी चर्चा करूंगा।

पीडीबी के साथ पाइस्टेस्ट में पोस्टमार्टम डिबगिंग

डिबगर में विफलताओं को संभालने के विभिन्न विकल्पों के लिए, मैं --pdbकमांड-लाइन स्विच के साथ शुरू करूंगा , जो एक परीक्षण विफल होने पर मानक डिबगिंग प्रॉम्प्ट खोलता है (संक्षिप्तता के लिए आउटपुट)

$ 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
[ ... ]

इस स्विच के साथ, जब एक परीक्षण विफल हो जाता है तो पाइस्टेस्ट पोस्टमार्टम डिबगिंग सत्र शुरू करता है । यह अनिवार्य रूप से ठीक वही है जो आप चाहते थे; एक असफल परीक्षण के बिंदु पर कोड को रोकने के लिए और अपने परीक्षण की स्थिति पर एक नज़र डालने के लिए डिबगर खोलें। आप परीक्षण के स्थानीय चर, ग्लोबल्स और स्टैक में हर फ्रेम के स्थानीय और ग्लोबल्स के साथ बातचीत कर सकते हैं।

यहाँ पाइस्टेस्ट आपको इस बिंदु के बाद बाहर निकलने के लिए पूर्ण नियंत्रण प्रदान करता है या नहीं: यदि आप qपद छोड़ते हैं, तो पाइस्टेस्ट रन को भी बाहर निकालता है, cजारी रखने के लिए उपयोग करने से पाइस्टेस्ट पर नियंत्रण वापस आ जाएगा और अगला परीक्षण निष्पादित हो जाता है।

वैकल्पिक डीबगर का उपयोग करना

आप इसके लिए pdbडीबगर के लिए बाध्य नहीं हैं ; आप --pdbclsस्विच के साथ एक अलग डीबगर सेट कर सकते हैं । कोई भी pdb.Pdb()संगत कार्यान्वयन कार्य करेगा, जिसमें IPython डीबगर कार्यान्वयन , या अधिकांश अन्य पायथन डीबगर ( pudb डीबगर को -sस्विच का उपयोग करने की आवश्यकता है, या एक विशेष प्लगइन ) शामिल है। स्विच एक मॉड्यूल और क्लास लेता है, उदाहरण के लिए pudbआप उपयोग कर सकते हैं:

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

आप इस सुविधा का उपयोग चारों ओर अपने स्वयं के आवरण वर्ग लिखने के लिए कर सकता है Pdbकि बस तुरंत वापस लौट यदि विशिष्ट विफलता कुछ में रुचि रखते हैं नहीं है। pytestउपयोग करता है Pdb()की तरह वास्तव में pdb.post_mortem()करता है :

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

यहाँ, tएक ट्रेसबैक ऑब्जेक्ट है । जब p.interaction(None, t)रिटर्न मिलता है, pytestतो अगले परीक्षण के साथ जारी रहता है, जब तक p.quitting कि सेट नहीं किया जाता है True(जिस बिंदु पर पहले तो बाहर निकलता है)।

यहां एक उदाहरण कार्यान्वयन है जो प्रिंट करता है कि हम डिबग में कमी कर रहे हैं और तुरंत वापस लौटते हैं, जब तक कि परीक्षण नहीं उठाया ValueErrorजाता है 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)

जब मैं उपरोक्त डेमो के साथ इसका उपयोग करता हूं, तो यह आउटपुट (फिर से संक्षिप्तता के लिए) है:

$ 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)

उपरोक्त इंट्रोस्पेक्ट्स sys.last_typeयह निर्धारित करने के लिए कि विफलता 'दिलचस्प' है या नहीं।

हालाँकि, मैं वास्तव में इस विकल्प की अनुशंसा नहीं कर सकता जब तक कि आप tkInter या कुछ समान का उपयोग करके अपना डिबगर नहीं लिखना चाहते। ध्यान दें कि यह एक बड़ा उपक्रम है।

फ़िल्टरिंग विफलताओं; डिबगर खोलने के लिए चुनें और चुनें

अगले स्तर तक पाइटेस्ट डिबगिंग और इंटरैक्शन हुक हैं ; ये व्यवहार के अनुकूलन के लिए हुक पॉइंट हैं, यह बदलने या बढ़ाने के लिए कि कैसे सामान्य रूप से किसी अपवाद को संभालने pdb.set_trace()या breakpoint()( या पायथन 3.7 या नए) के माध्यम से डिबगर में प्रवेश करने जैसी चीजें सामान्य रूप से संभालती हैं ।

इस हुक का आंतरिक कार्यान्वयन >>> entering PDB >>>ऊपर के बैनर को भी प्रिंट करने के लिए जिम्मेदार है , इसलिए डिबगर को चलने से रोकने के लिए इस हुक का उपयोग करने का मतलब है कि आप इस आउटपुट को बिल्कुल नहीं देखेंगे। आपके पास अपना हुक हो सकता है तब मूल हुक पर प्रतिनिधि कर सकते हैं जब एक परीक्षण विफलता 'दिलचस्प' है, और इसलिए आप उपयोग कर रहे डीबगर से स्वतंत्र फ़िल्टर परीक्षण विफलताओं ! आप आंतरिक कार्यान्वयन को नाम से एक्सेस करके एक्सेस कर सकते हैं ; इसके लिए आंतरिक हुक प्लगइन का नाम दिया गया है pdbinvoke। इसे चलाने से रोकने के लिए आपको इसे अपंजीकृत करने की आवश्यकता है लेकिन एक संदर्भ को बचाने के लिए हम इसे आवश्यकतानुसार सीधे कॉल कर सकते हैं।

इस तरह के हुक का एक नमूना कार्यान्वयन यहां दिया गया है; आप इसे किसी भी स्थान पर रख सकते हैं प्लगइन्स लोड किए गए हैं ; मैंने इसे इसमें डाला 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)

इसके बाद के संस्करण प्लगइन आंतरिक का उपयोग करता है TerminalReporterप्लगइन बाहर टर्मिनल के लिए लाइनों में लिखने के लिए; यह डिफ़ॉल्ट कॉम्पैक्ट परीक्षण स्थिति प्रारूप का उपयोग करते समय आउटपुट क्लीनर बनाता है, और आपको सक्षम कैप्चरिंग आउटपुट के साथ टर्मिनल पर चीजें लिखने देता है।

उदाहरण pytest_exception_interactएक और हुक के माध्यम से हुक के साथ प्लगइन ऑब्जेक्ट को पंजीकृत करता है pytest_configure(), लेकिन यह सुनिश्चित करता है कि यह @pytest.hookimpl(trylast=True)आंतरिक pdbinvokeप्लगइन को पंजीकृत करने में सक्षम होने के लिए पर्याप्त (उपयोग ) देर से चलता है । जब हुक कहा जाता है, उदाहरण call.exceptinfoवस्तु के खिलाफ परीक्षण करता है ; आप नोड या रिपोर्ट भी देख सकते हैं।

उपरोक्त नमूना कोड के साथ demo/conftest.py, test_hamपरीक्षण की विफलता को नजरअंदाज कर दिया जाता है, केवल test_spamपरीक्षण विफलता, जो उठती है ValueError, जिसके परिणामस्वरूप डेबिट प्रॉम्प्ट को रोका जाता है:

$ 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) 

पुन: पुनरावृति करने के लिए, उपर्युक्त दृष्टिकोण में अतिरिक्त लाभ है कि आप इसे किसी भी डीबगर के साथ जोड़ सकते हैं जो प्यूडस्ट के साथ काम करता है , जिसमें पुडब या आईपीथॉन डीबगर शामिल हैं:

$ 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>

यह भी अधिक संदर्भ है कि क्या परीक्षण चल रहा था ( nodeतर्क के माध्यम से ) और उठाए गए अपवाद के प्रत्यक्ष उपयोग ( call.excinfo ExceptionInfoउदाहरण के माध्यम से )।

ध्यान दें कि विशिष्ट pytest डिबगर प्लगइन्स (जैसे pytest-pudbया pytest-pycharm) अपने स्वयं के रजिस्टर pytest_exception_interacthooksp। एक और अधिक पूर्ण कार्यान्वयन को प्लग-मैनेजर में सभी प्लगइन्स को लूप करना होगा ताकि प्रत्येक प्लग को स्वचालित रूप से उपयोग करके config.pluginmanager.list_name_pluginऔर hasattr()परीक्षण करने के लिए , मनमाने ढंग से प्लगइन्स को ओवरराइड किया जा सके।

असफलताएं पूरी तरह से दूर कर देती हैं

हालांकि यह आपको विफल परीक्षण डीबगिंग पर पूर्ण नियंत्रण देता है, फिर भी यह परीक्षण को विफल कर देता है, भले ही आपने किसी दिए गए परीक्षण के लिए डिबगर को खोलने का विकल्प न चुना हो। यदि आप असफलताओं को पूरी तरह से दूर करना चाहते हैं, तो आप एक अलग हुक का उपयोग कर सकते हैं pytest_runtest_call():।

जब पाइस्टेस्ट परीक्षण चलाता है, तो यह उपरोक्त हुक के माध्यम से परीक्षण चलाएगा, जिसके वापस लौटने Noneया अपवाद बढ़ाने की उम्मीद है। इससे एक रिपोर्ट बनाई जाती है, वैकल्पिक रूप से एक लॉग प्रविष्टि बनाई जाती है, और यदि परीक्षण विफल हो जाता है, तो उपरोक्त pytest_exception_interact()हुक कहा जाता है। तो आपको बस इतना करने की आवश्यकता है कि यह हुक क्या परिणाम देता है; एक अपवाद के बजाय इसे कुछ भी वापस नहीं करना चाहिए।

सबसे अच्छा तरीका है कि एक हुक आवरण का उपयोग करना है । हुक रैपरों को वास्तविक काम करने की ज़रूरत नहीं है, लेकिन इसके बजाय एक हुक के परिणाम के साथ क्या होता है, इसे बदलने का मौका दिया जाता है। आपको बस लाइन जोड़ना है:

outcome = yield

आपके हुक आवरण कार्यान्वयन में और आपको हुक परिणाम तक पहुँच मिलती है , जिसमें परीक्षण अपवाद भी शामिल है outcome.excinfo। यह विशेषता परीक्षण के अपवाद को उठाए जाने पर (प्रकार, उदाहरण, ट्रेसबैक) के टपल पर सेट है। वैकल्पिक रूप से, आप outcome.get_result()मानक try...exceptहैंडलिंग को कॉल और उपयोग कर सकते हैं ।

तो आप असफल परीक्षा पास कैसे करते हैं? आपके पास 3 मूल विकल्प हैं:

  • आप आवरण में कॉल करके परीक्षण को एक अपेक्षित विफलता के रूप में चिह्नित कर सकते हैं pytest.xfail()
  • आप आइटम को छोड़ के रूप में चिह्नित कर सकते हैं , जो बताता है कि परीक्षण को कॉल करके, पहली बार में कभी नहीं चलाया गया था pytest.skip()
  • आप outcome.force_result()विधि का उपयोग करके, अपवाद को हटा सकते हैं ; परिणाम को यहां एक खाली सूची में सेट करें (मतलब: पंजीकृत हुक कुछ भी नहीं उत्पादित None), और अपवाद पूरी तरह से साफ हो गया है।

आप जो उपयोग करते हैं वह आपके ऊपर है। पहले छोड़े गए और अपेक्षित-विफलता परीक्षणों के लिए परिणाम की जांच करना सुनिश्चित करें क्योंकि आपको उन मामलों को संभालने की आवश्यकता नहीं है जैसे कि परीक्षण विफल हो गया। आप इन विकल्पों के माध्यम से जुटाए गए विशेष अपवादों तक पहुंच सकते हैं pytest.skip.Exceptionऔर pytest.xfail.Exception

यहां एक उदाहरण कार्यान्वयन है जो विफल किए गए परीक्षणों को चिह्नित करता है ValueError, जैसे कि छोड़ दिया नहीं गया :

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")

जब conftest.pyउत्पादन में डाल दिया जाता है:

$ 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 ======================

मैंने -r aध्वज का उपयोग यह स्पष्ट करने के लिए किया test_hamथा कि अब इसे छोड़ दिया गया था।

यदि आप pytest.skip()कॉल को बदलते हैं pytest.xfail("[XFAIL] ignoring everything but ValueError"), तो परीक्षण को एक अपेक्षित विफलता के रूप में चिह्नित किया गया है:

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

और outcome.force_result([])इसे उत्तीर्ण करते हुए अंक का उपयोग करना:

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

यह आपके ऊपर है कि आपको कौन सा उपयोग करने का सबसे अच्छा मामला लगता है। के लिए skip()और xfail()मैंने मानक संदेश प्रारूप ( [NOTRUN]या इसके साथ उपसर्ग [XFAIL]) की नकल की, लेकिन आप अपने इच्छित किसी अन्य संदेश प्रारूप का उपयोग करने के लिए स्वतंत्र हैं।

सभी तीन मामलों में पाइस्टेस्ट उन परीक्षणों के लिए डिबगर नहीं खोलेगा जिनके परिणाम आपने इस पद्धति का उपयोग करके बदल दिए हैं।

अलग-अलग कथन बयान करना

यदि आप एकassert परीक्षण के भीतर परीक्षणों को बदलना चाहते हैं , तो आप अपने आप को एक बहुत अधिक काम के लिए स्थापित कर रहे हैं। हां, यह तकनीकी रूप से संभव है, लेकिन केवल उसी कोड को फिर से लिखना जिससे कि संकलन समय पर पायथन निष्पादित करने जा रहा है ।

जब आप उपयोग करते हैं pytest, तो यह वास्तव में पहले से ही किया जा रहा हैजब आपके मुखर विफल हो जाते हैं , तब आपको अधिक संदर्भ देने के लिए पाइएस्ट्स फिर से लिखता हैassert ; इस ब्लॉग पोस्ट को वास्तव में क्या किया जा रहा है, साथ ही _pytest/assertion/rewrite.pyस्रोत कोड के अच्छे अवलोकन के लिए देखें । ध्यान दें कि मॉड्यूल 1k लाइनों से अधिक लंबा है, और आपको यह समझने की आवश्यकता है कि पायथन के अमूर्त सिंटैक्स ट्री कैसे काम करते हैं। यदि आप ऐसा करते हैं, तो आप उस मॉड्यूल को अपने स्वयं के संशोधनों को जोड़ने के लिए एक हैंडलर के साथ आसपास के बंदरपंच कर सकते हैंasserttry...except AssertionError:

हालाँकि , आप केवल चयनात्मकता को अक्षम या अनदेखा नहीं कर सकते हैं, क्योंकि बाद के कथन आसानी से राज्य (विशिष्ट ऑब्जेक्ट व्यवस्था, चर सेट आदि) पर निर्भर हो सकते हैं, जो कि एक स्किप किए गए मुखर से रक्षा करने के लिए था। यदि एक मुखर परीक्षण है कि fooनहीं है None, तो बाद में मुखर foo.barअस्तित्व पर निर्भर करता है, तो आप बस AttributeErrorवहाँ एक में भाग जाएगा , आदि यदि आप इस मार्ग पर जाने की जरूरत है, अपवाद को फिर से बढ़ाने के लिए छड़ी।

मैं assertsयहाँ फिर से लिखने पर आगे विस्तार में नहीं जा रहा हूँ , क्योंकि मुझे नहीं लगता कि यह पीछा करने लायक है, इसमें शामिल काम की मात्रा नहीं दी गई है, और पोस्टमार्टम डिबगिंग के साथ आपको परीक्षण की स्थिति तक पहुंच प्रदान की गई है वैसे भी दावे की विफलता ।

ध्यान दें कि यदि आप ऐसा करना चाहते हैं, तो आपको उपयोग करने की आवश्यकता नहीं है eval()(जो वैसे भी काम नहीं करेगा, assertएक बयान है, इसलिए आपको exec()इसके बजाय उपयोग करने की आवश्यकता होगी ), और न ही आपको दो बार दावा करना होगा (जो अगर अभिव्यक्ति में परिवर्तन किया गया है तो मुद्दों को जन्म दे सकता है)। आप इसके बजाय ast.Assertएक नोड के अंदर नोड को एम्बेड करेंगे ast.Try, और एक हैंडलर को छोड़कर एक खाली ast.Raiseनोड का उपयोग करते हैं जो अपवाद पकड़ा गया था फिर से बढ़ाएं।

डीबगर का उपयोग करके कथन को छोड़ना।

पायथन डीबगर वास्तव में / कमांड का उपयोग करके आपको स्टेटमेंट्स को छोड़ देता है । यदि आप जानते हैं सामने , जो किसी विशेष जोर होगा विफल, आप इसे बाईपास के लिए इसका उपयोग कर सकते हैं। आप अपने परीक्षणों को चला सकते हैं , जो हर परीक्षण की शुरुआत में डिबगर को खोलता है , फिर एब्सटर के ठीक पहले डीबगर को रोकने पर इसे जारी करने की अनुमति दें।jjump--tracej <line after assert>

आप इसे स्वचालित भी कर सकते हैं। उपरोक्त तकनीकों का उपयोग करके आप एक कस्टम डिबगर प्लगइन बना सकते हैं

  • अपवाद pytest_testrun_call()को पकड़ने के लिए हुक का उपयोग करता AssertionErrorहै
  • ट्रेसबैक से लाइन 'अपेंडिंग' लाइन नंबर को निकालता है, और शायद कुछ स्रोत कोड विश्लेषण के साथ एक सफल छलांग को निष्पादित करने के लिए आवश्यक जोर से पहले और बाद में लाइन नंबर निर्धारित करता है
  • परीक्षण फिर से चलाता है , लेकिन इस बार एक Pdbउपवर्ग का उपयोग करते हुए जो मुखर से पहले लाइन पर एक ब्रेकपॉइंट सेट करता है, और ब्रेकपाइंट हिट होने पर स्वचालित रूप से दूसरे पर एक कूद निष्पादित करता है, इसके बाद cजारी रहता है।

या, असफल होने के लिए प्रतीक्षा करने के बजाय, आप assertपरीक्षण में पाए गए प्रत्येक के लिए ब्रेकपॉइंट्स को स्वचालित कर सकते हैं (फिर से स्रोत कोड विश्लेषण का उपयोग करके, आप ast.Assertपरीक्षण के एक एएसटी में नोड्स के लिए पंक्ति संख्याओं को निकाल सकते हैं ), परीक्षण किए गए परीक्षण को निष्पादित करें। डीबगर लिपिबद्ध कमांड का उपयोग करके, और कमांड का उपयोग करके ही मुखर jumpको छोड़ें। आपको एक व्यापार करना होगा; डिबगर के तहत सभी परीक्षण चलाएं (जो दुभाषिया के रूप में धीमा है, प्रत्येक कथन के लिए एक ट्रेस फ़ंक्शन को कॉल करना है) या केवल इसे असफल परीक्षणों पर लागू करें और खरोंच से उन परीक्षणों को फिर से चलाने की कीमत का भुगतान करें।

इस तरह के एक प्लगइन बनाने के लिए बहुत काम होगा, मैं यहां एक उदाहरण लिखने वाला नहीं हूं, आंशिक रूप से क्योंकि यह किसी भी तरह एक उत्तर में फिट नहीं होगा, और आंशिक रूप से क्योंकि मुझे नहीं लगता कि यह समय के लायक है । मैं अभी डिबगर को खोलूंगा और जंप को मैन्युअल रूप से करूंगा। एक असफल मुखर या तो परीक्षण या कोड-अंडर-टेस्ट में बग को इंगित करता है, इसलिए आप समस्या को डीबग करने पर ध्यान केंद्रित कर सकते हैं।


7

आप बिल्कुल वही प्राप्त कर सकते हैं जो आप बिना किसी कोड संशोधन के pytest --pdb के साथ चाहते हैं

अपने उदाहरण के साथ:

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

साथ चलाएं - पीपीडीबी:

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)

जैसे ही एक परीक्षण विफल होता है, आप इसे बिलियन पायथन डीबगर के साथ डीबग कर सकते हैं। यदि आप डीबगिंग कर रहे हैं, तो आप continueबाकी परीक्षणों के साथ कर सकते हैं ।


क्या यह रुक जाएगा जब परीक्षण का मामला विफल हो जाता है या परीक्षण चरण विफल हो जाता है।
नितेश

कृपया लिंक किए गए डॉक्स देखें: doc.pytest.org/en/latest/…
gnvk

उत्कृष्ट विचार। लेकिन अगर --ddb, टेस्टकेस का उपयोग करें तो हर विफलता पर विराम लगेगा। क्या मैं रनटाइम में यह तय कर सकता हूं कि मैं किस असफलता की परीक्षा के मामले को रोकना चाहता हूं
नितेश

5

यदि आप PyCharm का उपयोग कर रहे हैं, तो जब भी कोई मुखर विफल रहता है तो आप निष्पादन को रोकने के लिए अपवाद ब्रेकअप जोड़ सकते हैं। देखें ब्रेकपॉइंट्स (CTRL-SHIFT-F8) का चयन करें और AssertionError के लिए एक ऑन-अप अपवाद हैंडलर जोड़ें। ध्यान दें कि यह परीक्षणों के निष्पादन को धीमा कर सकता है।

अन्यथा, यदि आप प्रत्येक असफल परीक्षण के अंत में रुकने का मन नहीं करते (ठीक इससे पहले कि त्रुटियां हैं) इस बिंदु पर जोर देने के बजाय विफल रहता है, तो आपके पास कुछ विकल्प हैं। हालांकि ध्यान दें कि इस बिंदु पर विभिन्न सफाई कोड, जैसे कि परीक्षण में खोली गई फाइलें बंद हो सकती हैं, पहले से ही चलाए जा सकते हैं। संभावित विकल्प हैं:

  1. आप ppest को --pdb विकल्प का उपयोग करके त्रुटियों पर डीबगर में छोड़ने के लिए कह सकते हैं ।

  2. आप निम्नलिखित डेकोरेटर को परिभाषित कर सकते हैं और प्रत्येक प्रासंगिक परीक्षण फ़ंक्शन को इसके साथ सजा सकते हैं। (किसी संदेश को लॉग करने के अलावा, आप इस बिंदु पर pdb.post_mortem भी शुरू कर सकते हैं , या फ्रेम के स्थानीय लोगों के साथ एक संवादात्मक कोड.नेटक्ट भी कर सकते हैं , जहां अपवाद उत्पन्न हुआ, जैसा कि इस उत्तर में वर्णित है ।)

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. आप मैन्युअल रूप से हर परीक्षण समारोह को सजाने के लिए नहीं करना चाहते हैं, आप के बजाय एक autouse स्थिरता कि निरीक्षण परिभाषित कर सकते हैं 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)

मुझे डेकोरेटर्स के साथ जवाब पसंद आया लेकिन यह गतिशील रूप से नहीं किया जा सकता है। जब मैं pause_on_assert करना चाहता हूं या नहीं तो मैं गतिशील रूप से नियंत्रण करना चाहता हूं। क्या इसका कोई समाधान है?
नितेश

किस तरह से गतिशील? हर जगह इसे सक्षम / अक्षम करने के लिए एक ही स्विच में? या प्रत्येक परीक्षण के लिए इसे नियंत्रित करने का कोई तरीका?
उड़ी ग्रांट

मान लीजिए मैं कुछ टेस्टकेस चला रहा हूं। बीच में मुझे असफलता पर विराम की आवश्यकता हुई। मैं स्विच को सक्षम करूंगा। बाद में किसी भी समय मुझे लगता है कि मुझे स्विच को अक्षम करना होगा।
नितेश

उत्तर में आपका डेकोरेटर: 2 मेरे लिए काम नहीं करेगा क्योंकि मेरे परीक्षण के मामले में कई जोर होंगे
नीतेश

एक 'स्विच' के संबंध में, आप यह pause_on_assertनिर्धारित करने के लिए फ़ाइल से पढ़ने के कार्यान्वयन को या तो अपडेट कर सकते हैं कि क्या रोकना है या नहीं।
उड़ी ग्रांट

4

एक साधारण समाधान, यदि आप विज़ुअल स्टूडियो कोड का उपयोग करने के इच्छुक हैं, तो सशर्त ब्रेकप्वाइंट का उपयोग किया जा सकता है ।

उदाहरण के लिए, आपको अपने दावे सेट करने की अनुमति होगी:

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

फिर अपनी मुखर लाइन में एक सशर्त ब्रेकपॉइंट जोड़ें जो केवल तभी टूटेगा जब आपका दावा विफल हो जाएगा:

यहां छवि विवरण दर्ज करें


@ नीतेश - मुझे लगता है कि यह समाधान आपकी सभी समस्याओं को हल कर देता है, आप केवल तब टूटते हैं जब कोई दावा विफल हो जाता है, आप कोड को तब डिबग कर सकते हैं और आप बाद में शेष परीक्षणों के साथ जारी रख सकते हैं ... यद्यपि यह स्थापित करने के लिए थोड़ा अधिक बोझिल है शुरू में
निक मार्टिन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.