Документация Python неофициальный перевод
Содержание страницы

26.8. test – Пакет регрессионных тестов для Pythontest – Regression tests package for Python

Примечание

Пакет test предназначен только для внутреннего использования Python. Он dокументирован для разработчиков ядра Python. Использование этого пакета за пределами стандартной библиотеки Python не рекомендуется, так как упомянутый здесь код может быть изменён или удалён без уведомления между выпусками Python.


Пакет test содержит все регрессионные тесты для Python, а также модули test.support и test.regrtest. test.support используется для расширения возможностей тестов, а test.regrtest управляет набором тестов.

Каждый модуль в пакете test, имя которого начинается с test_, является набором тестов для конкретного модуля или функции. Все новые тесты следует писать с использованием модулей unittest или doctest. Некоторые старые тесты написаны в «традиционном» стиле тестирования, сравнивающем вывод, направленный в sys.stdout; такой стиль теста считается устаревшим.

См. также

Модуль unittest

Написание регрессионных тестов PyUnit.

Модуль doctest

Тесты, встроенные в строки документации.

26.8.1. Написание модульных тестов для пакета testWriting Unit Tests for the test package

Для тестов, использующих модуль unittest, рекомендуется следовать нескольким правилам. Одно из них – называть модуль тестов, начиная его имя с test_ и заканчивая названием тестируемого модуля. Методы тестов в модуле тестирования должны начинаться с test_ и заканчиваться описанием того, что метод тестирует. Это необходимо, чтобы тестовый драйвер распознавал методы как тестовые. Кроме того, не следует включать строку документации для метода. Вместо этого следует использовать комментарий (например, # Tests function returns only True or False) для документирования тестовых методов. Это связано с тем, что строки документации выводятся на печать, если они существуют, и таким образом не указывается, какой именно тест выполняется.

Часто используется стандартная заготовка:

import unittest
from test import support

class MyTestCase1(unittest.TestCase):

    # Используйте setUp() и tearDown() только при необходимости

    def setUp(self):
        ... code to execute in preparation for tests ...

    def tearDown(self):
        ... code to execute to clean up after tests ...

    def test_feature_one(self):
        # Тест первой возможности.
        ... testing code ...

    def test_feature_two(self):
        # Тест второй возможности.
        ... testing code ...

    ... more test methods ...

class MyTestCase2(unittest.TestCase):
    ... same structure as MyTestCase1 ...

... more test classes ...

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

Этот шаблон кода позволяет запускать набор тестов с помощью test.regrtest, самостоятельно как скрипт, поддерживающий CLI unittest, или через CLI python -m unittest.

Цель регрессионного тестирования – попытаться сломать код. Это приводит к нескольким правилам, которым следует следовать:

  • Набор тестов должен задействовать все классы, функции и константы. Это включает не только внешний API, который будет представлен внешнему миру, но и «приватный» код.

  • Предпочтительно тестирование методом белого ящика (анализ тестируемого кода при написании тестов). Тестирование методом чёрного ящика (тестирование только опубликованного пользовательского интерфейса) недостаточно для проверки всех граничных и крайних случаев.

  • Убедитесь, что все возможные значения протестированы, включая недопустимые. Это гарантирует, что не только все допустимые значения принимаются, но и неправильные значения обрабатываются корректно.

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

  • Добавляйте явные тесты для любых ошибок, обнаруженных в тестируемом коде. Это гарантирует, что ошибка не возникнет снова, если код будет изменён в будущем.

  • Убедитесь, что после тестов выполнена очистка (например, закрыты и удалены все временные файлы).

  • Если тест зависит от конкретного условия операционной системы, то проверьте, что условие уже выполняется, прежде чем запускать тест.

  • Импортируйте как можно меньше модулей и делайте это как можно раньше. Это минимизирует внешние зависимости тестов, а также уменьшает возможные аномальные поведения из-за побочных эффектов импорта модуля.

  • Старайтесь максимально повторно использовать код. Иногда тесты могут различаться такой мелочью, как тип используемых входных данных. Минимизируйте дублирование кода, создавая подкласс базового тестового класса, который определяет входные данные:

    class TestFuncAcceptsSequencesMixin:
    
        func = mySuperWhammyFunction
    
        def test_func(self):
            self.func(self.arg)
    
    class AcceptLists(TestFuncAcceptsSequencesMixin, unittest.TestCase):
        arg = [1, 2, 3]
    
    class AcceptStrings(TestFuncAcceptsSequencesMixin, unittest.TestCase):
        arg = 'abc'
    
    class AcceptTuples(TestFuncAcceptsSequencesMixin, unittest.TestCase):
        arg = (1, 2, 3)
    

    При использовании этого шаблона помните, что все классы, наследующие от unittest.TestCase, запускаются как тесты. Класс Mixin в приведённом выше примере не содержит никаких данных и поэтому не может быть запущен сам по себе, следовательно, он не наследует от unittest.TestCase.

См. также

Разработка через тестирование

Книга Кента Бека о написании тестов до кода.

26.8.2. Запуск тестов с помощью интерфейса командной строкиRunning tests using the command-line interface

Пакет test может быть запущен как скрипт для выполнения набора регрессионных тестов Python благодаря опции -m: python -m test. Под капотом он использует test.regrtest; вызов python -m test.regrtest, использовавшийся в предыдущих версиях Python, по-прежнему работает. При самостоятельном запуске скрипт автоматически начинает выполнение всех регрессионных тестов пакета test. Это делается путём поиска всех модулей в пакете, имена которых начинаются с test_, их импорта и выполнения функции test_main(), если она присутствует, или загрузки тестов через unittest.TestLoader.loadTestsFromModule, если test_main не существует. Также скрипту можно передать имена тестов для выполнения. Указание одного регрессионного теста (python -m test test_spam) минимизирует вывод и будет показано только, прошёл тест или нет.

Запуск test напрямую позволяет задать, какие ресурсы доступны для использования тестами. Для этого используется опция командной строки -u. Указание all в качестве значения опции -u включает все возможные ресурсы: python -m test -uall. Если нужны все ресурсы, кроме одного (более частый случай), после all можно указать разделённый запятыми список ненужных ресурсов. Команда python -m test -uall,-audio,-largefile запустит test со всеми ресурсами, кроме audio и largefile. Список всех ресурсов и дополнительные параметры командной строки можно узнать, выполнив python -m test -h.

Некоторые другие способы выполнения регрессионных тестов зависят от платформы, на которой они выполняются. В Unix можно выполнить make test в корневом каталоге сборки Python. В Windows выполнение rt.bat из каталога PCBuild запустит все регрессионные тесты.

26.9. test.support – Утилиты для набора тестов Pythontest.support – Utilities for the Python test suite

Модуль test.support предоставляет поддержку для набора регрессионных тестов Python.

Примечание

test.support не является публичным модулем. Он документирован здесь, чтобы помочь разработчикам Python писать тесты. API этого модуля может измениться без соблюдения обратной совместимости между выпусками.

В этом модуле определены следующие исключения:

exception test.support.TestFailed

Исключение, которое вызывается при неудаче теста. Устарело в пользу тестов на основе unittest и методов утверждений unittest.TestCase.

exception test.support.ResourceDenied

Подкласс unittest.SkipTest. Возбуждается, когда ресурс (например, сетевое соединение) недоступен. Возбуждается функцией requires().

Модуль test.support определяет следующие константы:

test.support.verbose

True, если включен подробный вывод. Следует проверять, когда требуется более детальная информация о выполняющемся тесте. verbose устанавливается test.regrtest.

test.support.is_jython

True, если запущенный интерпретатор – Jython.

test.support.TESTFN

Устанавливается в имя, безопасное для использования в качестве имени временного файла. Любой созданный временный файл должен быть закрыт и удалён.

Модуль test.support определяет следующие функции:

test.support.forget(module_name)

Удаляет модуль module_name из sys.modules и удаляет все скомпилированные в байт-код файлы этого модуля.

test.support.is_resource_enabled(resource)

Возвращает True, если resource включён и доступен. Список доступных ресурсов задаётся только при выполнении тестов test.regrtest.

test.support.requires(resource, msg=None)

Возбуждает ResourceDenied, если resource недоступен. msg – это аргумент для ResourceDenied, если он возбуждается. Всегда возвращает True, если вызвана из функции, чей __name__ равен '__main__'. Используется при выполнении тестов с помощью test.regrtest.

test.support.findfile(filename, subdir=None)

Возвращает путь к файлу с именем filename. Если совпадений не найдено, возвращается filename. Это не считается ошибкой, так как оно может быть путём к файлу.

Установка subdir задаёт относительный путь для поиска файла, а не прямой поиск в каталогах пути.

test.support.run_unittest(*classes)

Выполняет подклассы unittest.TestCase, переданные функции. Функция сканирует классы на наличие методов, начинающихся с префикса test_, и выполняет тесты по отдельности.

Также допускается передавать строки в качестве параметров; они должны быть ключами в sys.modules. Каждый связанный модуль будет просканирован функцией unittest.TestLoader.loadTestsFromModule(). Обычно это встречается в следующей функции test_main():

def test_main():
    support.run_unittest(__name__)

Это запустит все тесты, определенные в указанном модуле.

test.support.run_doctest(module, verbosity=None)

Запускает doctest.testmod() для указанного module. Возвращает (failure_count, test_count).

Если verbosity равно None, то doctest.testmod() запускается с verbosity равным verbose. В противном случае он запускается с verbosity равным None.

test.support.check_warnings(*filters, quiet=True)

Удобная обёртка для warnings.catch_warnings(), упрощающая проверку того, что предупреждение было корректно возбуждено. Она примерно эквивалентна вызову warnings.catch_warnings(record=True) с warnings.simplefilter(), установленным в always, и с возможностью автоматической проверки записанных результатов.

check_warnings принимает кортежи из 2 элементов вида ("message regexp", WarningCategory) в качестве позиционных аргументов. Если указан один или несколько filters, или если необязательный ключевой аргумент quiet равен False, он проверяет, что предупреждения соответствуют ожидаемым: каждый указанный фильтр должен совпадать хотя бы с одним предупреждением, вызванным вложенным кодом, иначе тест завершается неудачей; если возникают предупреждения, не совпадающие ни с одним из указанных фильтров, тест также завершается неудачей. Чтобы отключить первую из этих проверок, установите quiet в True.

Если аргументы не указаны, по умолчанию используется:

check_warnings(("", Warning), quiet=True)

В этом случае перехватываются все предупреждения и исключения не возбуждаются.

При входе в менеджер контекста возвращается экземпляр WarningRecorder. Базовый список предупреждений из catch_warnings() доступен через атрибут warnings объекта recorder. Для удобства атрибуты объекта, представляющего последнее предупреждение, также можно получить напрямую через объект recorder (см. пример ниже). Если предупреждений не было, то любой из атрибутов, которые обычно ожидаются у объекта, представляющего предупреждение, вернёт None.

Объект recorder также имеет метод reset(), который очищает список предупреждений.

Менеджер контекста предназначен для использования следующим образом:

with check_warnings(("assertion is always true", SyntaxWarning),
                    ("", UserWarning)):
    exec('assert(False, "Hey!")')
    warnings.warn(UserWarning("Hide me!"))

В этом случае, если какое-либо из предупреждений не было возбуждено, или было возбуждено другое предупреждение, check_warnings() возбудит исключение.

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

with check_warnings(quiet=True) as w:
    warnings.warn("foo")
    assert str(w.args[0]) == "foo"
    warnings.warn("bar")
    assert str(w.args[0]) == "bar"
    assert str(w.warnings[0].args[0]) == "foo"
    assert str(w.warnings[1].args[0]) == "bar"
    w.reset()
    assert len(w.warnings) == 0

Здесь будут перехвачены все предупреждения, и тестовый код напрямую проверяет перехваченные предупреждения.

Изменено в версии 3.2: Новые необязательные аргументы filters и quiet.

test.support.captured_stdin()
test.support.captured_stdout()
test.support.captured_stderr()

Менеджер контекста, который временно заменяет указанный поток данных объектом io.StringIO.

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

with captured_stdout() as stdout, captured_stderr() as stderr:
    print("hello")
    print("error", file=sys.stderr)
assert stdout.getvalue() == "hello\n"
assert stderr.getvalue() == "error\n"

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

with captured_stdin() as stdin:
    stdin.write('hello\n')
    stdin.seek(0)
    # вызвать тестовый код, который читает из sys.stdin
    captured = input()
self.assertEqual(captured, "hello")
test.support.temp_dir(path=None, quiet=False)

Менеджер контекста, который создаёт временную директорию по path и возвращает эту директорию.

Если path равен None, временная директория создаётся с помощью tempfile.mkdtemp(). Если quiet равен False, менеджер контекста вызывает исключение при ошибке. В противном случае, если path указан и не может быть создан, выдаётся только предупреждение.

test.support.change_cwd(path, quiet=False)

Менеджер контекста, который временно изменяет текущую рабочую директорию на path и возвращает эту директорию.

Если quiet равно False, менеджер контекста возбуждает исключение при ошибке. В противном случае он только выводит предупреждение и оставляет текущую рабочую директорию неизменной.

test.support.temp_cwd(name='tempcwd', quiet=False)

Менеджер контекста, который временно создаёт новую директорию и изменяет текущую рабочую директорию (CWD).

Менеджер контекста создаёт временную директорию в текущей директории с именем name, прежде чем временно изменить текущую рабочую директорию. Если name равно None, временная директория создаётся с помощью tempfile.mkdtemp().

Если quiet равен False и не удаётся создать или изменить текущую рабочую директорию (CWD), возникает ошибка. В противном случае выдаётся только предупреждение и используется исходная CWD.

test.support.temp_umask(umask)

Менеджер контекста, который временно устанавливает umask процесса.

Возвращает True, если ОС поддерживает символические ссылки, и False в противном случае.

Декоратор для запуска тестов, требующих поддержки символических ссылок.

@test.support.anticipate_failure(condition)

Декоратор для условной пометки тестов с помощью unittest.expectedFailure(). Любое использование этого декоратора должно сопровождаться комментарием с указанием соответствующей проблемы в трекере.

@test.support.run_with_locale(catstr, *locales)

Декоратор для выполнения функции в другой локали, с корректным сбросом локали после завершения. catstr – это категория локали в виде строки (например, "LC_ALL"). Переданные locales будут перебираться последовательно, и будет использована первая подходящая локаль.

test.support.make_bad_fd()

Создаёт некорректный файловый дескриптор, открывая и закрывая временный файл, и возвращая его дескриптор.

test.support.import_module(name, deprecated=False)

Эта функция импортирует и возвращает указанный модуль. В отличие от обычного импорта, эта функция вызывает unittest.SkipTest, если модуль не удаётся импортировать.

Сообщения об устаревании модулей и пакетов подавляются во время этого импорта, если устарело равен True.

Новое в версии 3.1.

test.support.import_fresh_module(name, fresh=(), blocked=(), deprecated=False)

Эта функция импортирует и возвращает свежую копию указанного модуля Python, удаляя его из sys.modules перед импортом. Обратите внимание, что, в отличие от reload(), оригинальный модуль не затрагивается этой операцией.

fresh – это итерируемый объект с дополнительными именами модулей, которые также удаляются из кеша sys.modules перед импортом.

blocked – это итерируемый объект с именами модулей, которые заменяются на None в кеше модулей во время импорта, чтобы попытки их импортировать вызывали ImportError.

Указанный модуль и все модули, перечисленные в параметрах fresh и blocked, сохраняются перед началом импорта, а затем возвращаются обратно в sys.modules после завершения свежего импорта.

Сообщения об устаревании модулей и пакетов подавляются во время этого импорта, если устарело равен True.

Эта функция вызовет ImportError, если указанный модуль не удаётся импортировать.

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

# Получение копий модуля warnings для тестирования без влияния на
# версию, используемую остальной частью тестового набора. Одна копия использует
# реализацию на C, другая принудительно использует запасную реализацию на чистом
# реализация
py_warnings = import_fresh_module('warnings', blocked=['_warnings'])
c_warnings = import_fresh_module('warnings', fresh=['_warnings'])

Новое в версии 3.1.

test.support.bind_port(sock, host=HOST)

Привязывает сокет к свободному порту и возвращает номер порта. Полагается на временные порты, чтобы гарантировать использование непривязанного порта. Это важно, так как многие тесты могут выполняться одновременно, особенно в среде buildbot. Этот метод вызывает исключение, если sock.family равен AF_INET, sock.type равен SOCK_STREAM, и на сокете установлены SO_REUSEADDR или SO_REUSEPORT. Тесты никогда не должны устанавливать эти параметры сокета для TCP/IP-сокетов. Единственный случай для установки этих параметров – тестирование многоадресной рассылки через несколько UDP-сокетов.

Кроме того, если доступен параметр сокета SO_EXCLUSIVEADDRUSE (например, в Windows), он будет установлен на сокете. Это предотвратит привязку других процессов к нашему узлу/порту на время теста.

test.support.find_unused_port(family=socket.AF_INET, socktype=socket.SOCK_STREAM)

Возвращает неиспользуемый порт, подходящий для привязки. Для этого создаётся временный сокет с тем же семейством и типом, что и у параметра sock (по умолчанию AF_INET, SOCK_STREAM), и выполняется его привязка к указанному адресу узла (по умолчанию 0.0.0.0) с портом, установленным в 0, что заставляет ОС выделить неиспользуемый временный порт. Затем временный сокет закрывается и удаляется, а временный порт возвращается.

Для любых тестов, где серверный сокет должен быть привязан к определённому порту на время теста, следует использовать либо этот метод, либо bind_port(). Какой из них выбрать, зависит от того, создаёт ли вызывающий код сокет Python, или же нужно предоставить неиспользуемый порт в конструкторе или передать внешней программе (например, аргумент -accept для режима s_server в openssl). Всегда предпочитайте bind_port() перед find_unused_port(), где это возможно. Использовать жёстко заданный порт не рекомендуется, так как это может сделать невозможным одновременный запуск нескольких экземпляров теста, что является проблемой для сборочных ботов.

test.support.load_package_tests(pkg_dir, loader, standard_tests, pattern)

Общая реализация протокола unittest load_tests для использования в тестовых пакетах. pkg_dir – это корневой каталог пакета; loader, standard_tests и pattern – аргументы, ожидаемые load_tests. В простых случаях __init__.py тестового пакета может быть следующим:

import os
from test.support import load_package_tests

def load_tests(*args):
    return load_package_tests(os.path.dirname(__file__), *args)
test.support.detect_api_mismatch(ref_api, other_api, *, ignore=())

Возвращает набор атрибутов, функций или методов ref_api, не найденных в other_api, за исключением определённого списка элементов, которые следует игнорировать в этой проверке, указанного в ignore.

По умолчанию пропускаются закрытые атрибуты, начинающиеся с '_', но включаются все магические методы, то есть начинающиеся и заканчивающиеся на '__'.

Новое в версии 3.5.

test.support.check__all__(test_case, module, name_of_module=None, extra=(), blacklist=())

Утверждает, что переменная __all__ модуля module содержит все публичные имена.

Публичные имена модуля (его API) определяются автоматически на основе того, соответствуют ли они соглашению об именовании публичных имён и были ли определены в module.

Аргумент name_of_module может задавать (в виде строки или кортежа строк), в каком модуле (или модулях) может быть определено API, чтобы считаться публичным. Один из примеров – когда module импортирует часть своего публичного API из других модулей, возможно, C-бэкенда (например, csv и его _csv).

Аргумент extra может быть набором имён, которые в противном случае не были бы автоматически распознаны как «публичные», например объекты без соответствующего атрибута __module__. Если он указан, то будет добавлен к автоматически обнаруженным.

Аргумент blacklist может быть набором имён, которые не должны рассматриваться как часть публичного API, даже если их имена говорят об обратном.

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

import bar
import foo
import unittest
from test import support

class MiscTestCase(unittest.TestCase):
    def test__all__(self):
        support.check__all__(self, foo)

class OtherTestCase(unittest.TestCase):
    def test__all__(self):
        extra = {'BAR_CONST', 'FOO_CONST'}
        blacklist = {'baz'}  # Недокументированное имя.
        # bar импортирует часть своего API из _bar.
        support.check__all__(self, bar, ('bar', '_bar'),
                             extra=extra, blacklist=blacklist)

Новое в версии 3.6.

Модуль test.support определяет следующие классы:

class test.support.TransientResource(exc, **kwargs)

Экземпляры являются контекстным менеджером, который возбуждает ResourceDenied, если возникает указанный тип исключения. Все именованные аргументы трактуются как пары атрибут/значение для сравнения с любым исключением, возникшим внутри оператора with. Только если все пары правильно совпадают с атрибутами исключения, возбуждается ResourceDenied.

class test.support.EnvironmentVarGuard

Класс, используемый для временной установки или отмены переменных окружения. Экземпляры можно использовать как контекстный менеджер, и они имеют полный интерфейс словаря для запроса/изменения базового os.environ. После выхода из контекстного менеджера все изменения переменных окружения, сделанные через этот экземпляр, будут отменены.

Изменено в версии 3.1: Добавлен интерфейс словаря.

EnvironmentVarGuard.set(envvar, value)

Временно устанавливает переменную окружения envvar в значение value.

EnvironmentVarGuard.unset(envvar)

Временно удаляет переменную окружения envvar.

class test.support.SuppressCrashReport

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

В Windows отключает диалоги отчётов об ошибках Windows с помощью SetErrorMode.

В UNIX resource.setrlimit() используется для установки мягкого ограничения resource.RLIMIT_CORE в 0, чтобы предотвратить создание файлов coredump.

На обеих платформах старое значение восстанавливается с помощью __exit__().

class test.support.WarningsRecorder

Класс, используемый для записи предупреждений в модульных тестах. Подробнее см. документацию check_warnings() выше.

class test.support.FakePath(path)

Простой объект, подобный пути. Он реализует __fspath__() метод, который просто возвращает аргумент path. Если path является исключением, оно будет возбуждено в __fspath__().