如何使用 pytest 确保正确创建对象?
How to use pytest to ensure an object was created properly?
我有一个以特定格式保存的文件,还有一个 class 将根据文件中的数据创建一个对象。
我想通过测试对象中的每个属性来确保 file/string 中的所有值都被正确提取。
这是我正在做的事情的简化版本:
classlist.py
import re
class ClassList:
def __init__(self, data):
values = re.findall('name=(.*?)$age=(.*?)$', data)
self.students = [Student(name, int(age)) for name, age in values]
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
test_classlist.py
import pytest
from classlist import ClassList
def single_data():
text = 'name=alex$age=20$'
return ClassList(text)
def double_data():
text = 'name=taylor$age=23$' \
'name=morgan$age=25$'
return ClassList(text)
@pytest.mark.parametrize('classinfo, expected', [
(single_data(), ['alex']),
(double_data(), ['taylor', 'morgan'])
])
def test_name(classinfo, expected):
result = [student.name for student in classinfo.students]
assert result == expected
@pytest.mark.parametrize('classinfo, expected', [
(single_data(), [20]),
(double_data(), [23, 25])
])
def test_age(classinfo, expected):
result = [student.age for student in classinfo.students]
assert result == expected
我想根据不同的数据创建对象并将它们用作参数化值。
我当前的设置 有效 ,尽管无意中听到为每个测试创建对象。我希望它们被创建一次。
如果我尝试执行以下操作:
...
@pytest.fixture(scope='module') # fixture added
def double_data():
text = 'name=taylor$age=23$' \
'name=morgan$age=25$'
return ClassList(text)
@pytest.mark.parametrize('classinfo, expected', [
(single_data, ['alex']),
(double_data, ['taylor', 'morgan']) # () removed
])
def test_name(classinfo, expected):
result = [student.name for student in classinfo.students]
assert result == expected
...
AttributeError: 'function' object has no attribute 'students'
...它不起作用,因为它引用函数而不是夹具。
此外,test_name
和test_age
中的代码几乎相同。在我的实际代码中,我对大约 12 个属性执行此操作。 Should/can 这个要合并成一个函数吗?怎么样?
如何清理我的测试代码?
谢谢!
编辑:
我觉得这是相关的,但我不确定如何让它适用于我的情况:Can params passed to pytest fixture be passed in as a variable?
您可以添加一个 fixture
which returns object of that class 并在每次测试之前调用该 fixture。我做了一些更改并在 test_classlist.py
中创建了一个夹具 get_object
,而 classlist.py
保持原样。
get_object
会给你一个 class 的对象,你可以通过 request
模块在测试函数中使用那个对象。我已经在 request.instance.cobj
中分配了那个 class 对象。您可以在测试功能中访问相同的内容。
我从你的描述中得到的是你想要创建 ClassList
的对象。如果我没有弄错,下面的解决方案应该适合你。试试这个。
import pytest
from classlist import ClassList
def single_data():
text = 'name=alex$age=20$'
print text
return ClassList(text)
def double_data():
text = 'name=taylor$age=23$' \
'name=morgan$age=25$'
return ClassList(text)
@pytest.fixture
def get_object(request):
classobj= request.getfuncargvalue('classinfo')()
request.instance.cobj = classobj
class Test_clist:
@pytest.mark.parametrize('classinfo, expected', [
(single_data, ['alex']),
(double_data, ['taylor', 'morgan']) # () removed
])
@pytest.mark.usefixtures('get_object')
def test_name(self,classinfo,expected,request):
result = [student.name for student in request.instance.cobj.students]
print result
print expected
assert result == expected
My current setup works, although there is the unnecessary overheard of creating the object for each test. I'd want them to be created once.
这对我来说听起来像是不必要的预优化,但如果您关心这个,那么 运行 创建数据以在模块级别进行测试的函数,因此它们只 运行 一次。
例如:
...
def single_data():
text = 'name=alex$age=20$'
return ClassList(text)
def double_data():
text = 'name=taylor$age=23$' \
'name=morgan$age=25$'
return ClassList(text)
double_data_object = double_data()
single_data_object = single_data()
@pytest.mark.parametrize('classinfo, expected', [
(single_data_object, ['alex']),
(double_data_object, ['taylor', 'morgan'])
])
def test_name(classinfo, expected):
result = [student.name for student in classinfo.students]
assert result == expected
@pytest.mark.parametrize('classinfo, expected', [
(single_data_object, [20]),
(double_data_object, [23, 25])
])
def test_age(classinfo, expected):
...
Furthermore, the code in test_name and test_age is almost identical.
In my actual code, I'm doing this for about 12 attributes. Should/can
this be merged into a single function? How?
How can I clean up my test code?
有几种方法可以做到这一点,但从你的例子中,提供一个 equality magic method to the Student
class and use that to test your code (also add a repr 来代表你的对象):
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
return (self.name, self.age) == (other.name, other.age)
def __repr__(self):
return 'Student(name={}, age={})'.format(self.name, self.age)
然后你的测试看起来像这样:
@pytest.mark.parametrize('classinfo, expected', [
(single_data(), [Student('alex', 20)]),
(double_data(), [Student('taylor', 23), Student('morgan', 25)]),
])
def test_student(classinfo, expected):
assert classinfo.students == expected
我有一个以特定格式保存的文件,还有一个 class 将根据文件中的数据创建一个对象。
我想通过测试对象中的每个属性来确保 file/string 中的所有值都被正确提取。
这是我正在做的事情的简化版本:
classlist.py
import re
class ClassList:
def __init__(self, data):
values = re.findall('name=(.*?)$age=(.*?)$', data)
self.students = [Student(name, int(age)) for name, age in values]
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
test_classlist.py
import pytest
from classlist import ClassList
def single_data():
text = 'name=alex$age=20$'
return ClassList(text)
def double_data():
text = 'name=taylor$age=23$' \
'name=morgan$age=25$'
return ClassList(text)
@pytest.mark.parametrize('classinfo, expected', [
(single_data(), ['alex']),
(double_data(), ['taylor', 'morgan'])
])
def test_name(classinfo, expected):
result = [student.name for student in classinfo.students]
assert result == expected
@pytest.mark.parametrize('classinfo, expected', [
(single_data(), [20]),
(double_data(), [23, 25])
])
def test_age(classinfo, expected):
result = [student.age for student in classinfo.students]
assert result == expected
我想根据不同的数据创建对象并将它们用作参数化值。
我当前的设置 有效 ,尽管无意中听到为每个测试创建对象。我希望它们被创建一次。
如果我尝试执行以下操作:
...
@pytest.fixture(scope='module') # fixture added
def double_data():
text = 'name=taylor$age=23$' \
'name=morgan$age=25$'
return ClassList(text)
@pytest.mark.parametrize('classinfo, expected', [
(single_data, ['alex']),
(double_data, ['taylor', 'morgan']) # () removed
])
def test_name(classinfo, expected):
result = [student.name for student in classinfo.students]
assert result == expected
...
AttributeError: 'function' object has no attribute 'students'
...它不起作用,因为它引用函数而不是夹具。
此外,test_name
和test_age
中的代码几乎相同。在我的实际代码中,我对大约 12 个属性执行此操作。 Should/can 这个要合并成一个函数吗?怎么样?
如何清理我的测试代码?
谢谢!
编辑:
我觉得这是相关的,但我不确定如何让它适用于我的情况:Can params passed to pytest fixture be passed in as a variable?
您可以添加一个 fixture
which returns object of that class 并在每次测试之前调用该 fixture。我做了一些更改并在 test_classlist.py
中创建了一个夹具 get_object
,而 classlist.py
保持原样。
get_object
会给你一个 class 的对象,你可以通过 request
模块在测试函数中使用那个对象。我已经在 request.instance.cobj
中分配了那个 class 对象。您可以在测试功能中访问相同的内容。
我从你的描述中得到的是你想要创建 ClassList
的对象。如果我没有弄错,下面的解决方案应该适合你。试试这个。
import pytest
from classlist import ClassList
def single_data():
text = 'name=alex$age=20$'
print text
return ClassList(text)
def double_data():
text = 'name=taylor$age=23$' \
'name=morgan$age=25$'
return ClassList(text)
@pytest.fixture
def get_object(request):
classobj= request.getfuncargvalue('classinfo')()
request.instance.cobj = classobj
class Test_clist:
@pytest.mark.parametrize('classinfo, expected', [
(single_data, ['alex']),
(double_data, ['taylor', 'morgan']) # () removed
])
@pytest.mark.usefixtures('get_object')
def test_name(self,classinfo,expected,request):
result = [student.name for student in request.instance.cobj.students]
print result
print expected
assert result == expected
My current setup works, although there is the unnecessary overheard of creating the object for each test. I'd want them to be created once.
这对我来说听起来像是不必要的预优化,但如果您关心这个,那么 运行 创建数据以在模块级别进行测试的函数,因此它们只 运行 一次。
例如:
...
def single_data():
text = 'name=alex$age=20$'
return ClassList(text)
def double_data():
text = 'name=taylor$age=23$' \
'name=morgan$age=25$'
return ClassList(text)
double_data_object = double_data()
single_data_object = single_data()
@pytest.mark.parametrize('classinfo, expected', [
(single_data_object, ['alex']),
(double_data_object, ['taylor', 'morgan'])
])
def test_name(classinfo, expected):
result = [student.name for student in classinfo.students]
assert result == expected
@pytest.mark.parametrize('classinfo, expected', [
(single_data_object, [20]),
(double_data_object, [23, 25])
])
def test_age(classinfo, expected):
...
Furthermore, the code in test_name and test_age is almost identical. In my actual code, I'm doing this for about 12 attributes. Should/can this be merged into a single function? How?
How can I clean up my test code?
有几种方法可以做到这一点,但从你的例子中,提供一个 equality magic method to the Student
class and use that to test your code (also add a repr 来代表你的对象):
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
return (self.name, self.age) == (other.name, other.age)
def __repr__(self):
return 'Student(name={}, age={})'.format(self.name, self.age)
然后你的测试看起来像这样:
@pytest.mark.parametrize('classinfo, expected', [
(single_data(), [Student('alex', 20)]),
(double_data(), [Student('taylor', 23), Student('morgan', 25)]),
])
def test_student(classinfo, expected):
assert classinfo.students == expected