Автоматизация тестирования на Python: практические решения и варианты

Раздел: Тестирование -> Автоматизация тестирования

Введение в автоматизацию тестирования на Python

Автоматизация тестирования позволяет повысить качество программного продукта, сократить время регрессионных проверок и избежать человеческих ошибок. Python предлагает несколько инструментов для этой задачи, каждый из которых подходит для разных сценариев.

Основной инструмент: pytest

Pytest является наиболее популярным фреймворком благодаря простоте, мощным фикстурам и обширной экосистеме плагинов. Он поддерживает как простые assert-проверки, так и сложные сценарии с параметризацией и маркировкой тестов.

Как написать и запустить простой тест с помощью pytest?

Установка фреймворка выполняется через pip:

pip install pytest

автоматизация тестирования на python (автоматизация тестирования на python)

Создадим файл test_example.py с простой функцией и тестом:

def add(a, b):
    return a + b

def test_add():
    assert add(2, 3) == 5
    assert add(-1, 1) == 0

Запуск теста из терминала:

pytest test_example.py

Pytest автоматически находит все функции, начинающиеся с test_, и выполняет их. При успешном прохождении выводится точка, при ошибке – подробный трейс.

Типичные ошибки

  • Функция не начинается с test_ – тест не будет обнаружен.
  • Отсутствие файла __init__.py в пакете может привести к проблемам импорта при сложной структуре проекта.
  • Путаница при использовании assert с None – например, assert result is None вместо assert result == None может дать ложный результат.

Как организовать тесты с фикстурами и параметризацией?

Фикстуры позволяют подготавливать окружение (базы данных, временные файлы, моки) и очищать его после теста. Пример фикстуры для создания временного файла:

import pytest
import tempfile
import os

@pytest.fixture
def temp_file():
    # Создаём временный файл и записываем данные
    fd, path = tempfile.mkstemp()
    with os.fdopen(fd, 'w') as tmp:
        tmp.write('test data')
    yield path  # передаём путь в тест
    # Удаляем файл после теста
    os.remove(path)

def test_read_temp_file(temp_file):
    with open(temp_file, 'r') as f:
        content = f.read()
    assert content == 'test data'

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

import pytest

@pytest.mark.parametrize('a, b, expected', [
    (1, 2, 3),
    (0, 0, 0),
    (-5, 5, 0),
    (100, -100, 0)
])
def test_add_parametrized(a, b, expected):
    assert a + b == expected

Для каждого набора создаётся отдельный тест с понятным именем. Это упрощает поиск ошибок при провале.

Проблемы и их решения

Если параметризованных данных много, вывод pytest может стать слишком длинным. Решение – использование маркера @pytest.mark.slow для медленных тестов или фильтрация по группам. Также возможно переопределение идентификатора параметров через ids:

@pytest.mark.parametrize('a,b,expected', [
    (1,2,3),
    (0,0,0)
], ids=['positive', 'zero'])
def test_add(a,b,expected):
    assert a + b == expected

Альтернативы pytest

Несмотря на лидерство pytest, существуют и другие решения. Рассмотрим наиболее употребимые.

Вариант 1: unittest (встроенный модуль)

Как написать тесты с использованием встроенного модуля unittest?

Unittest вдохновлён JUnit и требует создания классов-наследников от TestCase. Пример:

import unittest

def multiply(a, b):
    return a * b

class TestMultiply(unittest.TestCase):
    def test_positive(self):
        self.assertEqual(multiply(2, 3), 6)
    
    def test_negative(self):
        self.assertEqual(multiply(-1, 5), -5)

if __name__ == '__main__':
    unittest.main()

Запускается так же: python test_unittest.py. Unittest предоставляет множество assert-методов (assertEqual, assertTrue, assertRaises и др.) и поддерживает setUp/tearDown для инициализации.

Ограничения unittest

  • Больше шаблонного кода из-за классов и методов setUp.
  • Медленнее выполняется по сравнению с pytest из-за дополнительных накладных расходов.
  • Сложнее организовать параметризацию (требуются сторонние библиотеки или написание собственных декораторов).

Вариант 2: doctest (для проверки документации)

Как использовать doctest для тестирования примеров в docstring?

Doctest предназначен для проверки примеров, приведённых в документации. Пример:

def factorial(n):
    """
    Вычисление факториала.
    >>> factorial(5)
    120
    >>> factorial(0)
    1
    """
    if n == 0:
        return 1
    return n * factorial(n-1)

if __name__ == '__main__':
    import doctest
    doctest.testmod()

Запуск: python test_doctest.py. Если примеры не совпадают, doctest сообщит об ошибке.

Недостатки doctest

  • Тесты смешиваются с кодом, что ухудшает читаемость при большом объёме.
  • Сложно тестировать внешние зависимости (сеть, базы данных).
  • Порядок вывода недетерминированных данных (например, множество) может привести к ложным сбоям.

Вариант 3: nose2 (наследник nose)

Как использовать nose2 для автоматического обнаружения тестов?

Nose2 – форк оригинального nose, поддерживает автоматическое обнаружение тестов по шаблону, плагины и параллельный запуск. Установка:

pip install nose2

Создадим файл test_nose.py:

def test_upper():
    assert 'hello'.upper() == 'HELLO'

def test_isupper():
    assert 'HELLO'.isupper()
    assert not 'Hello'.isupper()

Запуск:

nose2

Nose2 предоставляет такие возможности, как --test-runner, плагины для покрытия и фикстуры через атрибуты.

Проблемы nose2

  • Сообщество малоактивно, многие плагины не обновляются.
  • Нет встроенной поддержки параметризации (требуются сторонние расширения).
  • Конфигурация может быть запутанной (файлы setup.cfg, nose2.cfg).

Заключение

Выбор инструмента автоматизации тестирования зависит от проекта. Для большинства новых разработок рекомендуется pytest – он гибкий, быстро работает и имеет большое сообщество. Unittest подойдёт для тех, кто привык к строгой объектно-ориентированной модели. Doctest удобен для небольших проверок в документации, а nose2 может быть полезен при миграции со старого кода. В любом случае, автоматизация тестирования на Python даёт мощный арсенал для поддержания качества кода.

Расширенные примеры автоматизации тестирования на Python

Пример 1: Параметризованные тесты с разными типами данных

Иногда необходимо проверить функцию на множестве входных данных, включая граничные случаи. Используем pytest.mark.parametrize с комбинациями значений.

Пример
import pytest

def divide(a, b):
    if b == 0:
        raise ValueError('Division by zero')
    return a / b

@pytest.mark.parametrize('a,b,expected', [
    (10, 2, 5.0),
    (7, 3, 7/3),
    (0, 1, 0.0),
    (-4, 2, -2.0),
    (1, 3, 1/3)
])
def test_divide_normal(a, b, expected):
    assert divide(a, b) == expected

@pytest.mark.parametrize('a,b', [
    (5, 0),
    (0, 0),
    (100, 0)
])
def test_divide_by_zero(a, b):
    with pytest.raises(ValueError, match='Division by zero'):
        divide(a, b)

Результат выполнения (фрагмент вывода pytest):

test_parametrize.py ........                          [100%]
8 passed in 0.04s

Каждый тест выполняется отдельно, при падении одного из них остальные продолжают работу.

Пример 2: Тестирование REST API с помощью pytest и requests

Для проверки внешних HTTP-сервисов удобно использовать библиотеку requests. Создадим фикстуру для базового URL и протестируем несколько эндпоинтов.

Пример
import pytest
import requests

@pytest.fixture
def base_url():
    return 'https://jsonplaceholder.typicode.com'

def test_get_posts(base_url):
    response = requests.get(f'{base_url}/posts/1')
    assert response.status_code == 200
    data = response.json()
    assert 'userId' in data
    assert 'title' in data

def test_create_post(base_url):
    payload = {'title': 'foo', 'body': 'bar', 'userId': 1}
    response = requests.post(f'{base_url}/posts', json=payload)
    assert response.status_code == 201
    created = response.json()
    assert created['title'] == 'foo'
    assert created['body'] == 'bar'
    assert created['userId'] == 1

def test_error_404(base_url):
    response = requests.get(f'{base_url}/posts/9999')
    assert response.status_code == 404

Вывод при успешном прохождении:

test_api.py ...                                   [100%]
3 passed

Если API недоступен, тесты упадут с ошибкой соединения. Для изоляции рекомендуется использовать моки (библиотека responses или unittest.mock).

Пример 3: Тестирование веб-интерфейса с Selenium WebDriver

Автоматизация браузера позволяет проверять UI. Установите selenium и драйвер (например, ChromeDriver).

Пример
import pytest
from selenium import webdriver
from selenium.webdriver.common.by import By

@pytest.fixture
def browser():
    driver = webdriver.Chrome()
    yield driver
    driver.quit()

def test_search_google(browser):
    browser.get('https://www.google.com')
    search_box = browser.find_element(By.NAME, 'q')
    search_box.send_keys('Python автоматизация тестирования')
    search_box.submit()
    assert 'Python' in browser.title

Результат (успех):

test_selenium.py .                                [100%]
1 passed

Типичные проблемы: версия драйвера не соответствует версии браузера, отсутствие драйвера в PATH. Решение – использование WebDriver Manager (pip install webdriver-manager).

Пример 4: Использование mock для изоляции зависимостей

При тестировании функций, вызывающих внешние сервисы, полезно подменять их с помощью unittest.mock.

Пример
from unittest.mock import patch
import pytest

# Функция, которая получает данные из внешнего API
def fetch_user_name(user_id):
    import requests
    response = requests.get(f'https://api.example.com/users/{user_id}')
    return response.json()['name']

# Тест с моком
def test_fetch_user_name():
    with patch('requests.get') as mock_get:
        mock_get.return_value.status_code = 200
        mock_get.return_value.json.return_value = {'name': 'Иван'}
        result = fetch_user_name(1)
        assert result == 'Иван'
        mock_get.assert_called_once_with('https://api.example.com/users/1')

Вывод:

test_mock.py .                                    [100%]
1 passed

Mock позволяет тестировать код без реальных сетевых вызовов, что ускоряет тесты и делает их детерминированными.

Пример 5: Генерация отчётов Allure для наглядности

Allure Framework формирует детальные отчёты с историей и вложениями. Установите allure-pytest и allure-commandline.

Пример
pip install allure-pytest

Добавим шаги в тест с помощью декораторов:

Пример
import allure
import pytest

@allure.step('Сложение двух чисел')
def add(a, b):
    return a + b

@allure.feature('Арифметика')
@allure.story('Сложение')
def test_add_allure():
    result = add(3, 4)
    allure.attach(str(result), name='Результат', attachment_type=allure.attachment_type.TEXT)
    assert result == 7

Запуск с генерацией отчёта:

Пример
pytest --alluredir=./allure-results test_allure.py
allure serve ./allure-results

Откроется веб-страница с визуализацией пройденных тестов, шагов и вложений.

Эти примеры демонстрируют гибкость Python в автоматизации различных видов тестирования. Используя комбинации pytest, mock, Selenium и других библиотек, можно покрыть практически любой сценарий проверки качества.

Автоматизация тестирования на Python - comments

En
автоматизация тестирования на python (python)