Python pytest hook
https://luavis.me/python/python-pytest-hook이번 문서에서는 Pytest의 hook 기능에 대해서 알아보고자 Pytest 공식 사이트의 latest 버전의(2.4) hook reference 페이지를 번역해 보기로 했다.
Note:
Hook 함수들은 conftest.py에 추가해야 동작한다.
이 외의 pytest 관련 문서
Contents
Hook specification and validation
pytest는 초기화, 동작, 테스트 실행과 reporting을 hooking하기 위해서 hook 함수를 호출한다. pytest는 각각의 hook 함수는 그 hook specification에 따라서 유효성 검사를 하는 plugin을 불러온다. 각각의 hook 함수 이름과 함수의 매개변수 이름은 hook의 specification에 맞쳐주어야 한다. 하지만 hook 함수는 spec에 비해서 더 적게 매개변수를 갖고 있어도 일반적으로 상관없다. 만약 매개변수 이름이나 hook 함수의 이름을 잘못 입력한 경우 에러가 발생할 것이다.
밑에 있는 함수들의 테스트를 위해서 간략한 테스트 코드를 작성하였다.
test_example.py
1
2
3
4
5
6
import pytest
@pytest.mark.tryfirst
def test_example():
assert 0
Initialization, command line and configuration hooks
pytest_cmdline_preparse(config, args) [deprecated]
opetion들이 파싱되기 전에 변경할 수 있다.
각 매개변수가 어떤 값을 보여주는지 확인해보기 위해서 print함수를 이용해서 출력해보았다.
conftest.py
1
2
3
4
5
def pytest_cmdline_preparse(config, args):
print("cmdline preparse")
print(config)
print(args)
이 코드를 conftest.py에 추가하고 testing을 시작하면,
$ py.test -s
cmdline preparse
<_pytest.config.Config object at 0x10fa66790>
['-s']
========================================= test session starts =========================================
platform darwin -- Python 2.7.6 -- py-1.4.30 -- pytest-2.7.2
rootdir: /Users/Luavis/Projects/pytest-examples, inifile:
collected 1 items
test_example.py F
============================================== FAILURES ===============================================
____________________________________________ test_example _____________________________________________
@pytest.mark.tryfirst
def test_example():
> assert 0
E assert 0
test_example.py:7: AssertionError
====================================== 1 failed in 0.01 seconds =======================================
결과를 얻을 수 있다. -s라는 option을 붙혀서 py.test를 실행했을때, args에는 -s가 list형식으로 들어가 있는것을 볼 수 있다.
pytest_addoption(parser)
ini 파일이나 argument로 등록되는 설정들을 새롭게 등록할 수 있다. 이 함수는 플러그 인에서 구현되어 있어야하며, 테스트가 시작될 때 오직 한 번만 실행된다.
Parameters
- parser – 커맨드 라인 형식의 설정을 추가하고 싶다면, parser.addoption(…)를 호출하여 설정들을 추가할 수 있다. 만약 ini파일 형식의 설정을 추가하고 싶다면, parser.addini(…)를 호출하면 가능하다.
Note
옵션들은 나중에 config 객체를 통하여 접근할 수 있다.
config.getoption(name) 커맨드 라인으로 부터 받은 설정 값을 받을때
config.getini(name) ini file로 부터 받은 설정 값을 받을때
config 객체는 많은 pytest 내부 객체에 .config를 이용하여 접근할 수 있고, pytestconfig라는 fixture를 이용하여 접근할 수 있다. pytest module를 이용하여 pytest.config를 이용하여 접근할 수 있지만 deprecated된 기능이다.
pytest_cmdline_main(config)
main 커맨드 라인 동작이 실행될때 호출된다. 기본적인 구현은 configure hooks과 runtest_mainloop을 호출한다.
위의 pytest_addoption과 합친 예제를 보자면:
1
2
3
4
5
6
def pytest_addoption(parser):
parser.addoption("--cmdopt", action="store", default="type1",
help="my option: type1 or type2")
def pytest_cmdline_main(config):
print(config.getoption("--cmdopt"))
add option hook을 이용하여 커맨드라인 형식의 옵션 –cmdopt를 추가하였고, 이를 기본값으로 실행되었을때 cmdline_main에서는 config에서 확인 할 수 있어야 한다.
$ py.test
type1
========================================= test session starts =========================================
platform darwin -- Python 2.7.6 -- py-1.4.30 -- pytest-2.7.2
rootdir: /Users/Luavis/Projects/pytest-examples, inifile:
collected 1 items
test_example.py F
============================================== FAILURES ===============================================
____________________________________________ test_example _____________________________________________
@pytest.mark.tryfirst
def test_example():
> assert 0
E assert 0
test_example.py:7: AssertionError
====================================== 1 failed in 0.01 seconds =======================================
pytest_configure(config)
커맨드 라인 형식의 옵션이 모두 파싱되었고, 모든 플러그인과 initaial conftest가 호출된 뒤에 호출된다.
pytest_unconfigure(config)
테스트가 종료되기 전에 호출된다.
Generic “runtest” hooks
모든 runtest들은 pytest.Item 객체를 받을 수 있는 훅으로 연관되어 있다.
pytest_runtest_protocol(item, nextitem)
runtest의 setup, test의 호출, teardown을 예외처리, hook의 report 호출까지 포함하여 모두 item으로 받을 수 있다.
Parameters
- item – runtest 가 현재 실행되고 있는 test item
- nextitem – 다음에 실행될 것이라고 계획되어 있는 test item(마지막인 경우에는 None이다). 이 매개변수는 pytest_runtest_teardown()로 부터 온다.
Return [boolean]
만약 True를 반환하면 그 후의 hook이 동작하지 않는다.
pytest_runtest_setup(item)
pytest_runtest_call(item)가 호출되기 전에 호출된다.
pytest_runtest_call(item)
test item이 실행될때 호출된다.
pytest_runtest_teardown(item, nextitem)
pytest_runtest_call 호출된 뒤에 호출된다.
Parameters
- nextitem – 다음에 실행될 것이라고 계획되어 있는 test item(마지막인 경우에는 None이다).
위의 모든 hook 함수들을 테스트 해보기 위하여, testcase code와 conftest.py를 수정했다.
test_example.py
1
2
3
4
5
6
7
import pytest
def test_example():
pass
def test_example2():
pass
conftest.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def pytest_runtest_protocol(item, nextitem):
print("protocol")
print(item)
print(nextitem)
def pytest_runtest_setup(item):
print("setup")
print(item)
def pytest_runtest_call(item):
print("call")
print(item)
def pytest_runtest_teardown(item, nextitem):
print("teardown")
print(item)
print(nextitem)
teardown에서 발생하는 로그를 capturing되지 않도록 하기 위해서 -s(–capture=no) option을 주어 실행해보면,
$ py.test -s
========================================= test session starts =========================================
platform darwin -- Python 2.7.6 -- py-1.4.30 -- pytest-2.7.2
rootdir: /Users/Luavis/Projects/pytest-examples, inifile:
collected 2 items
protocol
<Function 'test_example'>
<Function 'test_example2'>
test_example.py setup
<Function 'test_example'>
call
<Function 'test_example'>
.teardown
<Function 'test_example'>
<Function 'test_example2'>
protocol
<Function 'test_example2'>
None
setup
<Function 'test_example2'>
call
<Function 'test_example2'>
.teardown
<Function 'test_example2'>
None
====================================== 2 passed in 0.01 seconds =======================================
test_example이 실행되고 후 test_example2가 실행될 것이다 그리고 protocol에서 이번 item인 test_example과 다음 실행될 test_example2가 nextitem인 것을 확인할 수 있다. 그리고 그 다음 protocol에서는 item이 test_example2이고 그 다음 실행될 run test는 없음으로 nextitem은 None으로 설정된것을 볼 수 있다.
pytest_runtest_makereport(item, call)
item은 pytest.Item의 instance이고, call은 _pytest.runner.CallInfo의 instance이다. 그리고 return 값은 _pytest.runner.TestReport의 instance여야 한다.
동작 방식을 보고자 아까 수정했던 코드에 조금 수정을 더하여 보았다.
conftest.py
1
2
3
4
5
6
7
8
9
10
11
12
# ...
def pytest_runtest_teardown(item, nextitem):
return "tear down test"
def pytest_runtest_makereport(item, call):
print("item")
print(item)
print("call")
print(call)
test_example.py
1
2
3
4
5
6
7
import pytest
def test_example():
pass
def test_example2():
pass
그리고 test_example.py에서 test_example2를 지웠다. 그 결과를 출력해보면:
$ py.test
========================================= test session starts =========================================
platform darwin -- Python 2.7.6 -- py-1.4.30 -- pytest-2.7.2
rootdir: /Users/Luavis/Projects/pytest-examples, inifile:
collected 1 items
test_example.py item
<Function 'test_example'>
call
<CallInfo when='setup' result: []>
item
<Function 'test_example'>
call
<CallInfo when='call' result: []>
.item
<Function 'test_example'>
call
<CallInfo when='teardown' result: ['tear down test']>
====================================== 1 passed in 0.01 seconds =======================================
이런 결과화면을 얻을 수 있다. teardown이 위에서 hook function에 return 값으로 처리된 값이 report쪽에서 result로 접근하여 볼 수 있다.