เป็นไปได้หรือไม่ที่จะเปลี่ยนพฤติกรรมคำสั่งยืนยันของ PyTest ใน Python


18

ฉันกำลังใช้คำสั่งยืนยันงูหลามเพื่อให้ตรงกับพฤติกรรมที่เกิดขึ้นจริงและที่คาดหวัง ฉันไม่สามารถควบคุมสิ่งเหล่านี้ได้ราวกับว่ามีกรณีทดสอบข้อผิดพลาดเกิดขึ้น ฉันต้องการควบคุมข้อผิดพลาดในการยืนยันและต้องการกำหนดว่าฉันต้องการยกเลิกการทดสอบเมื่อล้มเหลวในการยืนยันหรือไม่

นอกจากนี้ฉันต้องการเพิ่มบางอย่างเช่นหากมีข้อผิดพลาดในการยืนยันแล้วกรณีทดสอบควรหยุดชั่วคราวและผู้ใช้สามารถกลับมาทำงานได้ตลอดเวลา

ฉันไม่มีความคิดใด ๆ วิธีการทำเช่นนี้

ตัวอย่างรหัสเราใช้ pytest ที่นี่

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

Below is my expectation

เมื่อ assert โยน assertionError ฉันควรมีตัวเลือกในการหยุด testcase และสามารถดีบักและดำเนินการต่อในภายหลัง สำหรับการหยุดชั่วคราวและดำเนินการต่อฉันจะใช้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: โค้ดข้างต้นยังไม่ได้ทำการทดสอบ อาจมีข้อผิดพลาดทางไวยากรณ์เล็กน้อยเช่นกัน

แก้ไข: ขอบคุณสำหรับคำตอบ ขยายคำถามนี้ไปข้างหน้าเล็กน้อยตอนนี้ ถ้าฉันต้องการเปลี่ยนพฤติกรรมการยืนยัน ปัจจุบันเมื่อมีข้อผิดพลาดในการยืนยันข้อยืนยันออก จะเกิดอะไรขึ้นถ้าฉันต้องการเลือกถ้าฉันต้องการออก testcase เมื่อความล้มเหลวยืนยันโดยเฉพาะหรือไม่ ฉันไม่ต้องการที่จะเขียนฟังก์ชั่นยืนยันที่กำหนดเองดังกล่าวข้างต้นเพราะวิธีนี้ฉันต้องเปลี่ยนจำนวนสถานที่


3
คุณสามารถให้รหัสตัวอย่างสิ่งที่คุณต้องการให้เราได้ไหม
mrblewog

1
อย่าใช้assertแต่เขียนฟังก์ชั่นการตรวจสอบของคุณเองที่ทำในสิ่งที่คุณต้องการ
molbdnilo

ทำไมคุณถึงไม่ใส่ข้อความยืนยันในtry block และข้อความแสดงข้อผิดพลาดยกเว้น ?
Prathik Kini

1
ดูเหมือนว่าสิ่งที่คุณต้องการคือการใช้pytestสำหรับกรณีทดสอบของคุณ รองรับการใช้การยืนยันและการข้ามการทดสอบพร้อมกับคุณสมบัติอื่น ๆ อีกมากมายที่ทำให้ชุดการทดสอบการเขียนง่ายขึ้น
blubberdiblub

1
มันจะไม่สวยตรงไปตรงมาในการเขียนเครื่องมือง่ายๆที่จะเข้ามาแทนที่โดยอัตโนมัติทุกassert cond, "msg"ในรหัสของคุณด้วย_assertCustom("assert cond, 'msg'")? อาจเป็นsedหนึ่งซับสามารถทำได้
NPE

คำตอบ:


23

คุณกำลังใช้pytestงานซึ่งจะช่วยให้คุณมีตัวเลือกมากมายในการโต้ตอบกับการทดสอบที่ล้มเหลว มันทำให้คุณมีตัวเลือกบรรทัดคำสั่งและตะขอหลายอย่างเพื่อให้เป็นไปได้ ฉันจะอธิบายวิธีใช้แต่ละจุดที่คุณสามารถปรับแต่งเพื่อให้เหมาะกับความต้องการในการดีบักเฉพาะของคุณ

ฉันจะไปยังตัวเลือกที่แปลกใหม่มากขึ้นซึ่งจะช่วยให้คุณสามารถข้ามการยืนยันที่เฉพาะเจาะจงทั้งหมดถ้าคุณรู้สึกว่าคุณต้อง

จัดการข้อยกเว้นไม่ยืนยัน

โปรดทราบว่าการทดสอบที่ล้มเหลวจะไม่หยุดโดยปกติ pytest เฉพาะในกรณีที่คุณเปิดใช้งานการแจ้งอย่างชัดเจนให้ออกหลังจากความล้มเหลวจำนวนหนึ่ง นอกจากนี้การทดสอบล้มเหลวเนื่องจากมีการยกข้อยกเว้น assertเพิ่มขึ้นAssertionErrorแต่นั่นไม่ใช่ข้อยกเว้นเพียงอย่างเดียวที่จะทำให้การทดสอบล้มเหลว! assertคุณต้องการที่จะควบคุมวิธีการข้อยกเว้นได้รับการจัดการไม่เปลี่ยนแปลง

อย่างไรก็ตามการยืนยันที่ล้มเหลวจะสิ้นสุดการทดสอบแต่ละรายการ นั่นเป็นเพราะเมื่อมีการยกข้อยกเว้นนอกtry...exceptบล็อก Python จะคลายเฟรมฟังก์ชันปัจจุบันและจะไม่มีการย้อนกลับไป

ฉันไม่คิดว่านั่นคือสิ่งที่คุณต้องการตัดสินโดยคำอธิบายของคุณเกี่ยว_assertCustom()กับความพยายามในการยืนยันอีกครั้ง แต่ฉันจะพูดถึงตัวเลือกของคุณต่อไป

การดีบักโพสต์ชันสูตรใน pytest ด้วย pdb

สำหรับตัวเลือกต่าง ๆ เพื่อจัดการกับความล้มเหลวในดีบักเกอร์ฉันจะเริ่มต้นด้วย--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
[ ... ]

ด้วยสวิตช์นี้เมื่อการทดสอบล้มเหลว pytest เริ่มชันสูตรศพเซสชั่นการแก้จุดบกพร่อง นี่คือสิ่งที่คุณต้องการอย่างแท้จริง เพื่อหยุดโค้ดที่จุดที่ทำการทดสอบที่ล้มเหลวและเปิดดีบักเกอร์เพื่อดูสถานะการทดสอบของคุณ คุณสามารถโต้ตอบกับตัวแปรท้องถิ่นของการทดสอบ, globals และ localals และ globals ของทุกเฟรมในสแต็ก

ที่นี่ pytest ช่วยให้คุณควบคุมได้อย่างสมบูรณ์ว่าจะออกจากจุดนี้หรือไม่: ถ้าคุณใช้qคำสั่ง quit จากนั้น pytest จะออกจากการรันด้วยการใช้cเพื่อดำเนินการต่อจะส่งคืนการควบคุมไปยัง pytest และทำการทดสอบครั้งต่อไป

ใช้ดีบักเกอร์สำรอง

คุณไม่ได้ผูกพันกับpdbดีบักเกอร์สำหรับสิ่งนี้ คุณสามารถตั้งค่าดีบักเกอร์ที่แตกต่างด้วย--pdbclsสวิตช์ การใช้งานที่pdb.Pdb()เข้ากันได้ใด ๆจะทำงานได้รวมถึงการใช้งานดีบักเกอร์ IPythonหรือดีบั๊ก Python อื่น ๆ ส่วนใหญ่ (ตัวดีบัก 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(ที่จุด pytest แล้วออก)

นี่คือตัวอย่างการใช้งานที่พิมพ์ออกมาว่าเราปฏิเสธที่จะแก้ไขข้อบกพร่องและส่งคืนทันทียกเว้นกรณีที่การทดสอบยกขึ้น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 หรือสิ่งที่คล้ายกัน โปรดทราบว่านั่นเป็นงานที่ยิ่งใหญ่

การกรองความล้มเหลว; เลือกและเลือกเวลาที่จะเปิดดีบักเกอร์

ระดับถัดไปคือการดีบัก pytest และการโต้ตอบการขอ ; สิ่งเหล่านี้เป็นจุดเชื่อมโยงสำหรับการปรับแต่งพฤติกรรมเพื่อแทนที่หรือปรับปรุงวิธีการจัดการโดยปกติของ pytest เช่นการจัดการข้อยกเว้นหรือการป้อนดีบักเกอร์ผ่านpdb.set_trace()หรือbreakpoint()(Python 3.7 หรือใหม่กว่า)

การนำไปใช้ภายในของ hook นี้รับผิดชอบการพิมพ์>>> entering PDB >>>แบนเนอร์ด้านบนด้วยดังนั้นการใช้ hook นี้เพื่อป้องกันไม่ให้ดีบักเกอร์ทำงานหมายความว่าคุณจะไม่เห็นผลลัพธ์นี้เลย คุณสามารถมีตะขอของคุณเองจากนั้นมอบหมายให้ตะขอเดิมเมื่อความล้มเหลวในการทดสอบนั้น 'น่าสนใจ' และดังนั้นความล้มเหลวในการทดสอบตัวกรองจึงไม่ขึ้นอยู่กับดีบักเกอร์ที่คุณใช้! คุณสามารถเข้าถึงการดำเนินงานภายในโดยการเข้าถึงโดยใช้ชื่อ ; ปลั๊กอิน hook ภายในสำหรับชื่อpdbinvokeนี้ ในการป้องกันไม่ให้ทำงานคุณจำเป็นต้องยกเลิกการลงทะเบียนแต่บันทึกการอ้างอิงเราสามารถเรียกมันได้โดยตรงตามต้องการ

นี่คือตัวอย่างการใช้งานของ hook เช่น; คุณสามารถวางสิ่งนี้ลงในปลั๊กอินตำแหน่งใดก็ได้ที่โหลดจาก ; ฉันใส่ไว้ใน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_interacthook ผ่านตะขออื่น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รวมถึง pudb หรือดีบักเกอร์ 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>

นอกจากนี้ยังมีบริบทอื่น ๆ อีกมากมายเกี่ยวกับการทดสอบที่เรียกใช้ (ผ่านการnodeโต้แย้ง) และการเข้าถึงโดยตรงไปยังข้อยกเว้นที่เกิดขึ้น (ผ่านcall.excinfo ExceptionInfoอินสแตนซ์)

โปรดทราบว่าปลั๊กอินดีบักเกอร์ pytest เฉพาะ (เช่นpytest-pudbหรือpytest-pycharm) ลงทะเบียนpytest_exception_interacthooksp ของตนเอง การใช้งานที่สมบูรณ์ยิ่งขึ้นจะต้องวนซ้ำปลั๊กอินทั้งหมดใน plugin-manager เพื่อแทนที่ปลั๊กอินใด ๆ โดยอัตโนมัติโดยใช้config.pluginmanager.list_name_pluginและhasattr()ทดสอบแต่ละปลั๊กอิน

ทำให้ความล้มเหลวหายไปโดยสิ้นเชิง

แม้ว่าสิ่งนี้จะช่วยให้คุณสามารถควบคุมการดีบักการทดสอบที่ล้มเหลวได้อย่างสมบูรณ์ แต่ก็ยังทำให้การทดสอบนั้นล้มเหลวแม้ว่าคุณจะเลือกที่จะไม่เปิดตัวดีบั๊กสำหรับการทดสอบที่ได้รับก็ตาม pytest_runtest_call()หากคุณต้องการที่จะทำให้ความล้มเหลวหายไปโดยสิ้นเชิงคุณสามารถทำให้การใช้ตะขอที่แตกต่างกัน

เมื่อ pytest ทำการทดสอบมันจะทำการทดสอบผ่านทาง hook ด้านบนซึ่งคาดว่าจะคืนNoneหรือเพิ่มข้อยกเว้น จากรายงานนี้ถูกสร้างขึ้นเป็นทางเลือกรายการบันทึกถูกสร้างขึ้นและหากการทดสอบล้มเหลวpytest_exception_interact()เบ็ดดังกล่าวจะถูกเรียกว่า ดังนั้นสิ่งที่คุณต้องทำคือเปลี่ยนสิ่งที่ผลลัพธ์ที่ตะขอนี้ผลิต แทนที่จะเป็นข้อยกเว้นไม่ควรส่งคืนสิ่งใดเลย

วิธีที่ดีที่สุดที่จะทำคือการใช้กระดาษห่อเบ็ด ฮุกห่อไม่จำเป็นต้องทำงานจริง แต่แทนที่จะได้รับโอกาสที่จะแก้ไขสิ่งที่เกิดขึ้นกับผลของตะขอ สิ่งที่คุณต้องทำคือเพิ่มบรรทัด:

outcome = yield

ในตะขอเสื้อคลุมการดำเนินงานและคุณจะได้รับการเข้าถึงผลเบ็ดoutcome.excinfoรวมทั้งยกเว้นการทดสอบผ่าน แอ็ตทริบิวต์นี้ถูกตั้งค่าเป็น tuple ของ (ชนิด, อินสแตนซ์, การติดตามกลับ) หากข้อยกเว้นถูกยกขึ้นในการทดสอบ อีกวิธีหนึ่งคุณสามารถโทรoutcome.get_result()และใช้การtry...exceptจัดการมาตรฐาน

ดังนั้นคุณจะทำการทดสอบผ่านที่ล้มเหลวได้อย่างไร คุณมี 3 ตัวเลือกพื้นฐาน:

  • คุณสามารถทำเครื่องหมายการทดสอบว่าเป็นความล้มเหลวที่คาดไว้โดยการโทรpytest.xfail()ใน wrapper
  • คุณสามารถทำเครื่องหมายรายการเป็นข้ามpytest.skip()ซึ่งอ้างว่าการทดสอบก็ไม่เคยทำงานในสถานที่แรกโดยการเรียก
  • คุณสามารถลบข้อยกเว้นโดยใช้outcome.force_result()วิธีการ ; ตั้งค่าผลลัพธ์เป็นรายการว่างที่นี่ (หมายถึง: hook ที่ลงทะเบียนไม่ได้ทำอะไรนอกจากNone) และข้อยกเว้นจะถูกล้างทั้งหมด

สิ่งที่คุณใช้นั้นขึ้นอยู่กับคุณ ตรวจสอบให้แน่ใจว่าได้ตรวจสอบผลลัพธ์สำหรับการทดสอบที่ข้ามและคาดว่าจะล้มเหลวก่อนเนื่องจากคุณไม่จำเป็นต้องจัดการกับกรณีเหล่านั้นราวกับว่าการทดสอบล้มเหลว คุณสามารถเข้าถึงข้อยกเว้นพิเศษเพิ่มตัวเลือกเหล่านี้ผ่านทางและpytest.skip.Exceptionpytest.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]) แต่คุณมีอิสระที่จะใช้รูปแบบข้อความอื่น ๆ ที่คุณต้องการ

ในทั้งสามกรณี pytest จะไม่เปิดดีบักเกอร์สำหรับการทดสอบที่คุณแก้ไขผลลัพธ์โดยใช้วิธีนี้

การปรับเปลี่ยนข้อความยืนยันแต่ละรายการ

หากคุณต้องการแก้ไขassertการทดสอบภายในการทดสอบคุณจะตั้งค่าตัวเองสำหรับการทำงานมากขึ้น ใช่มันเป็นไปได้ทางเทคนิคแต่เพียงแค่เขียนรหัสใหม่ที่ Python จะดำเนินการในเวลารวบรวมเท่านั้น

เมื่อคุณใช้pytestงานจริงสิ่งนี้กำลังดำเนินการอยู่ Pytest ปรับเปลี่ยนassertงบเพื่อให้คุณได้มากขึ้นเมื่อบริบทอ้างของคุณล้มเหลว ; ดูโพสต์บล็อกนี้สำหรับภาพรวมที่ดีของสิ่งที่จะถูกดำเนินการเช่นเดียวกับรหัสที่มา_pytest/assertion/rewrite.py โปรดทราบว่าโมดูลนั้นมีความยาวเกิน 1k บรรทัดและต้องการให้คุณเข้าใจว่าโครงสร้างแบบนามธรรมของ Pythonทำงานอย่างไร หากคุณทำเช่นนั้นคุณสามารถปรับแต่งโมดูลนั้นเพื่อเพิ่มการแก้ไขของคุณเองที่นั่นรวมถึงการล้อมรอบassertด้วยtry...except AssertionError:ตัวจัดการ

อย่างไรก็ตามคุณไม่สามารถปิดใช้งานหรือเพิกเฉยการอ้างสิทธิ์ที่เลือกได้เนื่องจากข้อความสั่งที่ตามมาอาจขึ้นอยู่กับสถานะ (การจัดเรียงวัตถุเฉพาะชุดตัวแปร ฯลฯ ) ที่การยืนยันที่ข้ามนั้นมีไว้เพื่อป้องกัน หากการทดสอบการยืนยันที่fooไม่ได้Noneมีการยืนยันในภายหลังอาศัยfoo.barอยู่แล้วคุณก็จะวิ่งเข้าไปที่AttributeErrorนั่น ฯลฯ ทำติดเพื่อยกข้อยกเว้นอีกครั้งถ้าคุณจำเป็นต้องไปเส้นทางนี้

ฉันจะไม่เข้าไปดูรายละเอียดเพิ่มเติมเกี่ยวกับการเขียนใหม่assertsที่นี่เนื่องจากฉันไม่คิดว่านี่จะเป็นสิ่งที่ควรทำไม่ได้รับปริมาณงานที่เกี่ยวข้อง จุดของความล้มเหลวการยืนยันอยู่แล้ว

โปรดทราบว่าหากคุณต้องการทำสิ่งนี้คุณไม่จำเป็นต้องใช้eval()(ซึ่งไม่สามารถใช้งานassertได้เป็นคำสั่งดังนั้นคุณจะต้องใช้exec()แทน) และคุณจะไม่ต้องทำการยืนยันสองครั้ง สามารถนำไปสู่ปัญหาหากการแสดงออกที่ใช้ในสถานะการเปลี่ยนแปลงการยืนยัน) คุณต้องฝังast.Assertโหนดภายในast.Tryโหนดแทนและแนบตัวจัดการยกเว้นที่ใช้ast.Raiseโหนดว่างอีกครั้งยกข้อยกเว้นที่ถูกจับ

การใช้ดีบักเกอร์เพื่อข้ามคำสั่งยืนยัน

งูหลามดีบักจริงช่วยให้คุณสามารถข้ามงบโดยใช้j/jumpคำสั่ง หากคุณรู้ล่วงหน้าว่าการยืนยันเฉพาะจะล้มเหลวคุณสามารถใช้วิธีนี้เพื่อข้ามมันได้ คุณสามารถเรียกใช้การทดสอบของคุณด้วย--traceซึ่งจะเปิดตัวดีบั๊กในตอนเริ่มต้นของการทดสอบทุกครั้งจากนั้นออก a j <line after assert>เพื่อข้ามเมื่อดีบักหยุดชั่วคราวก่อนที่จะยืนยัน

คุณสามารถทำได้โดยอัตโนมัติ การใช้เทคนิคด้านบนคุณสามารถสร้างปลั๊กอินดีบักเกอร์แบบกำหนดเองได้

  • ใช้pytest_testrun_call()hook เพื่อตรวจจับAssertionErrorข้อยกเว้น
  • แยกหมายเลขบรรทัด 'offending' ของบรรทัดออกจากการย้อนกลับและอาจมีการวิเคราะห์ซอร์สโค้ดกำหนดหมายเลขบรรทัดก่อนและหลังการยืนยันที่จำเป็นในการดำเนินการกระโดดที่ประสบความสำเร็จ
  • รันการทดสอบอีกครั้งแต่คราวนี้ใช้Pdbคลาสย่อยที่กำหนดเบรกพอยต์บนบรรทัดก่อนที่จะยืนยันและดำเนินการกระโดดโดยอัตโนมัติไปที่สองเมื่อตีเบรกพอยต์ตามด้วยการcดำเนินการต่อ

หรือแทนที่จะรอให้การยืนยันล้มเหลวคุณสามารถตั้งค่าเบรกพอยต์ให้โดยอัตโนมัติสำหรับแต่ละรายการที่assertพบในการทดสอบ (อีกครั้งโดยใช้การวิเคราะห์ซอร์สโค้ดคุณสามารถแยกหมายเลขบรรทัดสำหรับast.Assertโหนดใน AST ของการทดสอบเล็กน้อย) ดำเนินการทดสอบที่ถูกยืนยัน ใช้คำสั่งสคริปต์ดีบักเกอร์และใช้jumpคำสั่งเพื่อข้ามการยืนยันตัวเอง คุณจะต้องทำการแลกเปลี่ยน; เรียกใช้การทดสอบทั้งหมดภายใต้ดีบักเกอร์ (ซึ่งช้าเพราะล่ามจะต้องเรียกใช้ฟังก์ชันการติดตามสำหรับทุกคำสั่ง) หรือใช้สิ่งนี้กับการทดสอบที่ล้มเหลวและจ่ายราคาของการรันการทดสอบเหล่านั้นอีกครั้งตั้งแต่เริ่มต้น

ปลั๊กอินดังกล่าวจะเป็นมากในการทำงานเพื่อสร้างฉันไม่ได้จะเขียนตัวอย่างที่นี่ส่วนหนึ่งเป็นเพราะมันจะไม่พอดีกับคำตอบอยู่แล้วและส่วนหนึ่งเป็นเพราะผมไม่คิดว่ามันคุ้มค่าเวลา ฉันเพิ่งจะเปิดตัวดีบักและทำการกระโดดด้วยตนเอง การยืนยันที่ล้มเหลวเป็นการระบุข้อผิดพลาดในการทดสอบตัวเองหรือการทดสอบภายใต้รหัสดังนั้นคุณอาจเพียงแค่มุ่งเน้นไปที่การดีบักปัญหา


7

คุณสามารถบรรลุสิ่งที่คุณต้องการโดยไม่ต้องปรับเปลี่ยนรหัสอย่างใด ๆ กับpytest --pdb

ด้วยตัวอย่างของคุณ:

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

ทำงานด้วย --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)

ทันทีที่การทดสอบล้มเหลวคุณสามารถดีบักได้ด้วยตัวดีบักหลามในตัว หากคุณทำการดีบักเสร็จแล้วคุณสามารถcontinueทำการทดสอบที่เหลือได้


จะหยุดเมื่อกรณีทดสอบล้มเหลวหรือขั้นตอนการทดสอบล้มเหลว
Nitesh

โปรดตรวจสอบเอกสารที่เชื่อมโยง: doc.pytest.org/en/latest/…
gnvk

ความคิดที่ยอดเยี่ยม แต่ถ้าใช้ --pdb, testcase จะหยุดทุกความล้มเหลว ฉันสามารถตัดสินใจที่รันไทม์ที่ความล้มเหลวที่ฉันต้องการหยุดกรณีทดสอบชั่วคราว
Nitesh

5

หากคุณกำลังใช้ PyCharm คุณสามารถเพิ่มจุดพักข้อยกเว้นเพื่อหยุดการประมวลผลเมื่อใดก็ตามที่การยืนยันล้มเหลว เลือกมุมมองเบรกพอยต์ (CTRL-SHIFT-F8) และเพิ่มตัวจัดการข้อยกเว้นแบบยกสำหรับ AssertionError โปรดทราบว่าการดำเนินการนี้อาจทำให้การทดสอบช้าลง

มิฉะนั้นถ้าคุณไม่รังเกียจที่จะหยุดเมื่อสิ้นสุดการทดสอบที่ล้มเหลวแต่ละครั้ง (ก่อนที่จะเกิดข้อผิดพลาด) แทนที่จะไปที่จุดที่การยืนยันล้มเหลวคุณจะมีตัวเลือกน้อย อย่างไรก็ตามโปรดทราบว่า ณ จุดนี้รหัสการทำความสะอาดต่างๆเช่นการปิดไฟล์ที่เปิดในการทดสอบอาจได้รับการเรียกใช้แล้ว ตัวเลือกที่เป็นไปได้คือ:

  1. คุณสามารถบอกได้ว่าจะลดลง pytest คุณเข้าสู่บั๊กข้อผิดพลาดโดยใช้ตัวเลือก --pdb

  2. คุณสามารถกำหนดมัณฑนากรต่อไปนี้และตกแต่งฟังก์ชั่นการทดสอบที่เกี่ยวข้องได้ (นอกเหนือจากการเข้าสู่ระบบข้อความคุณยังสามารถเริ่มต้นpdb.post_mortemที่จุดนี้หรือแม้กระทั่งการโต้ตอบcode.interactกับชาวบ้านของกรอบที่ยกเว้นมาตามที่อธิบายไว้ในคำตอบนี้ .)

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 หรือไม่ มีวิธีแก้ไขปัญหานี้หรือไม่?
Nitesh

แบบไดนามิกในทางใด ในสวิตช์เดียวเพื่อเปิด / ปิดการใช้งานทุกที่? หรือวิธีที่จะควบคุมมันสำหรับการทดสอบแต่ละครั้ง?
Uri Granta

สมมติว่าฉันกำลังใช้งานบางส่วนทดสอบ ในช่วงกลางฉันต้องการหยุดพักชั่วคราวเมื่อล้มเหลว ฉันจะเปิดใช้งานสวิตช์ หลังจากนั้นทุกช่วงเวลาฉันรู้สึกว่าฉันต้องปิดสวิตช์
Nitesh

มัณฑนากรของคุณในคำตอบ: 2 จะไม่ทำงานสำหรับฉันเพราะกรณีทดสอบของฉันจะมีการยืนยันหลายครั้ง
Nitesh

เกี่ยวกับ 'สวิตช์' คุณสามารถอัปเดตการใช้งานpause_on_assertเพื่ออ่านจากไฟล์เพื่อตัดสินใจว่าจะหยุดชั่วคราวหรือไม่
Uri Granta

4

หนึ่งวิธีง่ายๆถ้าคุณยินดีที่จะใช้ Visual Studio รหัสอาจจะใช้จุดพักตามเงื่อนไข

สิ่งนี้จะช่วยให้คุณตั้งค่าการยืนยันของคุณตัวอย่างเช่น:

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

จากนั้นเพิ่มเบรกพอยต์แบบมีเงื่อนไขในบรรทัดการยืนยันของคุณซึ่งจะหยุดเมื่อการยืนยันของคุณล้มเหลว:

ป้อนคำอธิบายรูปภาพที่นี่


@ Nitesh - ฉันคิดว่าวิธีนี้แก้ปัญหาทั้งหมดของคุณคุณพังเมื่อยืนยันล้มเหลวคุณสามารถแก้จุดบกพร่องรหัสที่นั่นและจากนั้นคุณสามารถดำเนินการต่อกับการทดสอบที่เหลือต่อไป ... แม้ว่ามันจะยุ่งยากกว่า เริ่มแรก
Nick Martin
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.