py.test를 사용하여 Python 클래스 MyTester에 래핑 된 일부 DLL 코드를 테스트하고 있습니다. 검증을 위해 테스트 중에 일부 테스트 데이터를 기록하고 나중에 더 많은 처리를해야합니다. test _… 파일이 많기 때문에 대부분의 테스트에서 테스터 개체 생성 (MyTester 인스턴스)을 재사용하고 싶습니다.
테스터 개체는 DLL의 변수 및 함수에 대한 참조를 가진 개체이므로 DLL의 변수 목록을 각 테스트 파일에 대한 테스터 개체에 전달해야합니다 (기록 할 변수는 test_ .. . 파일). 목록의 내용은 지정된 데이터를 기록하는 데 사용됩니다.
내 생각은 어떻게 든 다음과 같이하는 것입니다.
import pytest
class MyTester():
def __init__(self, arg = ["var0", "var1"]):
self.arg = arg
# self.use_arg_to_init_logging_part()
def dothis(self):
print "this"
def dothat(self):
print "that"
# located in conftest.py (because other test will reuse it)
@pytest.fixture()
def tester(request):
""" create tester object """
# how to use the list below for arg?
_tester = MyTester()
return _tester
# located in test_...py
# @pytest.mark.usefixtures("tester")
class TestIt():
# def __init__(self):
# self.args_for_tester = ["var1", "var2"]
# # how to pass this list to the tester fixture?
def test_tc1(self, tester):
tester.dothis()
assert 0 # for demo purpose
def test_tc2(self, tester):
tester.dothat()
assert 0 # for demo purpose
이렇게 할 수 있습니까, 아니면 더 우아한 방법이 있습니까?
보통은 설정 기능 (xUnit 스타일)을 사용하여 각 테스트 방법에 대해 수행 할 수 있습니다. 그러나 나는 어떤 종류의 재사용을 얻고 싶습니다. 이것이 조명기로 가능한지 아는 사람이 있습니까?
나는 다음과 같이 할 수 있다는 것을 안다. (문서에서)
@pytest.fixture(scope="module", params=["merlinux.eu", "mail.python.org"])
하지만 테스트 모듈에서 직접 매개 변수화가 필요합니다.
테스트 모듈에서 조명기의 params 속성에 액세스 할 수 있습니까?
답변
업데이트 : 이 질문에 대한 답변이 허용되고 가끔씩 찬성 투표를 받기 때문에 업데이트 를 추가해야합니다. 내 원래 답변 (아래)은 다른 사람들 이 pytest가 이제 조명기의 간접 매개 변수화를 지원 한다고 지적했듯이 이전 버전의 pytest에서 이것을 수행하는 유일한 방법 이지만. 예를 들어 다음과 같이 할 수 있습니다 (@imiric을 통해) :
# test_parameterized_fixture.py
import pytest
class MyTester:
def __init__(self, x):
self.x = x
def dothis(self):
assert self.x
@pytest.fixture
def tester(request):
"""Create tester object"""
return MyTester(request.param)
class TestIt:
@pytest.mark.parametrize('tester', [True, False], indirect=['tester'])
def test_tc1(self, tester):
tester.dothis()
assert 1
$ pytest -v test_parameterized_fixture.py
================================================================================= test session starts =================================================================================
platform cygwin -- Python 3.6.8, pytest-5.3.1, py-1.8.0, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: .
collected 2 items
test_parameterized_fixture.py::TestIt::test_tc1[True] PASSED [ 50%]
test_parameterized_fixture.py::TestIt::test_tc1[False] FAILED
그러나이 형식의 간접 매개 변수화는 명시 적이지만 @Yukihiko Shinoda가 지적했듯이 이제 암시 적 간접 매개 변수화 형식을 지원합니다 (공식 문서에서 이에 대한 명백한 참조를 찾을 수 없음).
# test_parameterized_fixture2.py
import pytest
class MyTester:
def __init__(self, x):
self.x = x
def dothis(self):
assert self.x
@pytest.fixture
def tester(tester_arg):
"""Create tester object"""
return MyTester(tester_arg)
class TestIt:
@pytest.mark.parametrize('tester_arg', [True, False])
def test_tc1(self, tester):
tester.dothis()
assert 1
$ pytest -v test_parameterized_fixture2.py
================================================================================= test session starts =================================================================================
platform cygwin -- Python 3.6.8, pytest-5.3.1, py-1.8.0, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: .
collected 2 items
test_parameterized_fixture2.py::TestIt::test_tc1[True] PASSED [ 50%]
test_parameterized_fixture2.py::TestIt::test_tc1[False] FAILED
나는이 양식의 의미는 정확히 모르겠지만, 그 보인다 pytest.mark.parametrize
있지만 것을 인식 test_tc1
방법이라는 주장을지지 않습니다 tester_arg
는 tester
가를 통해의 매개 변수화 인수를 전달하므로, 그것을 사용하고 있음을 고정이하는 tester
기구.
비슷한 문제가있었습니다.라는 픽스처가 있는데 test_package
나중에 특정 테스트에서 실행할 때 해당 픽스처에 선택적 인수를 전달할 수 있기를 원했습니다. 예를 들면 :
@pytest.fixture()
def test_package(request, version='1.0'):
...
request.addfinalizer(fin)
...
return package
(이러한 목적에서는 조명기가 무엇을하는지 또는 반환 된 객체 유형이 무엇인지는 중요하지 않습니다 package
.)
그런 다음 어떻게 든 테스트 기능에서이 조명기를 사용하는 것이 바람직 할 것입니다. version
해당 테스트와 함께 사용할 해당 조명기에 인수를 . 현재는 불가능하지만 좋은 기능이 될 수 있습니다.
그동안 내 조명기 가 이전에 수행했던 모든 작업을 수행 하는 함수 를 단순히 반환하도록 만드는 것은 충분히 쉬웠 지만, version
인수 를 지정할 수있게합니다 .
@pytest.fixture()
def test_package(request):
def make_test_package(version='1.0'):
...
request.addfinalizer(fin)
...
return test_package
return make_test_package
이제 다음과 같이 테스트 기능에서 이것을 사용할 수 있습니다.
def test_install_package(test_package):
package = test_package(version='1.1')
...
assert ...
등등.
OP의 시도 된 솔루션은 올바른 방향으로 향했으며 @ hpk42의 답변에서 알 MyTester.__init__
수 있듯이 다음과 같이 요청에 대한 참조를 저장할 수 있습니다.
class MyTester(object):
def __init__(self, request, arg=["var0", "var1"]):
self.request = request
self.arg = arg
# self.use_arg_to_init_logging_part()
def dothis(self):
print "this"
def dothat(self):
print "that"
그런 다음 이것을 사용하여 다음과 같은 조명기를 구현하십시오.
@pytest.fixture()
def tester(request):
""" create tester object """
# how to use the list below for arg?
_tester = MyTester(request)
return _tester
원하는 경우 MyTester
클래스를 약간 재구성하여 .args
속성이 생성 된 후 업데이트 될 수 있도록 개별 테스트의 동작을 조정할 수 있습니다.
답변
이것은 실제로 간접 매개 변수화 를 통해 py.test에서 기본적으로 지원됩니다 .
귀하의 경우에는 다음이 필요합니다.
@pytest.fixture
def tester(request):
"""Create tester object"""
return MyTester(request.param)
class TestIt:
@pytest.mark.parametrize('tester', [['var1', 'var2']], indirect=True)
def test_tc1(self, tester):
tester.dothis()
assert 1
답변
픽스처 함수 (따라서 Tester 클래스에서)에서 요청하는 모듈 / 클래스 / 함수에 액세스 할 수 있습니다 . 픽스처 함수에서 테스트 컨텍스트 요청과 상호 작용을 참조하십시오 . 따라서 클래스 또는 모듈에서 일부 매개 변수를 선언 할 수 있으며 테스터 픽스처가이를 선택할 수 있습니다.
답변
문서를 찾을 수 없지만 최신 버전의 pytest에서 작동하는 것 같습니다.
@pytest.fixture
def tester(tester_arg):
"""Create tester object"""
return MyTester(tester_arg)
class TestIt:
@pytest.mark.parametrize('tester_arg', [['var1', 'var2']])
def test_tc1(self, tester):
tester.dothis()
assert 1
답변
imiric의 대답을 조금 개선하려면 :이 문제를 해결하는 또 다른 우아한 방법은 “parameter fixtures”를 만드는 것입니다. 저는 개인적 indirect
으로 pytest
. 이 기능은에서 사용할 수 pytest_cases
있으며 원래 아이디어는 Sup3rGeo 에서 제안했습니다 .
import pytest
from pytest_cases import param_fixture
# create a single parameter fixture
var = param_fixture("var", [['var1', 'var2']], ids=str)
@pytest.fixture
def tester(var):
"""Create tester object"""
return MyTester(var)
class TestIt:
def test_tc1(self, tester):
tester.dothis()
assert 1
참고 pytest-cases
도 제공 @pytest_fixture_plus
그것은 당신이 당신의 설비에 매개 변수화 마크를 사용할 수 있도록하고, @cases_data
별도의 모듈에서 함수에서 매개 변수를 소싱 할 수있다. 자세한 내용은 문서 를 참조하십시오. 그건 그렇고 나는 저자입니다;)
답변
다음과 같은 고정물을 작성할 수있는 재미있는 데코레이터를 만들었습니다.
@fixture_taking_arguments
def dog(request, /, name, age=69):
return f"{name} the dog aged {age}"
여기, 왼쪽 /
에는 다른 조명기가 있고 오른쪽에는 다음을 사용하여 제공되는 매개 변수가 있습니다.
@dog.arguments("Buddy", age=7)
def test_with_dog(dog):
assert dog == "Buddy the dog aged 7"
이것은 함수 인수가 작동하는 방식과 동일하게 작동합니다. age
인수를 제공하지 않으면 69
대신 기본값 인이 사용됩니다. 을 제공하지 않거나 데코레이터를 name
생략 dog.arguments
하면 일반 TypeError: dog() missing 1 required positional argument: 'name'
. argument를 취하는 다른 조명기가 있다면 name
이것과 충돌하지 않습니다.
비동기 픽스쳐도 지원됩니다.
또한 이것은 멋진 설정 계획을 제공합니다.
$ pytest test_dogs_and_owners.py --setup-plan
SETUP F dog['Buddy', age=7]
...
SETUP F dog['Champion']
SETUP F owner (fixtures used: dog)['John Travolta']
전체 예 :
from plugin import fixture_taking_arguments
@fixture_taking_arguments
def dog(request, /, name, age=69):
return f"{name} the dog aged {age}"
@fixture_taking_arguments
def owner(request, dog, /, name="John Doe"):
yield f"{name}, owner of {dog}"
@dog.arguments("Buddy", age=7)
def test_with_dog(dog):
assert dog == "Buddy the dog aged 7"
@dog.arguments("Champion")
class TestChampion:
def test_with_dog(self, dog):
assert dog == "Champion the dog aged 69"
def test_with_default_owner(self, owner, dog):
assert owner == "John Doe, owner of Champion the dog aged 69"
assert dog == "Champion the dog aged 69"
@owner.arguments("John Travolta")
def test_with_named_owner(self, owner):
assert owner == "John Travolta, owner of Champion the dog aged 69"
데코레이터의 코드 :
import pytest
from dataclasses import dataclass
from functools import wraps
from inspect import signature, Parameter, isgeneratorfunction, iscoroutinefunction, isasyncgenfunction
from itertools import zip_longest, chain
_NOTHING = object()
def _omittable_parentheses_decorator(decorator):
@wraps(decorator)
def wrapper(*args, **kwargs):
if not kwargs and len(args) == 1 and callable(args[0]):
return decorator()(args[0])
else:
return decorator(*args, **kwargs)
return wrapper
@dataclass
class _ArgsKwargs:
args: ...
kwargs: ...
def __repr__(self):
return ", ".join(chain(
(repr(v) for v in self.args),
(f"{k}={v!r}" for k, v in self.kwargs.items())))
def _flatten_arguments(sig, args, kwargs):
assert len(sig.parameters) == len(args) + len(kwargs)
for name, arg in zip_longest(sig.parameters, args, fillvalue=_NOTHING):
yield arg if arg is not _NOTHING else kwargs[name]
def _get_actual_args_kwargs(sig, args, kwargs):
request = kwargs["request"]
try:
request_args, request_kwargs = request.param.args, request.param.kwargs
except AttributeError:
request_args, request_kwargs = (), {}
return tuple(_flatten_arguments(sig, args, kwargs)) + request_args, request_kwargs
@_omittable_parentheses_decorator
def fixture_taking_arguments(*pytest_fixture_args, **pytest_fixture_kwargs):
def decorator(func):
original_signature = signature(func)
def new_parameters():
for param in original_signature.parameters.values():
if param.kind == Parameter.POSITIONAL_ONLY:
yield param.replace(kind=Parameter.POSITIONAL_OR_KEYWORD)
new_signature = original_signature.replace(parameters=list(new_parameters()))
if "request" not in new_signature.parameters:
raise AttributeError("Target function must have positional-only argument `request`")
is_async_generator = isasyncgenfunction(func)
is_async = is_async_generator or iscoroutinefunction(func)
is_generator = isgeneratorfunction(func)
if is_async:
@wraps(func)
async def wrapper(*args, **kwargs):
args, kwargs = _get_actual_args_kwargs(new_signature, args, kwargs)
if is_async_generator:
async for result in func(*args, **kwargs):
yield result
else:
yield await func(*args, **kwargs)
else:
@wraps(func)
def wrapper(*args, **kwargs):
args, kwargs = _get_actual_args_kwargs(new_signature, args, kwargs)
if is_generator:
yield from func(*args, **kwargs)
else:
yield func(*args, **kwargs)
wrapper.__signature__ = new_signature
fixture = pytest.fixture(*pytest_fixture_args, **pytest_fixture_kwargs)(wrapper)
fixture_name = pytest_fixture_kwargs.get("name", fixture.__name__)
def parametrizer(*args, **kwargs):
return pytest.mark.parametrize(fixture_name, [_ArgsKwargs(args, kwargs)], indirect=True)
fixture.arguments = parametrizer
return fixture
return decorator