Тестирование
- Место тестирования в жизненном цикле программного продукта
- Собирается — значит, работает!
- + Проверяется анализатором кода, и потыкано end-user тестерами согласно тест-плану
- + Автоматическая проверка работоспособности там, где это возможно
- + Измерение тестового покрытия (coverage)
- Ручное, автоматизированное, автоматическое
- unit — спецификация компонентов (функций/классов и т. п.)
integration — спецификация интерфейсов между компонентами
- system — спецификация конечного продукта
- acceptacne — потребительские/рыночные/эксплуатационные/… свойства продукта
- Тест регрессий
- Покрытие
- Функции / классы
- Строчки кода
- Различные execution path (?)
- Различные execution path с критически различными наборами данных (??)
- Test-driven development:
- Сначала весь код, потом некоторые тесты (когда это можно?)
- Каждая новая фича сопровождается тестом
Разработка_через_тестирование (TDD)
- сначала пишется тест и заглушка
сам код падает (иначе бесполезен)
- под тест пишется код
- код не падает
код изменяется и всё равно не падает
- green test trap: Тестирование может доказать наличие дефектов, но не их отсутствие
- red test trap: Не всякие проваленные тесты означают дефекты. Могут означать пробел в требованиях, в том числе нефункциональных
- Полезные ≠ друг другу термины:
ошибка программиста при написании программы может привести к
дефекту (багу) в программе, который в свою очередь может
проявиться (или не проявиться) в виде программного сбоя
- Стоимость исправления дефекта возрастает пропорционально его «возрасту»
- Непрерывная интеграция
Модульное тестирование в Python
Doctest
doctest: тест = диалог с python-интерпретатором
Пример использования doctest
Поэтому только кратко:
- Модуль
- Тестируем вручную из командной строки
- Добавляем диалог as is в docstring:
Тестирование: python3 -m doctest Moo.py
Отчёт (с успешными тестами): python3 -m doctest -v Moo.py
- Тестирование исключений
- Вообще говоря, важны только три строчки (сама команда, первая строка и сообщение с исключением), остальные можно выкинуть
Перенос тестов во внешний текстовый файл,
например, в .rst
- этот файл отлично включается в Sphinx-документацию
Запуск python3 exttest.py -v:
«Серьёзные» фреймворки
- Fixture
- Подготовка компонента к тесту: не все функции можно оттестировать сходу, иногда надо
сначала что-то создать, открыть, запустить, … (set-up)
- провести тест
удалить, закрыть, остановить, … это что-то (tear-down) Такое одно что-то называет fixture
- Case
Что именно тестируем. Подготавливаем окружение (fixtur-ы), что-то дёргаем, смотрим, подходит ли результат
- SubCase
- Множественное тестирование (например, тестирование в цикле обреботки нескольких наборов параметров)
- Suite
- Набор cases (на разные темы, разных больших частей, разных уровней и т. п.)
- Runner
- Запускалка тестов, обработчик отчётов и т. п.
Модуль unittest
Принципы unittest:
Тестирующая функция вызывает проверочный метод assertЧтоТоТам(какие-то, параметры) или проверяет в контекстном менеджере, что выпале нужное исключение / warning
- Case — это класс, в нём несколько атомарных тестирующих функций
- Suite — это объект, в который можно добавлять Case и другие Suite
- Runner — заводится автоматом, но можно задать вручную с выбором Suite
Fixture — два метода в Case (.SetUp() и .TearDown())
Возможности:
- Сбор тестов (discovery)
- Пропуск тестов по условию
- Ожидаемый сбой
- Подтесты
- Обработка сигналов
Примеры использования
Квазиобъекты (mock)
TODO unittest.mock
Тестовое покрытие
Модуль coverage
- поддержка unittest, pytest (и слегка старого уже nose)
- выборочное покрытие
- маркировка ветвлений
- подпроцессы
- журналирование контекста
Пример
В репозитории примеров к лекции
- Набор тестов
- Исключение того, что тестировать не надо
Модуль pytest
Сравнение с unittest:
Test discover «из коробки» — поиск тестов по имени файла/функции/класаа (есть в unittest)
Обычный assert для теста (вместо многих функций)
- Атомарные фикстуры (перенаправление в/в, временные изменения классов и т. п.)
- Множество дополнений
Пример использования pytest: pudb
PuDB — отладчик для питона
Соберём под него окружение:
1 [george@inspiron src]$ python3 -m venv init demo-pudb
2 [george@inspiron src]$ cd demo-pudb
3 [george@inspiron demo-pudb]$ . ./bin/activate
4 (demo-pudb) [george@inspiron demo-pudb]$ git clone https://github.com/inducer/pudb.git
5 Cloning into 'pudb'...
6 cd pudb
7 (demo-pudb) [george@inspiron pudb]$ ls
8 debug_me.py example-theme.py pudb setup.py
9 doc LICENSE README.rst test
10 example-shell.py MANIFEST.in requirements.dev.txt try-the-debugger.sh
11 example-stringifier.py manual-tests setup.cfg upload_coverage.sh
12 (demo-pudb) [george@inspiron pudb]$
13
Cоберём модуль:
1 (demo-pudb) [george@inspiron pudb]$ pip install -r requirements.dev.txt
2 Collecting codecov==2.0.5 (from -r requirements.dev.txt (line 1))
3 . . .
4 Successfully installed Pygments-2.2.0 argparse-1.4.0 . . .
5 (demo-pudb) [george@inspiron pudb]$ python setup.py build
6 running build
7 . . .
8 copying pudb/ui_tools.py -> build/lib/pudb
9 (demo-pudb) [george@inspiron pudb]$
10
Все нужные зависимости указаны в файла requirements.dev.txt
Собранный модуль в чистом виде лежит в build/lib/pudb
Для запуска тестов надо сделать так, чтобы build/lib попал в PYTHONPATH, тогда модуль будет экспортирован оттуда
1 (demo-pudb) [george@inspiron pudb]$ export PYTHONPATH=`pwd`/build/lib
2 (demo-pudb) [george@inspiron pudb]$ pytest
3 ========================= test session starts =========================
4 platform linux -- Python 3.8.2, pytest-3.0.7, py-1.4.33, pluggy-0.4.0
5 rootdir: /home/george/src/demo-pudb/pudb, inifile:
6 plugins: mock-1.10.0, cov-2.4.0
7 collected 16 items
8
9 test/test_lowlevel.py ....
10 test/test_make_canvas.py .....
11 test/test_settings.py ..
12 test/test_source_code_providers.py ....
13 test/test_var_view.py .
14
15 ====================== 16 passed in 0.22 seconds ======================
16
pytest сам нашёл, где лежат тесты
Можно сказать pytest -v для отчёта по каждому тесту в файле
В этом проекте используется два дополнения к pytest:
- вместо полноценных фикстур — т. н. моккеры (mock):
1 (demo-pudb) [george@inspiron pudb]$ pytest --fixtures-per-test 2 =========================== test session starts ============================ 3 platform linux -- Python 3.8.2, pytest-3.0.7, py-1.4.33, pluggy-0.4.0 4 rootdir: /home/george/src/demo-pudb/pudb, inifile: 5 plugins: mock-1.10.0, cov-2.4.0 6 collected 16 items 7 8 ------------------ fixtures used by test_load_breakpoints ------------------ 9 ------------------------ (test/test_settings.py:10) ------------------------ 10 mocker 11 return an object that has the same interface to the `mock` module, but 12 takes care of automatically undoing all patches after each test method. 13 pytestconfig 14 the pytest config object with access to command line opts. 15 . . . 16
Замер покрытия кода тестами cov
1 (demo-pudb) [george@inspiron pudb]$ pytest --cov=build/lib/pudb 2 =========================== test session starts ======================= 3 platform linux -- Python 3.8.2, pytest-3.0.7, py-1.4.33, pluggy-0.4.0 4 rootdir: /home/george/src/demo-pudb/pudb, inifile: 5 plugins: mock-1.10.0, cov-2.4.0 6 collected 16 items 7 8 test/test_lowlevel.py .... 9 test/test_make_canvas.py ..... 10 test/test_settings.py .. 11 test/test_source_code_providers.py .... 12 test/test_var_view.py . 13 14 ----------- coverage: platform linux, python 3.8.2-final-0 ----------- 15 Name Stmts Miss Branch BrPart Cover 16 ----------------------------------------------------------------- 17 build/lib/pudb/__init__.py 194 159 26 4 18% 18 build/lib/pudb/__main__.py 3 3 0 0 0% 19 build/lib/pudb/b.py 14 14 2 0 0% 20 build/lib/pudb/debugger.py 1386 1276 223 2 7% 21 build/lib/pudb/ipython.py 31 31 8 0 0% 22 build/lib/pudb/lowlevel.py 134 61 56 7 51% 23 build/lib/pudb/py3compat.py 23 10 2 1 56% 24 build/lib/pudb/remote.py 120 120 12 0 0% 25 build/lib/pudb/run.py 27 27 0 0 0% 26 build/lib/pudb/settings.py 378 302 88 13 22% 27 build/lib/pudb/shell.py 137 137 12 0 0% 28 build/lib/pudb/source_view.py 230 167 36 7 26% 29 build/lib/pudb/theme.py 595 595 2 0 0% 30 build/lib/pudb/ui_tools.py 222 159 66 0 25% 31 build/lib/pudb/var_view.py 396 324 92 6 18% 32 ----------------------------------------------------------------- 33 TOTAL 3890 3385 625 40 13% 34 ======================== 16 passed in 0.55 seconds =================== 35
Без указания --cov=build/lib/pudb ключ --cov посчитает покрытие всего запускаемого кода на python (включая все системные библиотеки:)
Вместо дополнения к pytest можно использовать отдельный модуль coverage
Д/З
- Осознать, что нуждается в unit-тестировании
- Оснастить код семестрового проекта unit-тестами (любой фреймворк)
- Зафиксировать в документации, как их запускать
- Подумать над тестированием UI
например, с помощью порождения событий tkinter