Size: 17358
Comment:
|
Size: 14638
Comment:
|
Deletions are marked like this. | Additions are marked like this. |
Line 39: | Line 39: |
'''TODO''' исправить под использование `pipenv` | |
Line 42: | Line 41: |
Пример использования doctest * [[LecturesCMC/PythonDevelopment2020/09_Testing#Doctest|В лекциях прошлого года]] * [[https://git.sr.ht/~frbrgeorge/MooTest/log|В репозитории с примерами к этой лекции]] Поэтому только кратко: |
|
Line 43: | Line 46: |
{{{#!python def moo(oos=2, end=""): '''Издать мычание длиной oos с end в конце''' return "M"+"o"*oos+end }}} 1. Тестируем вручную: {{{#!highlight pycon $ python3 -i Moo.py >>> moo() 'Moo' >>> moo(4) 'Moooo' >>> moo(0) 'M' >>> moo(end='!') 'Moo!' >>> moo(0,'?') 'M?' }}} 1. Добавляем тесты в docstring: {{{#!pytnon def moo(oos=2, end=""): '''Издать мычание длиной oos с end в конце Оба параметра необязательны: >>> moo() 'Moo' Первый задаёт количество букв 'o' в слове 'Moo' >>> moo(4) 'Mooooo' Букв 'o' может и не быть >>> moo(0) 'M' Второй задаёт символ после всех 'o' (по умолчанию — ничего) >>> moo(end='!') 'Moo!' >>> moo(0,'?') 'M?' ''' return "M"+"o"*oos+end }}} 1. Тестирование: {{{#!highlight console $ python3 -m doctest Moo.py ********************************************************************** File "/home/george/src/moo/Moo.py", line 13, in Moo.moo Failed example: moo(4) Expected: 'Mooooo' Got: 'Moooo' ********************************************************************** 1 items had failures: 1 of 5 in Moo.moo ***Test Failed*** 1 failures. }}} 1. Отчёт (с успешными тестами): {{{#!highlight console $ python3 -m doctest -v Moo.py Trying: moo() Expecting: 'Moo' ok Trying: moo(4) Expecting: 'Mooooo' ********************************************************************** File "/home/george/src/moo/Moo.py", line 13, in Moo.moo Failed example: moo(4) Expected: 'Mooooo' Got: 'Moooo' Trying: moo(0) Expecting: 'M' ok Trying: moo(end='!') Expecting: 'Moo!' ok Trying: moo(0,'?') Expecting: 'M?' ok 1 items had no tests: Moo ********************************************************************** 1 items had failures: 1 of 5 in Moo.moo 5 tests in 2 items. 4 passed and 1 failed. ***Test Failed*** 1 failures. }}} Тестирование исключений: {{{#!highlight pycon $ python -im Moo >>> moo("QQ") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/george/src/tests/Moo.py", line 33, in moo return "M"+"o"*moos+end TypeError: can't multiply sequence by non-int of type 'str' >>> }}} Как обычно, добавим ''просто весь вывод''! {{{#!python def moo(oos=2, end=""): '''Издать мычание длиной oos с end в конце ... Здесь должно быть исключение: >>> moo("QQ") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/george/src/tests/Moo.py", line 33, in moo return "M"+"o"*moos+end TypeError: can't multiply sequence by non-int of type 'str' ''' }}} Вообще говоря, важны только три строчки, остальные можно выкинуть {{{ ... Здесь должно быть исключение: >>> moo("QQ") Traceback (most recent call last): TypeError: can't multiply sequence by non-int of type 'str' ... }}} Тесты должны пройти! [[py3doc:doctest#simple-usage-checking-examples-in-a-text-file|Перенос тестов во внешний файл]] Пишем файл ( <!> можно в `.rst`, для Sphinx), например, `exttest.rst`: {{{#!highlight rst External test ============= Using Moo --------- Start from importing `Moo` module: >>> import Moo Then call `moo`: >>> Moo.moo(5) 'Mooooo' }}} К нему запускалку тестов: {{{#!highlight python import doctest doctest.testfile("exttest.rst") }}} И запускаем её (ключи как у модуля `pytest`): {{{#!console $ python3 exttest.py -v Trying: import Moo Expecting nothing ok Trying: Moo.moo(5) Expecting: 'Mooooo' ok 1 items passed all tests: 2 tests in exttest.rst 2 tests in 1 items. 2 passed and 0 failed. Test passed. }}} |
1. Тестируем вручную из командной строки 1. Добавляем диалог as is в docstring: 1. Тестирование: `python3 -m doctest Moo.py` 1. Отчёт (с успешными тестами): `python3 -m doctest -v Moo.py` 1. Тестирование исключений * Вообще говоря, важны только три строчки (сама команда, первая строка и сообщение с исключением), остальные можно выкинуть 1. [[py3doc:doctest#simple-usage-checking-examples-in-a-text-file|Перенос тестов во внешний текстовый файл]], * например, в `.rst` * этот файл отлично включается в Sphinx-документацию * Запуск `python3 exttest.py -v`: {{{#!highlight python import doctest doctest.testfile("exttest.rst") }}} |
Line 245: | Line 72: |
'''TODO''' [[py3doc:unittest]] * [[https://tirinox.ru/unit-test-python/|методичка]] |
Примеры использования [[py3doc:unittest]]: * [[https://git.sr.ht/~frbrgeorge/MooTest/log|В репозитории примеров к лекции]] * [[https://tirinox.ru/unit-test-python/|В методичке]] |
Line 260: | Line 87: |
'''TODO''' |
|
Line 272: | Line 101: |
'''TODO''' Покрытие |
Пример [[https://git.sr.ht/~frbrgeorge/MooTest/log|В репозитории примеров к лекции]] * Набор тестов * Исключение того, что тестировать не надо |
Тестирование
- Место тестирования в жизненном цикле программного продукта
- Собирается — значит, работает!
- + Проверяется анализатором кода, и потыкано 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)
- Пропуск тестов по условию
- Ожидаемый сбой
- Подтесты
- Обработка сигналов
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