pytest
실패한 테스트와 상호 작용할 수있는 충분한 옵션을 제공하는을 사용 하고 있습니다. 명령 행 옵션과이를 가능하게하는 몇 가지 후크를 제공합니다. 각각의 사용법과 특정 디버깅 요구에 맞게 사용자 정의 할 수있는 위치에 대해 설명합니다.
또한 정말로 필요한 경우 특정 어설 션을 완전히 건너 뛸 수있는보다 이국적인 옵션을 살펴 보겠습니다.
단언이 아닌 예외 처리
실패한 테스트는 일반적으로 pytest를 중지하지 않습니다. 특정 횟수의 실패 후에 명시 적으로 종료하도록 지시 한 경우에만 가능합니다 . 또한 예외가 발생하여 테스트가 실패합니다. assert
제기 AssertionError
하지만 테스트를 실패하게 만드는 유일한 예외는 아닙니다! alter가 아닌 예외 처리 방법을 제어하려고합니다 assert
.
그러나 어설 션 이 실패 하면 개별 테스트 가 종료 됩니다 . try...except
블록 외부에서 예외가 발생 하면 파이썬은 현재 함수 프레임을 풀고 다시 돌아 가지 않기 때문입니다.
_assertCustom()
어설 션을 다시 실행하려는 시도에 대한 설명으로 판단하여 원하는 것이 아니라고 생각 하지만 옵션에 대해서는 아래에서 자세히 설명하겠습니다.
pdb로 pytest에서 사후 디버깅
디버거에서 실패를 처리하는 다양한 옵션에 대해서는 --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는 사후 디버깅 세션을 시작합니다 . 이것은 본질적으로 정확히 당신이 원하는 것입니다. 테스트 실패 시점에서 코드를 중지하고 디버거를 열어 테스트 상태를 확인하십시오. 스택의 모든 프레임에 대한 테스트의 로컬 변수, 글로벌 및 로컬 및 글로벌과 상호 작용할 수 있습니다.
여기서 pytest는이 시점 이후에 종료할지 여부를 완전히 제어합니다. q
quit 명령을 사용하면 pytest도 실행을 종료하고 c
for continue는 제어를 pytest로 리턴하고 다음 테스트가 실행됩니다.
대체 디버거 사용
이를 위해 pdb
디버거에 바인딩되지 않았습니다 . --pdbcls
스위치 로 다른 디버거를 설정할 수 있습니다 . IPython 디버거 구현 또는 대부분의 다른 Python 디버거를 포함하여 pdb.Pdb()
호환 가능한 모든 구현이 작동 합니다 ( 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
다음 테스트를 계속 진행 합니다 (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 또는 이와 유사한 것을 사용하여 자신의 디버거를 작성하지 않으면이 옵션을 실제로 권장 할 수 없습니다. 그것은 큰 사업입니다.
필터링 실패; 디버거를 열 때 선택하고 선택하십시오
다음 단계는 pytest 디버깅 및 상호 작용 후크입니다 . 이것들은 pytest가 예외 처리 pdb.set_trace()
또는 또는breakpoint()
(Python 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
다른 hook을 통해 플러그인 객체를 hook 에 등록하지만 내부 플러그인의 등록을 취소 할 수있을 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)
다시 말하면 , 위의 접근 방식은 pudb 또는 IPython 디버거를 포함하여 pytest 와 함께 작동하는 디버거 와 이것을 결합 할 수 있다는 추가 이점이 있습니다 .
$ 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_interact
hooksp를 등록합니다 . 보다 완전한 구현은 플러그인 관리자의 모든 플러그인을 반복하여 임의의 플러그인을 자동으로 재정의 config.pluginmanager.list_name_plugin
하고hasattr()
각 플러그인을 테스트하기 위해 .
실패를 완전히 없애기
이렇게하면 실패한 테스트 디버깅을 완전히 제어 할 수 있지만 주어진 테스트에 대해 디버거를 열지 않더라도 테스트는 실패한 것으로 남아 있습니다. 실패를 완전히 없애고 싶다면 다른 후크를 사용할 수 있습니다 pytest_runtest_call()
.
pytest가 테스트를 실행하면 위의 후크를 통해 테스트가 실행되어 None
예외 가 반환 되거나 발생할 것으로 예상됩니다 . 이로부터 보고서가 작성되고, 선택적으로 로그 항목이 작성되며, 테스트에 실패한 경우pytest_exception_interact()
후크가 호출됩니다. 따라서이 후크가 생성하는 결과를 변경하기 만하면됩니다. 예외 대신 아무것도 반환하지 않아야합니다.
가장 좋은 방법은 후크 래퍼 를 사용하는 것 입니다. 후크 랩퍼는 실제 작업을 수행 할 필요는 없지만 대신 후크 결과에 대해 변경 될 수 있습니다. 행을 추가하기 만하면됩니다.
outcome = yield
후크 래퍼 구현에서를 통해 테스트 예외를 포함한 후크 결과에 액세스 할 수 있습니다 outcome.excinfo
. 테스트에서 예외가 발생한 경우이 속성은 튜플 (유형, 인스턴스, 추적)로 설정됩니다. 또는 outcome.get_result()
표준 try...except
처리를 호출 하여 사용할 수 있습니다 .
어떻게 실패한 시험 합격을합니까? 3 가지 기본 옵션이 있습니다.
당신이 사용하는 것은 당신에게 달려 있습니다. 테스트가 실패한 것처럼 해당 사례를 처리 할 필요가 없으므로 건너 뛴 테스트 및 예상 실패 테스트의 결과를 먼저 확인하십시오. 이 옵션에서 발생하는 특수 예외 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%]
유스 케이스에 가장 적합한 것은 당신에게 달려 있습니다. For skip()
및 xfail()
표준 메시지 형식 ( [NOTRUN]
또는 접두사)을 흉내 [XFAIL]
냈지만 원하는 다른 메시지 형식을 자유롭게 사용할 수 있습니다.
세 가지 경우 모두 pytest는이 방법을 사용하여 결과를 변경 한 테스트의 디버거를 열지 않습니다.
개별 주장 진술 변경
assert
테스트 내에서 테스트 를 변경 하려면 훨씬 더 많은 작업을 수행해야합니다. 예, 이것은 기술적으로 가능하지만 컴파일 타임에 Python이 실행할 코드를 다시 작성해야만 가능합니다 .
당신이 사용할 때 pytest
, 이것은 실제로 이미 수행되고 있습니다. Pytest 는 어설 션이 실패 할 때 더 많은 컨텍스트를 제공하기 위해 명령문을 다시 작성 assert
합니다 . 소스 코드 뿐만 아니라 수행중인 작업에 대한 개요를 보려면 이 블로그 게시물 을 참조하십시오 . 이 모듈의 길이는 1k 줄 이상이므로 Python의 추상 구문 트리 작동 방식을 이해해야합니다 . 당신이 할 경우, 당신은 할 수 주변을 포함하여, 거기에 자신 만의 수정을 추가 할 수있는 해당 모듈 monkeypatch 와 핸들러를._pytest/assertion/rewrite.py
assert
try...except AssertionError:
그러나 후속 명령문은 건너 뛴 어설 션이 보호해야하는 상태 (특정 오브젝트 배열, 변수 세트 등)에 쉽게 종속 될 수 있으므로 어설 션을 선택적으로 비활성화하거나 무시할 수 없습니다. 어설 테스트하면 foo
되지 None
, 다음 나중에 어설에 의존하는 foo.bar
존재, 당신은 단순히으로 실행 AttributeError
이 경로를 이동해야하는 경우, 다시 제기 예외 등, 거기에서 스틱을.
필자는 asserts
여기 에 재 작성에 대해 자세히 설명 하지 않을 것입니다. 이것은 관련된 작업량을 고려하지 않고 추구 할 가치가 있다고 생각하지 않기 때문에 사후 디버깅을 통해 테스트 상태에 액세스 할 수 있습니다. 어쨌든 어설 션 실패 지점 .
이 작업을 원한다면 사용할 필요가 없습니다 eval()
(어쨌든 작동하지 않을 것입니다, assert
진술이므로 exec()
대신 사용해야 합니다). 어설 션을 두 번 실행해야합니다 ( 어설 션에 사용 된식이 상태를 변경하면 문제가 발생할 수 있습니다). 대신 ast.Assert
노드를 노드 안에 임베드하고 ast.Try
빈 ast.Raise
노드 를 사용하는 예외 처리기를 연결하여 포착 된 예외를 다시 발생시킵니다.
디버거를 사용하여 어설 션 문을 건너 뜁니다.
Python 디버거를 사용하면 실제로 / 명령을 사용하여 명령문 을 건너 뛸 수 있습니다 . 당신이 알고있는 경우 앞까지 특정 주장이 그 것이다 실패, 당신은 바이 패스이를 사용할 수 있습니다. 을 사용하여 테스트를 실행할 수 있습니다. 그러면 모든 테스트 시작시 디버거가 열리고 어설 션 직전에 디버거가 일시 중지되면 a 를 실행하여 건너 뜁니다.j
jump
--trace
j <line after assert>
이것을 자동화 할 수도 있습니다. 위의 기술을 사용하여 다음과 같은 사용자 정의 디버거 플러그인을 만들 수 있습니다
pytest_testrun_call()
후크를 사용하여 AssertionError
예외 를 잡습니다.
- 역 추적에서 행 '해제'행 번호를 추출하고 일부 소스 코드 분석을 통해 성공적인 점프를 실행하는 데 필요한 어설 션 전후 행 번호를 결정합니다.
- 테스트를 다시 실행 하지만 이번에
Pdb
는 어설 션 이전 줄에 중단 점을 설정하고 중단 점에 도달하면 2 초로 자동으로 점프 한 다음 c
계속을 수행하는 서브 클래스를 사용합니다 .
또는 어설 션이 실패하기를 기다리는 대신 assert
테스트에서 찾은 각각에 대해 중단 점 설정을 자동화 할 수 있습니다 (소스 코드 분석을 사용하여 테스트 ast.Assert
의 AST에있는 노드의 줄 번호를 간단하게 추출 할 수 있음 ). 디버거 스크립트 명령을 사용하고 jump
명령을 사용 하여 어설 션 자체를 건너 뜁니다. 트레이드 오프를해야합니다. 디버거에서 모든 테스트를 실행하거나 (인터프리터가 모든 명령문에 대해 추적 함수를 호출해야하므로 느리게)이를 실패한 테스트에만 적용하고 해당 테스트를 처음부터 다시 실행하는 비용을 지불합니다.
이러한 플러그인은 많은 작업을 만들 것입니다. 여기서 예제에 쓰지 않을 것입니다. 일부 답변에 맞지 않기 때문에 부분적으로 가치가 있다고 생각하지 않기 때문 입니다. 디버거를 열고 수동으로 점프합니다. 어설 션이 실패하면 테스트 자체 또는 테스트중인 코드에 버그가 있음을 나타내므로 문제 디버깅에만 집중할 수 있습니다.