如何使用pytest确保正确创建对象?

时间:2017-07-10 07:27:02

标签: python python-3.x pytest

我有一个以特定格式保存的文件,以及一个将根据文件中的数据创建对象的类。

我想通过测试对象中的每个属性来确保正确提取文件/字符串中的所有值。

以下是我正在做的简化版本:

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_nametest_age中的代码几乎完全相同。在我的实际代码中,我为大约12个属性执行此操作。应该/可以将它合并为一个函数吗?怎么样?

如何清理我的测试代码?

谢谢!

编辑:

我认为这是相关的,但我不确定如何使其适用于我的情况:Can params passed to pytest fixture be passed in as a variable?

2 个答案:

答案 0 :(得分:2)

您可以添加一个fixture,它返回该类的对象,并在每次测试之前调用该fixture。我已经完成了一些更改并在get_object中创建了一个test_classlist.py,同时classlist.py就是这样。

get_object将为您提供该类的对象,您可以通过request模块在​​测试函数中使用该对象。我在request.instance.cobj中分配了该类对象。您可以在测试功能中访问它。

我从您的描述中获得的是您想要创建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

答案 1 :(得分:2)

  
    

我当前的设置有效,尽管每次测试都有不必要的无意中创建对象。我希望它们被创建一次。

  

这对我来说有点不必要的预优化,但如果您关心这一点,那么运行创建数据的函数在模块级别进行测试,这样它们只运行一次。

例如:

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

此外,test_name和test_age中的代码几乎相同。   在我的实际代码中,我为大约12个属性执行此操作。如若/罐   这被合并成一个单一的功能?怎么样?

     

如何清理我的测试代码?

有两种方法可以执行此操作,但是从您的示例中,为Student类提供equality magic method并使用它来测试您的代码(还为正确的代表添加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