Тестування на треку Python

Дізнайтеся, як тестувати свої вправи з Python на Exercism.


Ми використовуємо pytest як засіб запуску тестів на сайті. Щоб завантажити й запускати тести вправ треку Python локально, потрібно встановити pytest на власній машині для розробки. Також варто встановити такі плагіни pytest:

Радимо скористатися і програмою для лінтування коду pylint: вона є частиною автоматичного фідбеку на сайті й може стати дуже корисним інструментом статичного аналізу коду. Для зручності плагін pytest-pylint для pytest дає змогу запускати pylint через pytest у командному рядку.

Налаштування pylint може здатися складним, тож стати в пригоді для початку може цей туторіал із pylint.readthedocs.io, а також цей огляд Code Quality: Tools and Best Practices від Real Python.

Встановлення pytest

pytest можна встановити й оновити за допомогою вбудованої утиліти Python pip.

За додатковими порадами звернімося до швидкого посібника Бретта Кеннона про те, як встановлювати пакунки для Python, а також до ґрунтовного пояснення чому варто використовувати python -m pip. Докладніше про аргументи командного рядка Python читайте в розділі command line and environment документації Python.

Примітка: Python3 і py можуть бути, а можуть і не бути псевдонімами Python на компʼютері. Відповідно скоригуйте наведені нижче команди встановлення. Щоб встановити pytest у віртуальному середовищі, переконайтеся, що середовище активоване, перш ніж виконувати команди. Інакше встановлення pytest буде глобальним.

Windows

PS C:\Users\foobar> py -m pip install pytest pytest-cache pytest-subtests pytest-pylint
Successfully installed pytest-8.3.3 ...

Linux / MacOS

$ python3 -m pip install pytest pytest-cache pytest-subtests pytest-pylint
Successfully installed pytest-8.3.3 ...

Щоб перевірити, чи встановлення було успішним:

$ python3 -m pytest --version
pytest 8.3.3

Запуск тестів

Щоб запустити тести, перейдіть у теку, де зберігається вправа, за допомогою cd у терміналі (замініть <exercise-folder-location> нижче на свій шлях).

$ cd <exercise-folder-location>

Note

<exercise-folder-location> або більшість того, що стоїть у кутових дужках, позначає значення-заповнювач. Звичайний шлях або імʼя файлу слід писати без жодних дужок.

Наприклад: /Users/janedoe/exercism/python/exercises/concept/chaitanas-colossal-coaster (у *nix-системах), C:\Users\janedoe\exercism\python\exercises\practice\hello-world\ (у Windows), myFolder або my_file.py.


Файл, який потрібно запустити, зазвичай закінчується на _test.py. Цей файл містить тести для рішення вправи, і це ті самі тести, що виконуються на сайті, коли рішення завантажують. Далі виконайте таку команду в терміналі, замінивши <exercise_test.py> на шлях або назву файлу з тестами:

$ python3 -m pytest -o markers=task <exercise_test.py>
==================== 7 passed in 0.08s ====================

Виправлення попереджень

Якщо не додати pytest -o markers=task до наведеної вище команди, під час запуску тесту з нашим новим синтаксисом можуть зʼявитися warnings про «unknown markers».

Щоб не набирати pytest -o markers=task для кожного запуску тестів, можна скористатися файлом налаштувань pytest.ini, який можна завантажити з кореня теки треку Python: pytest.ini.

Можна також створити власний файл pytest.ini з таким вмістом:

[pytest]
markers =
    task: A concept exercise task.

Якщо покласти цей файл у кореневу або робочу теку вправ Exercism, позначки зареєструються й попередження зникнуть. Більше про позначки pytest можна дізнатися з документації pytest щодо marking test functions і з документації pytest щодо working with custom markers.

Більше про налаштування конфігурацій pytest можна дізнатися з документації pytest щодо configuration file formats

Невдалі тести

Коли тести не проходять, pytest виводить у термінал текст кожного невдалого тесту, а також очікувані та фактичні значення return кожного з них. Нижче наведено загальний приклад невдалого тесту:

$(my_venv) python3 -m pytest -o markers=task <exercise_test.py>

=================== FAILURES ====================
______________ name_of_failed_test ______________
# Test code inside of <exercise_test.py> that failed.
...
E   TypeOfError: ReturnedValue != ExpectedValue

exercise_test.py:<line_of_failed_test>: TypeOfError
============ short test summary info ============
FAILED exercise_test.py::ExerciseTest::name_of_failed_test
========== 1 failed, 2 passed in 0.13s ==========

Додаткові аргументи

Якщо хочеться точно керувати тим, що pytest показує на екрані, ось кілька зручних аргументів командного рядка, за допомогою яких можна налаштувати поведінку pytest.

Показати всі подробиці [-v]

Додавання прапорця -v (verbose) покаже не лише невдалі тести, а й інформацію про середовище та підсумок тестування:

$(my_venv)  python3 -m pytest -o markers=task -v exercises/<exercise_name>/<test_file_test.py>

======================================== test session starts ===========================================
platform darwin -- Python 3.9.0, pytest-6.2.5, -- /usr/local/envs/my_env/bin/python3
cachedir: .pytest_cache
metadata: {'Python': '3.9.0', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '6.2.1'}, 'Plugins': {'subtests': '0.5.0', 'pylint': '0.18.0'}
rootdir: /Users/<user>/exercism/python, configfile: pytest.ini
plugins: subtests-0.5.0, pylint-0.18.0

collected 5 items

exercises/exercise-name/exercise_file_test.py::ExerciseNameTest::test_one FAILED                          [ 20%]
exercises/exercise-name/exercise_file_test.py::ExerciseNameTest::test_two FAILED
exercises/exercise-name/exercise_file_test.py::ExerciseNameTest::test_three PASSED                        [ 40%]
exercises/concept/exercise-name/exercise_file_test.py::ExerciseNameTest::test_four FAILED
exercises/concept/exercise-name/exercise_file_test.py::ExerciseNameTest::test_five PASSED                 [ 60%]
exercises/concept/exercise-name/exercise_file_test.py::ExerciseNameTest::test_six FAILED
exercises/concept/exercise-name/exercise_file_test.py::ExerciseNameTest::test_seven PASSED                [ 80%]
exercises/concept/exercise-name/exercise_file_test.py::ExerciseNameTest::test_eight FAILED
exercises/concept/exercise-name/exercise_file_test.py::ExerciseNameTest::test_nine PASSED                 [100%]

================================================ FAILURES ======================================================
# Failed tests are then individually printed out below

.......

Зупинитися після першої невдачі [-x]

Прапорець -x запускає тести як звичайно, але зупиняє запуск після першої невдачі. Це допомагає, коли потрібно налагоджувати по одній задачі чи невдалому тесту за раз:

$(my_venv) python3 -m pytest -o markers=task -x exercises/<exercise_name>/<test_file_test.py>

=================== FAILURES ====================
_______________ example_test_foo ________________
...
...
============ short test summary info ============
FAILED example_test.py::ExampleTest::example_test_foo
!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!
========== 1 failed, 5 passed in 0.28s ==========

Спочатку невдалі тести [--ff]

Плагін pytest-cache запамʼятовує, які тести не пройшли під час останнього запуску pytest, тож прапорець --ff змусить pytest запустити раніше невдалі тести першими, а потім перейти до решти. Це може пришвидшити тестування, якщо робити багато дрібних виправлень навколо однієї задачі чи набору вхідних даних.

$(my_venv) python3 -m pytest -o markers=task --ff <example_file_test.py>
==================== 7 passed in 503s ====================

Рекомендований порядок роботи

Радимо використовувати такі команди, щоб налагодження було простішим і (можливо) швидшим:

Спершу перейдіть у теку вправи, яку хочете протестувати:

$(my_venv) cd path/to/exercise

Потім запустіть тести разом із поясненими вище аргументами -x та --ff:

$(my_venv) python3 -m pytest -o markers=task -x --ff <example_file_test.py>

Це запустить тести для рішення. Коли pytest натрапляє на невдалий тест, програма зупиняється й повідомляє, який тест не пройшов. Коли ми вносимо виправлення й запускаємо тести знову, pytest спершу виконає попередній невдалий тест, а потім перейде до решти.

Використання PDB, налагоджувача Python, разом із pytest

Якщо хочеться «налагоджувати як професіонал», додайте аргумент --pdb після команди pytest і потрапите у вбудований налагоджувач Python, PDB.

$(my_venv) python3 -m pytest -o markers=task -x --ff --pdb <example_file_test.py>
=============== 4 passed in 0.15s ===============

Коли тест не проходить, потрапляння в PDB дає змогу покроково виконувати код, бачити поточну область видимості, а також перевіряти значення змінних і сигнатури різних функцій. Більше про модуль PDB можна дізнатися з документації Python щодо PDB. Крім того, надзвичайно корисними будуть документація pytest щодо PDB і цей посібник від Real Python.

Розширення IDE

Якщо хочеться розширити IDE інструментами, які допоможуть тестувати й покращувати код, зазирніть на сторінку інструментів. Там ми розглядаємо різні IDE, редактори та корисні розширення для лінтування й налагодження.

Додаткова інформація

Додавання python до PATH

Примітка: Якщо Python на Windows встановлено через PSF Installer, команда буде py, а не python3.

Набирати python3 -m щоразу, коли потрібно запустити модуль, може трохи дратувати. Щоб цього уникнути, можна додати теку Scripts зі встановленого Python до свого PATH. Якщо невідомо, куди встановлено Python, виконайте таку команду в терміналі:

$ python3 -c "import os, sys; print(os.path.dirname(sys.executable))"
<python_directory>

Повернена тека вказує, куди встановлено поточну активну версію Python; у цьому розділі вона позначена як <python_directory>.

Windows

Натисніть кнопку Windows Start, знайдіть Edit the system environment variables і натисніть Enter. Далі натисніть Environment Variables...:

Натисніть синю кнопку, лол

Знайдіть змінну Path у розділі User variables, виділіть її й натисніть Edit...:

Вибір змінної path

Потім додайте новий рядок, як показано на малюнку, замінивши <python_directory> на теку встановлення Python:

Додати python до path

MacOS/Linux

Наведене нижче має працювати для більшості варіантів Linux і MacOS з оболонкою bash. Команди можуть відрізнятися залежно від дистрибутива Linux і від того, яку оболонку використано, fish чи zsh. Замініть <python_directory> на результат виконання python3 -c "import os, sys; print(os.path.dirname(sys.executable))"

export PATH="$PATH:<python_directory>"