How to use pytest to ensure proper object creation?

I have a file that is saved in a specific format, and a class that will create an object based on the data in the file.

I want all the values ​​in the file / line to be correctly extracted by testing each attribute in the object.

Here is a simplified version of what I'm doing:

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 

I want to create objects based on different data and use them as a parameterized value.

My current setup works, although there is an unnecessary ability to create an object for each test. I would like them to be created once.

If I try to do the following:

 ... @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'

... it does not work, because it refers to a function, not a device.

In addition, the code in test_name and test_age almost identical. In my actual code, I do this for about 12 attributes. Should / be combined into one function? How?

How can I clear my test code?

Thanks!

Edit:

I believe that this is relevant, but I'm not sure how it works for my situation: Can the passed parameters be passed to pytest fixture as a variable?

+5
source share
2 answers

My current setup works, although there is an unnecessary ability to create an object for each test. I would like them to be created once.

It smells like unnecessary pre-optimization for me, but if you're interested, run the functions that create your data for testing at the module level, so they only run once.

For instance:

 ... 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): ... 

In addition, the code in test_name and test_age is almost identical. In my actual code, I do this for about 12 attributes. In case / can it be combined into one function? How?

How can I clear my test code?

There are several ways to do this, but from your example, provide an equality magic method to the Student class and use this to test your code (also add repr to properly represent your object):

 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) 

Then your test might look like this:

 @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 
+2
source

You can add one fixture that returns an object of this class and calls this fixture before each test. I made some changes and create the get_object lamp in test_classlist.py , and classlist.py the way it is.

get_object will provide you an object of this class, and you can use this object in a test function through the request module. I assigned this class object to request.instance.cobj . The same can be obtained in the test function.

What I get from your description is you want to create a ClassList object. If I'm not mistaken, the solution below should work for you. Try it.

 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 
+2
source

Source: https://habr.com/ru/post/1269664/


All Articles