Содержание страницы
25.5. test – Пакет регрессионных тестов для Python¶test – 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; такой стиль считается устаревшим.
См. также
25.5.1. Написание модульных тестов для пакета test¶Writing Unit Tests for the test package
Рекомендуется, чтобы тесты, использующие модуль unittest, следовали нескольким рекомендациям. Одна из них – называть тестовый модуль, начиная его с test_ и заканчивая именем тестируемого модуля. Методы тестирования в тестовом модуле должны начинаться с test_ и заканчиваться описанием того, что тестирует метод. Это необходимо, чтобы драйвер тестов распознавал методы как тестовые. Кроме того, не следует включать строку документации для метода. Вместо этого следует использовать комментарий (например, # Тестирует функция возвращает только True или 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 ...
def test_main():
support.run_unittest(MyTestCase1,
MyTestCase2,
... list other tests ...
)
if __name__ == '__main__':
test_main()
Этот шаблонный код позволяет запускать тестовый набор как с помощью test.regrtest, так и самостоятельно в качестве скрипта.
Цель регрессионного тестирования – попытаться сломать код. Это приводит к нескольким правилам, которым следует следовать:
Набор тестов должен проверять все классы, функции и константы. Это включает не только внешний API, который будет представлен внешнему миру, но и «приватный» код.
Предпочтение отдается тестированию «белого ящика» (анализ тестируемого кода при написании тестов). Тестирование «черного ящика» (тестирование только опубликованного пользовательского интерфейса) недостаточно полно, чтобы убедиться, что все граничные и крайние случаи протестированы.
Убедитесь, что проверены все возможные значения, включая недопустимые. Это гарантирует, что не только все корректные значения принимаются, но и неправильные значения обрабатываются корректно.
Исчерпайте как можно больше путей выполнения кода. Тестируйте места ветвления и соответственно подбирайте входные данные, чтобы гарантировать, что проходит как можно больше различных путей.
Добавляйте явные тесты для любых ошибок, обнаруженных в тестируемом коде. Это гарантирует, что ошибка не возникнет снова, если код будет изменён в будущем.
Убедитесь, что после тестов выполнена очистка (например, закрыты и удалены все временные файлы).
Если тест зависит от конкретного условия операционной системы, то проверьте, что условие уже выполняется, прежде чем запускать тест.
Импортируйте как можно меньше модулей и делайте это как можно раньше. Это минимизирует внешние зависимости тестов, а также уменьшает возможные аномальные поведения из-за побочных эффектов импорта модуля.
Старайтесь максимально повторно использовать код. Иногда тесты различаются такими мелочами, как тип входных данных. Минимизируйте дублирование кода, создавая подкласс базового тестового класса, который задаёт входные данные:
class TestFuncAcceptsSequences(unittest.TestCase): func = mySuperWhammyFunction def test_func(self): self.func(self.arg) class AcceptLists(TestFuncAcceptsSequences): arg = [1,2,3] class AcceptStrings(TestFuncAcceptsSequences): arg = 'abc' class AcceptTuples(TestFuncAcceptsSequences): arg = (1,2,3)
См. также
- Разработка через тестирование
- Книга Кента Бека о написании тестов до кода.
25.5.2. Запуск тестов с помощью интерфейса командной строки¶Running tests using the command-line interface
Модуль test.regrtest может быть запущен как скрипт для управления набором регрессионных тестов Python благодаря опции -m: python -m test.regrtest. Запуск скрипта без аргументов автоматически запускает все регрессионные тесты в пакете test. Это достигается нахождением всех модулей в пакете, имена которых начинаются с test_, их импортированием и выполнением функции test_main(), если она присутствует. Имена тестов для выполнения также могут быть переданы скрипту. Указание одного регрессионного теста (python -m test.regrtest test_spam) минимизирует вывод и будет выводить только то, прошёл тест или нет, тем самым уменьшая объём вывода.
Прямой запуск test.regrtest позволяет задать, какие ресурсы доступны для использования тестами. Для этого используется опция командной строки -u. Выполните python -m test.regrtest -uall, чтобы включить все ресурсы; указание all в качестве значения опции -u включает все возможные ресурсы. Если нужны все ресурсы, кроме одного (более распространённый случай), после all можно указать разделённый запятыми список ресурсов, которые не нужны. Команда python -m test.regrtest -uall,-audio,-largefile запустит test.regrtest со всеми ресурсами, кроме audio и largefile. Чтобы увидеть список всех ресурсов и дополнительные опции командной строки, выполните python -m test.regrtest -h.
Некоторые другие способы выполнения регрессионных тестов зависят от платформы, на которой они выполняются. В Unix можно выполнить make test в корневом каталоге, где была собрана Python. В Windows выполнение rt.bat из каталога PCBuild запустит все регрессионные тесты.
25.6. test.support – Вспомогательные функции для тестов¶test.support – Utility functions for tests
Модуль test.support предоставляет поддержку для регрессионных тестов Python.
В этом модуле определены следующие исключения:
- exception test.support.TestFailed¶
- Исключение, возбуждаемое при сбое теста. Это устарело в пользу тестов на основе unittest и методов утверждений unittest.TestCase.
- exception test.support.TestSkipped¶
- Подкласс TestFailed. Возникает, когда тест пропускается. Это происходит, когда необходимый ресурс (например, сетевое соединение) недоступен на момент тестирования.
- exception test.support.ResourceDenied¶
- Подкласс TestSkipped. Возникает, когда ресурс (например, сетевое соединение) недоступен. Вызывается функцией requires().
Модуль test.support определяет следующие константы:
- test.support.verbose¶
- True, если включён подробный вывод. Следует проверять, когда требуется более детальная информация о выполняющемся тесте. verbose устанавливается test.regrtest.
- 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)¶
- Возвращает путь к файлу с именем filename. Если совпадение не найдено, возвращается filename. Это не считается ошибкой, поскольку это может быть путь к файлу.
- test.support.run_unittest(*classes)¶
Выполняет подклассы unittest.TestCase, переданные в функцию. Функция сканирует классы на наличие методов, начинающихся с префикса test_, и выполняет тесты по отдельности.
Также допускается передача строк в качестве параметров; они должны быть ключами в sys.modules. Каждый соответствующий модуль будет просканирован с помощью unittest.TestLoader.loadTestsFromModule(). Обычно это встречается в следующей функции test_main():
def test_main(): support.run_unittest(__name__)
Это запустит все тесты, определенные в указанном модуле.
- test.support.check_warnings()¶
Удобная обёртка для warnings.catch_warnings(), которая упрощает проверку того, что предупреждение было правильно вызвано с помощью одной проверки. Она примерно эквивалентна вызову warnings.catch_warnings(record=True).
Основное отличие в том, что при входе в менеджер контекста возвращается экземпляр WarningRecorder, а не простой список. Базовый список предупреждений доступен через атрибут warnings объекта-регистратора, а атрибуты последнего вызванного предупреждения также доступны напрямую через этот объект. Если ни одного предупреждения не было вызвано, то все эти атрибуты будут равны None.
На объекте-регистраторе также предоставляется метод reset(). Этот метод просто очищает список предупреждений.
Менеджер контекста используется следующим образом:
with check_warnings() as w: warnings.simplefilter("always") warnings.warn("foo") assert str(w.message) == "foo" warnings.warn("bar") assert str(w.message) == "bar" assert str(w.warnings[0].message) == "foo" assert str(w.warnings[1].message) == "bar" w.reset() assert len(w.warnings) == 0
- test.support.captured_stdout()¶
Это контекстный менеджер, который выполняет тело инструкции with, используя объект StringIO.StringIO в качестве sys.stdout. Этот объект можно получить с помощью предложения as инструкции with.
Пример использования:
with captured_stdout() as s: print("hello") assert s.getvalue() == "hello"
- 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 – это итерируемый объект с именами модулей, которые заменяются на 0 в кеше модулей во время импорта, чтобы гарантировать, что попытки их импорта вызовут ImportError.
Указанный модуль и все модули, перечисленные в параметрах fresh и blocked, сохраняются до начала импорта, а затем повторно вставляются в sys.modules после завершения свежего импорта.
Сообщения об устаревании модулей и пакетов подавляются во время этого импорта, если устарело равно True.
Эта функция вызывает unittest.SkipTest, если указанный модуль не может быть импортирован.
Пример использования:
# Получить копии модуля warnings для тестирования без # влияния на версию, используемую остальной частью тестового набора # Одна копия использует реализацию на C, другая вынуждена использовать # резервную реализацию на чистом Python py_warnings = import_fresh_module('warnings', blocked=['_warnings']) c_warnings = import_fresh_module('warnings', fresh=['_warnings'])
Новое в версии 3.1.
Модуль 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.WarningsRecorder¶
- Класс, используемый для записи предупреждений в модульных тестах. Дополнительные сведения см. в описании check_warnings() выше.