Как использовать параметризацию pytest с входными данными, которые необходимо изменить?

#python #pytest

Вопрос:

Большинство примеров, которые я видел, показывают простой случай, когда входы/выходы могут быть выражены в строке:

 @pytest.mark.parametrize("test_input", [1, 2, 3, 4])
 

Как мне следует работать с параметрами, требующими одной или двух строк изменений?
экс:

 test_input1 = User()
test_input1.name = 'John'

test_input2 = User()
test_input2.phone = '1234567890'
test_input2.address = '123 Main St'

test_input3 = User()
test_input3.initials.middle = 'A'

test_input4 = User()
test_input4.make_super_user()
 

Давайте предположим, что я не могу передать их в качестве параметров конструктора, так User(name='John') что это не вариант.

Комментарии:

1. Сделать заводскую функцию, которая это делает? Напр.. def create_user(**attrs): user = User(); for attr, value in attrs.items(): setattr(user, attr, value); return user

Ответ №1:

Есть несколько способов сделать это:

  1. Предоставьте информацию, необходимую для настройки объектов, затем выполните настройку в тестовой функции. Это немного громоздко для ваших конкретных примеров, когда настройка выполняется не по обычному шаблону, но все равно работает:
     @pytest.mark.parametrize(
            'params', [
                dict(
                    name='John',
                ),
                dict(
                    phone='1234567890',
                    address='123 Main St',
                ),
                dict(
                    initials=dict(middle='A'),
                ),
                dict(
                    is_super_user=True,
                ),
            ]
    )
    def test_user(params):
        user = User()
    
        if 'name' in params:
            user.name = params['name']
    
        if 'phone' in params:
            user.phone = params['phone']
    
        if 'address' in params:
            user.phone = params['address']
    
        if 'initials' in params:
            user.initials.middle = params['initials']['middle']
    
        if params.get('is_super_user')
            user.make_super_user()
    
        assert ...
     
  2. Параметризуйте тестовую функцию с помощью заводских функций. Эта идея немного отличается от того, что предлагал Джонршарп в комментариях, потому что в этом случае вы бы написали отдельную фабрику для каждого тестового случая. Тем не менее, вы могли бы сократить шаблон для примеров 1 и 2, написав «фабричную фабрику», используя код, аналогичный коду Джонршарпа:
     def make_user_with_name():
        user = User()
        user.name = 'John'
        return user
    
    def make_user_with_phone_address():
        user = User()
        user.phone = '1234567890'
        user.address = '123 Main St'
        return user
    
    def make_user_with_middle_initial():
        user = User()
        user.initial.middle = 'A'
        return user
    
    def make_super_user():
        user = User()
        user.make_super_user()
        return user
    
    @pytest.mark.parametrize(
            'factory', [
                make_user_with_name,
                make_user_with_phone_address,
                make_user_with_middle_initial,
                make_super_user,
            ]
    )
    def test_user(factory):
        user = factory()
        assert ...
     
  3. Используется exec() для создания объектов из строк. Это хороший подход, когда вы загружаете параметры из отдельного файла (что я настоятельно рекомендую; см. parametrize_from_file), хотя в приведенном ниже примере для простоты есть все на python. Обратите внимание, что каждый фрагмент должен определять глобальную переменную с именем user :
     @pytest.mark.parametrize(
        'snippet', [
                """
    user = User()
    user.name = 'John'
    """,
                """
    user = User()
    user.phone = '1234567890'
    user.address = '123 Main St'
    """,
                """
    user = User()
    user.initials.middle = 'A'
    """,
                """
    user = User()
    user.make_super_user()
    """,
        ]
    )
    def test_user(snippet):
        scope = {}
        exec(snippet, scope)
        user = scope['user']
    
        assert ...
     

Комментарии:

1. exec выглядит привлекательно, помимо предоставления параметров в виде строк. parametrize_from_file выглядит как нечто, что мог бы перенять pytest.