Jake
Jake

Reputation: 53

How to use pytest to ensure an object was created properly?

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

I want to ensure that all values in the file/string were extracted correctly 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 parametrized value.

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.

If I try doing 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 doesn't work as it references the function rather than the fixture.

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?

Thanks!

Edit:

I feel this is relevant, but I'm unsure how make it work for my situation: Can params passed to pytest fixture be passed in as a variable?

Upvotes: 5

Views: 7023

Answers (2)

salparadise
salparadise

Reputation: 5805

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.

This smells like unnecessary pre-optimization to me, but if you care about this, then run the functions that create your data to test at module level, so they only run once.

For example:

...
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?

A couple of ways to do this, but from your example, provide an equality magic method to the Student class and use that to test your code (also add a repr for sane representation of 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 can 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

Upvotes: 2

Chanda Korat
Chanda Korat

Reputation: 2561

You can add one fixture which returns object of that class and call that fixture before every test. I have done some changes and create a fixture get_object in test_classlist.py while classlist.py is as it is.

get_object will give you an object of that class and you can use that object in test function via request module. I have assigned that class object in request.instance.cobj. The same you can access in test function.

What I am getting from your description is you want to create object of ClassList. If i am not getting wrong ,the below solution should work for you. Try this.

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

Upvotes: 2

Related Questions