Модуль unittest: тестируем свои программы
Представьте, что вы написали какую-либо программу, а теперь хотите проверить, правильно ли она работает. Что вы для этого сделаете? Скорее всего, вы запустите её несколько раз с различными входными данными, и убедитесь в правильности выдаваемого ответа.
А теперь вы что-то поменяли и снова хотите проверить корректность программы. Запускать ещё несколько раз? А если потом снова что-то поменяется? Нельзя ли как-то автоматизировать это дело?
Оказывается, можно. В Python встроен модуль unittest, который поддерживает автоматизацию тестов, использование общего кода для настройки и завершения тестов, объединение тестов в группы, а также позволяет отделять тесты от фреймворка для вывода информации.
Для автоматизации тестов, unittest поддерживает некоторые важные концепции:
- Испытательный стенд (test fixture) — выполняется подготовка, необходимая для выполнения тестов и все необходимые действия для очистки после выполнения тестов. Это может включать, например, создание временных баз данных или запуск серверного процесса.
- Тестовый случай (test case) — минимальный блок тестирования. Он проверяет ответы для разных наборов данных. Модуль unittest предоставляет базовый класс TestCase, который можно использовать для создания новых тестовых случаев.
- Набор тестов (test suite) — несколько тестовых случаев, наборов тестов или и того и другого. Он используется для объединения тестов, которые должны быть выполнены вместе.
- Исполнитель тестов (test runner) — компонент, который управляет выполнением тестов и предоставляет пользователю результат. Исполнитель может использовать графический или текстовый интерфейс или возвращать специальное значение, которое сообщает о результатах выполнения тестов.
Модуль unittest предоставляет богатый набор инструментов для написания и запуска тестов. Однако достаточно лишь некоторых из них, чтобы удовлетворить потребности большинства пользователей.
Вот короткий скрипт для тестирования трех методов строк:
Тестовый случай создаётся путём наследования от unittest.TestCase. 3 отдельных теста определяются с помощью методов, имя которых начинается на test. Это соглашение говорит исполнителю тестов о том, какие методы являются тестами.
Суть каждого теста — вызов assertEqual() для проверки ожидаемого результата; assertTrue() или assertFalse() для проверки условия; assertRaises() для проверки, что метод порождает исключение. Эти методы используются вместо обычного assert для того, чтобы исполнитель тестов смог взять все результаты и оформить отчёт.
Методы setUp() и tearDown() (которые в данном простом случае не нужны) позволяют определять инструкции, выполняемые перед и после каждого теста, соответственно.
Последние 2 строки показывают простой способ запуска тестов. unittest.main() предоставляет интерфейс командной строки для тестирования программы. Будучи запущенным из командной строки, этот скрипт выводит отчёт, подобный этому:
Интерфейс командной строки
unittest может быть использован из командной строки для запуска модулей с тестами, классов или даже отдельных методов:
Можно также указывать путь к файлу:
С помощью флага -v можно получить более детальный отчёт:
Для нашего примера подробный отчёт будет таким:
-b ( --buffer ) — вывод программы при провале теста будет показан, а не скрыт, как обычно.
-c ( --catch ) — Ctrl+C во время выполнения теста ожидает завершения текущего теста и затем сообщает результаты на данный момент. Второе нажатие Ctrl+C вызывает обычное исключение KeyboardInterrupt.
-f ( --failfast ) — выход после первого же неудачного теста.
--locals (начиная с Python 3.5) — показывать локальные переменные для провалившихся тестов.
Обнаружение тестов
unittest поддерживает простое обнаружение тестов. Для совместимости с обнаружением тестов, все файлы тестов должны быть модулями или пакетами, импортируемыми из директории верхнего уровня проекта (см. подробнее о правилах наименования модулей ).
Обнаружение тестов реализовано в TestLoader.discover(), но может быть использовано из командной строки:
-v ( --verbose ) — подробный вывод.
-s ( --start-directory ) directory_name — директория начала обнаружения тестов (текущая по умолчанию).
-p ( --pattern ) pattern — шаблон названия файлов с тестами (по умолчанию test*.py).
-t ( --top-level-directory ) directory_name — директория верхнего уровня проекта (по умолчанию равна start-directory ).
Организация тестового кода
Базовые блоки тестирования это тестовые случаи — простые случаи, которые должны быть проверены на корректность.
Тестовый случай создаётся путём наследования от unittest.TestCase.
Тестирующий код должен быть самостоятельным, то есть никак не зависеть от других тестов.
Простейший подкласс TestCase может просто реализовывать тестовый метод (метод, начинающийся с test). Вымышленный пример:
Заметьте, что для того, чтобы проверить что-то, мы используем один из assert\*() методов.
Тестов может быть много, и часть кода настройки может повторяться. К счастью, мы можем определить код настройки путём реализации метода setUp(), который будет запускаться перед каждым тестом:
Мы также можем определить метод tearDown(), который будет запускаться после каждого теста:
Можно разместить все тесты в том же файле, что и сама программа (таком как widgets.py), но размещение тестов в отдельном файле (таком как test_widget.py) имеет много преимуществ:
- Модуль с тестом может быть запущен автономно из командной строки.
- Тестовый код может быть легко отделён от программы.
- Меньше искушения изменить тесты для соответствия коду программы без видимой причины.
- Тестовый код должен изменяться гораздо реже, чем программа.
- Протестированный код может быть легче переработан.
- Тесты для модулей на C должны быть в отдельных модулях, так почему же не быть последовательным?
- Если стратегия тестирования изменяется, нет необходимости изменения кода программы.
Пропуск тестов и ожидаемые ошибки
unittest поддерживает пропуск отдельных тестов, а также классов тестов. Вдобавок, поддерживается пометка теста как «не работает, но так и надо».
Пропуск теста осуществляется использованием декоратора skip() или одного из его условных вариантов.
Классы также могут быть пропущены:
Ожидаемые ошибки используют декоратор expectedFailure():
Очень просто сделать свой декоратор. Например, следующий декоратор пропускает тест, если переданный объект не имеет указанного атрибута:
Декораторы, пропускающие тесты или говорящие об ожидаемых ошибках:
@unittest.skip(reason) — пропустить тест. reason описывает причину пропуска.
@unittest.skipIf(condition, reason) — пропустить тест, если condition истинно.
@unittest.skipUnless(condition, reason) — пропустить тест, если condition ложно.
@unittest.expectedFailure — пометить тест как ожидаемая ошибка.
Для пропущенных тестов не запускаются setUp() и tearDown(). Для пропущенных классов не запускаются setUpClass() и tearDownClass(). Для пропущенных модулей не запускаются setUpModule() и tearDownModule().
Различение итераций теста с помощью подтестов
Когда некоторые тесты имеют лишь незначительные отличия, например некоторые параметры, unittest позволяет различать их внутри одного тестового метода, используя менеджер контекста subTest().
Например, следующий тест:
даст следующий отчёт:
Без использования подтестов, выполнение будет остановлено после первой ошибки, и ошибку будет сложнее диагностировать, потому что значение i не будет показано:
Проверки на успешность
Модуль unittest предоставляет множество функций для самых различных проверок:
assertTrue(x) — bool(x) is True
assertFalse(x) — bool(x) is False
assertIsNot(a, b) — a is not b
assertIsNone(x) — x is None
assertIsNotNone(x) — x is not None
assertNotIn(a, b) — a not in b
assertIsInstance(a, b) — isinstance(a, b)
assertNotIsInstance(a, b) — not isinstance(a, b)
assertRaises(exc, fun, *args, **kwds) — fun(*args, **kwds) порождает исключение exc
assertRaisesRegex(exc, r, fun, *args, **kwds) — fun(*args, **kwds) порождает исключение exc и сообщение соответствует регулярному выражению r
assertWarns(warn, fun, *args, **kwds) — fun(*args, **kwds) порождает предупреждение
assertWarnsRegex(warn, r, fun, *args, **kwds) — fun(*args, **kwds) порождает предупреждение и сообщение соответствует регулярному выражению r
assertAlmostEqual(a, b) — round(a-b, 7) == 0
assertNotAlmostEqual(a, b) — round(a-b, 7) != 0
assertNotRegex(s, r) — not r.search(s)
assertCountEqual(a, b) — a и b содержат те же элементы в одинаковых количествах, но порядок не важен
Юнит-тестирование
Модульное тестирование (англ. unit testing ) — процесс в программировании, позволяющий проверить на корректность отдельные модули исходного кода программы. Идея состоит в том, чтобы писать тесты для каждой нетривиальной функции или метода. Это позволяет достаточно быстро проверить, не привело ли очередное изменение кода к регрессии, то есть к появлению ошибок в уже написанных и оттестированных местах программы, а также облегчает обнаружение и устранение таких ошибок.
Содержание
Преимущества
Цель модульного тестирования — изолировать отдельные части программы и показать, что по отдельности эти части работоспособны.
Этот тип тестирования обычно выполняется программистами.
Поощрение изменений
Упрощение интеграции
Документирование кода
Отделение интерфейса от реализации
Ограничения
Как и любая технология тестирования, модульное тестирование не позволяет отловить все ошибки программы. В самом деле, это следует из практической невозможности трассировки всех возможных путей выполнения программы, за исключением простейших случаев. Кроме того, происходит тестирование каждого из модулей по отдельности. Это означает, что ошибки интеграции, системного уровня, функций, исполняемых в нескольких модулях не будут определены. Кроме того, данная технология бесполезна для проведения тестов на производительность. Таким образом, модульное тестирование более эффективно при использовании в сочетании с другими методиками тестирования.
Тестирование программного обеспечения — комбинаторная задача. Например, каждое возможное значение булевской переменной потребует двух тестов: один на вариант TRUE, другой — на вариант FALSE. В результате на каждую строку исходного кода потребуется 3-5 строк тестового кода.
Для получения выгоды от модульного тестирования требуется строго следовать технологии тестирования во всём процессе разработки программного обеспечения. Нужно хранить не только записи обо всех проведённых тестах, но и обо всех изменениях исходного кода во всех модулях. С этой целью следует использовать систему контроля версий ПО. Таким образом, если более поздняя версия ПО не проходит тест, который был успешно пройден ранее, будет несложным сверить исходный код вариантов и устранить ошибку. Также необходимо убедиться в неизменном отслеживании и анализе неудачных тестов. Игнорирование этого требования приведёт к лавинообразному увеличению неудачных тестовых результатов.
Приложения модульного тестирования
Экстремальное программирование
Экстремальное программирование предполагает как один из постулатов использование инструментов автоматического модульного тестирования. Этот инструментарий может быть либо созданным третьей стороной (например, Boost.Test), либо быть создан группой разработчиков данного приложения.
В экстремальном программировании используются модульные тесты для разработки через тестирование. Для этого разработчик до написания кода пишет тесты, отражающие требования к модулю. Очевидно, ни один из этих тестов до написания кода работать не должен. Дальнейший процесс сводится к написанию кратчайшего кода, удовлетворяющего данному набору тестов.
Техника модульного тестирования
Инструментарий
Для большинства популярных языков программирования высокого уровня существуют инструменты и библиотеки модульного тестирования. Некоторые из них:
Юнитест что это



В этой статье вы найдете следующую информацию:
- Что такое модульное (Unit) тестирование?
- Зачем оно нужно?
- Как его провести?
- Методы модульного тестирования
- Разработка через тестирование (TDD)
- Преимущества модульного тестирования
- Недостатки модульного тестирования
- Рекомендации по модульному тестированию
В моделях разработки SDLC, STLC, V Model модульное тестирование – это первый уровень тестирования, выполняемый перед интеграционным тестированием. Модульное тестирование – это метод тестирования WhiteBox, который обычно выполняется разработчиком. На деле же из-за нехватки времени или халатности разработчиков, иногда модульное тестирование приходится проводить QA инженерам.
Зачем нужно модульное тестирование?
Отсутствие модульного тестирования при написании кода значительно увеличивает уровень дефектов при дальнейшем (интеграционном, системном, и приемочном) тестировании. Качественное модульное тестирование на этапе разработки экономит время , а следовательно, в конечном итоге, и деньги.
- Модульные тесты позволяют исправить ошибки на ранних этапах разработки и снизить затраты.
- Это помогает разработчикам лучше понимать кодовую базу проекта и позволяет им быстрее и проще вносить изменения в продукт.
- Хорошие юнит-тесты служат проектной документацией.
- Модульные тесты помогают с миграцией кода. Просто переносите код и тесты в новый проект и изменяете код, пока тесты не запустятся снова.
- Разработчик записывает в приложение единицу кода, чтобы протестировать ее. После: они комментируют и, наконец, удаляют тестовый код при развертывании приложения.
- Разработчик может изолировать единицу кода для более качественного тестирования. Эта практика подразумевает копирование кода в собственную среду тестирования . Изоляция кода помогает выявить ненужные зависимости между тестируемым кодом и другими модулями или пространствами данных в продукте .
- Кодер обычно использует UnitTest Framework для разработки автоматизированных тестовых случаев. Используя инфраструктуру автоматизации, разработчик задает критерии теста для проверки корректного выполнения кода, и в процессе выполнения тестовых случаев регистрирует неудачные. Многие фреймворки автоматически отмечают и сообщают, о неудачных тестах и могут остановить последующее тестирование, опираясь на серьезность сбоя.
- Алгоритм модульного тестирования:
- Создание тестовых случаев
- Просмотр / переработка
- Базовая линия
- Выполнение тестовых случаев.
Ниже перечислены методы покрытия кода:
- Заявление покрытия
- Охват решений
- Охват филиала
- Состояние покрытия
- Покрытие конечного автомата
Пример модульного тестирования: фиктивные объекты
Модульное тестирование основывается на создании фиктивных объектов для тестирования фрагментов кода, которые еще не являются частью законченного приложения. Подставные объекты заполняют недостающие части программы.
Например, у вас может быть функция, которая нуждается в переменных или объектах, которые еще не созданы. В модульном тестировании они будут учитываться в форме фиктивных объектов, созданных исключительно для целей модульного тестирования, выполненного в этом разделе кода.
Разработка через тестирование (TDD)
Модульное тестирование в TDD включает в себя широкое использование платформ тестирования. Каркас модульного тестирования используется для создания автоматизированных модульных тестов. Структуры модульного тестирования не являются уникальными для TDD, но они необходимы для него. Ниже некоторые преимущества TDD: