Написать пост

Как писать тесты функций Python, если вы никогда этого не делали

Аватарка пользователя Елена Капаца

Разобрали на примере, как начинающим писать тесты для Python-кода и проверять вводимые почты на валидность

Обложка поста Как писать тесты функций Python, если вы никогда этого не делали

Основы тестирования сокращают вероятность ругательств со стороны тимлида и уменьшают количество ошибок на проде в целом. Особенно эту статью рекомендую дата-сайентистам, ибо онлайн-университеты почему-то урезают этот раздел на факультетах ИИ до минимума.

Проверка адресов email

Тест кода Python — это временная надстройка над основной частью программы. По мнению softwaretestinghelp.com, эта библиотека входит в тройку самых популярных инструментов. Давайте посмотрим на примере, как проверять вводимые адреса электронной почты.

Напишем функцию, проверяющую присутствие знака @, латиницы, и отсутствие некоторых спецсимволов:

			import string
def is_valid_email_address(s):
    s = s.lower()
    parts = s.split('@')
    if len(parts) != 2:
      # Если собачки нет
      return False
    allowed = set(string.ascii_lowercase + string.digits + '.-_')
    for part in parts:
        if not set(part) <= allowed:
          # Найдены неразрешенные символы
          return False
    return True
		

Теперь у нас есть система проверки email. Адреса вроде test@example.org, user123@tproger.ru действительны. А вот ‘not valid@example.org’ и ‘ivan ivanov’ — нет:

			print(is_valid_email_address('test@example.org')) # True
print(is_valid_email_address('user123@tproger.ru')) # True
print(is_valid_email_address('not valid@example.org')) # False
print(is_valid_email_address('ivan ivanov')) # False
		

pytest с легкостью автоматизирует такую проверку. Проверим валидность ящиков в три этапа:

			# проверяем верный адрес
def test_regular_email_validates():
    assert is_valid_email_address('test@example.org') # True

# проверяем на наличие одного знака @
def test_valid_email_has_one_at_sign():
    assert not is_valid_email_address('john.doe') # False

# проверяем на разрешенные символы
def test_valid_email_has_only_allowed_chars():
    assert not is_valid_email_address('john,doe@example.org') # True
    assert not is_valid_email_address('not valid@example.org') # True
		

Инструкция assert («допущение, утверждение») проверяет истинность выражения, not – инвертирует проверку

Важно понимать, что тестируем мы конкретный участок кода, а не сами почты. На проде is_valid_email_address() будет являться рабочим, а не тестирующим компонентом.

Запуск проверки

Сохраним test_regular_email_validates() в файл validator.py и запустим тест:

			pytest validator.py
		

Тест «обопрется» на assert и потому «соберет» 3 объекта (‘collected 3 items’):

			======================== test session starts ========================
platform darwin -- Python 3.9.6, pytest-7.0.1, pluggy-1.0.0
rootdir: /path/to/file
collected 3 items

validator.py ...                                                    [100%]

========================= 3 passed in 0.01s ==========================
		

Индикатор 100% показывает что наш валидатор работает штатно. Однако он далек от совершенства. john.doe@example прошел бы тест, как и john.doe+abc@gmail.com, хотя оба адреса недействительны.

Давайте добавим эти примеры в наш validator.py:

			def test_valid_email_can_have_plus_sign():
    assert is_valid_email_address('john.doe+abc@gmail.com')

def test_valid_email_must_have_a_tld():
    assert not is_valid_email_address('john.doe@example')
		

Теперь при перезапуске мы получим два «фэйла»:

			======================== test session starts =========================
platform darwin -- Python 3.9.6, pytest-7.0.1, pluggy-1.0.0
rootdir: /path/to/file
collected 5 items

test_validator.py ...FF                                                  [100%]

============================== FAILURES ==============================
________________ test_valid_email_can_have_plus_sign _________________

    def test_valid_email_can_have_plus_sign():
>       assert is_valid_email_address('john.doe+abc@gmail.com')
E       AssertionError: assert False
E        +  where False = is_valid_email_address('john.doe+abc@gmail.com')

test_validator.py:17: AssertionError
__________________ test_valid_email_must_have_a_tld __________________

    def test_valid_email_must_have_a_tld():
>       assert not is_valid_email_address('john.doe@example')
E       AssertionError: assert not True
E        +  where True = is_valid_email_address('john.doe@example')

test_validator.py:20: AssertionError
====================== short test summary info ======================
FAILED test_validator.py::test_valid_email_can_have_plus_sign - AssertionErro...
FAILED test_validator.py::test_valid_email_must_have_a_tld - AssertionError: ...
==================== 2 failed, 3 passed in 0.05s =====================
		

В выводе pytest появится новый раздел Failures, в котором подробно объясняется, в какой момент тест кода Python не удался. Это позволит отладить ошибки.

Фикстуры (Fixtures)

Обычно этим термином обозначают небольшой набор данных, которым наполняют хранилище приложения. В контексте тестирования фикстурами («арматурой») называют функции pytest.

Мы можем создать их с помощью декоратора pytest.fixture():

			import​ pytest

@pytest.fixture()
def database_environment():
    setup_database()
    yield
    teardown_database()
		

Обратите внимание, что настройка базы данных и удаление происходят в одном месте. Ключевое слово yield указывает pytest, где запустить тесты.

Чтобы фикстура действительно задейстовалась, добавим ее имя в качестве аргумента:

			def test_world(database_environment):
    assert 1 == 1
		

Полезные параметры командной строки

  • Запустить одну конкретную функцию:
			pytest test_validator.py::test_regular_email_validates
		
  • Перечислить функции в файле:
			pytest --collect-only
		
			======================== test session starts ========================
platform darwin -- Python 3.9.6, pytest-7.0.1, pluggy-1.0.0
rootdir: /path/to/file
collected 5 items

<Module test_validator.py>
  <Function test_regular_email_validates>
  <Function test_valid_email_has_one_at_sign>
  <Function test_valid_email_has_only_allowed_chars>
  <Function test_valid_email_can_have_plus_sign>
  <Function test_valid_email_must_have_a_tld>

===================== 5 tests collected in 0.01s =====================
		
  • Остановить при первой ошибке:
			pytest -x
		
  • Запустить только последний неудачный тест:
			pytest --lf
		
  • Запустите все тесты, но сначала последние неудачные:
			pytest --ff
		

Заключение

Теперь в нашем распоряжении простейший кейс проверки электронной почты. И мы знаем теперь, как использовать pytest и читать результаты. Если вы созрели для чего-то более масштабного, рекомендую статью Дениса Исангулова:

Следите за новыми постами
Следите за новыми постами по любимым темам
1К открытий2К показов