Параметризация теста#

Очень часто в тестах нужно проверять функцию/класс/метод на разных входящих данных. Один вариант, в этом случае, будет написать несколько assert в одном тесте.

def check_passwd(username, password, min_length=8, check_username=True):
    if len(password) < min_length:
        print('Пароль слишком короткий')
        return False
    elif check_username and username in password:
        print('Пароль содержит имя пользователя')
        return False
    else:
        print(f'Пароль для пользователя {username} прошел все проверки')
        return True


def test_password_min_length():
    assert check_passwd('user', '12345', min_length=3) == True
    assert check_passwd('user', '123456', min_length=5) == False
    assert check_passwd('user', 'userpass', min_length=5) == False

Этот вариант плох тем, что теперь все три проверки считаются одним тестом и если одна из проверок не проходит, следующие не проверяются.

Параметризация тестов позволяет указать несколько наборов данных, на которых надо проверить тест:

import pytest


@pytest.mark.parametrize(
    ("user", "passwd", "min_len", "result"),
    [
        ("user1", "123456", 4, True),
        ("user1", "123456", 8, False),
        ("user1", "123456", 6, True),
    ],
)
def test_min_len_param(user, passwd, min_len, result):
    assert check_passwd(user, passwd, min_length=min_len) == result

и, что особенно удобно, каждый набор данных срабатывает как отдельный запуск теста:

$ pytest test_check_password.py -v
============================ test session starts ==============================
...
collected 3 items

test_check_password.py::test_min_len_param[user1-123456-4-True] PASSED   [ 33%]
test_check_password.py::test_min_len_param[user1-123456-8-False] PASSED  [ 66%]
test_check_password.py::test_min_len_param[user1-123456-6-True] PASSED   [100%]

============================= 3 passed in 0.03s ================================

Пример использования параметризации теста для одного параметра:

import ipaddress


def is_ip_address(ip):
    if not isinstance(ip, str):
        return False
    try:
        ipaddress.ip_address(ip)
        return True
    except ValueError:
        return False


@pytest.mark.parametrize("ip", ["10.1.1.1", "224.1.1.1", "0.0.0.0"])
def test_is_ip_address_correct(ip):
    assert is_ip_address(ip) == True


@pytest.mark.parametrize("ip", ["500.1.1.1", "50.1.1", "a", 100])
def test_is_ip_address_wrong(ip):
    assert is_ip_address(ip) == False

Запуск теста:

$ pytest test_check_ip.py
========================= test session starts =======================
...
collected 9 items

test_check_ip.py::test_check_ip PASSED                          [ 11%]
test_check_ip.py::test_check_ip_correct[10.1.1.1] PASSED        [ 22%]
test_check_ip.py::test_check_ip_correct[224.1.1.1] PASSED       [ 33%]
test_check_ip.py::test_check_ip_correct[0.0.0.0] PASSED         [ 44%]
test_check_ip.py::test_check_ip_wrong[500.1.1.1] PASSED         [ 55%]
test_check_ip.py::test_check_ip_wrong[50.1.1] PASSED            [ 66%]
test_check_ip.py::test_check_ip_wrong[a] PASSED                 [ 77%]
test_check_ip.py::test_check_ip_wrong[100] PASSED               [ 88%]
test_check_ip.py::test_check_ip_wrong[ip4] PASSED               [100%]