Упражнения
В этом упражнении вы используете Pytest для тестирования функции. Затем вы найдете и исправите некоторые потенциальные проблемы с функцией, которая приводит к сбою тестов. Просмотр сбоев и использование полнофункциональные отчеты об ошибках Pytest важны для выявления и устранения проблемных тестов или ошибок в рабочем коде.
В этом упражнении мы используем функцию, которая admin_command()
принимает системную команду в качестве входных данных и при необходимости префиксирует ее с sudo
помощью средства. Функция содержит ошибку, обнаруженную путем написания тестов.
Шаг 1. Добавление файла с тестами для этого упражнения
Используя соглашения об именовании Python для тестовых файлов, создайте новый тестовый файл. Присвойте тестовому файлу имя test_exercise.py и добавьте следующий код:
def admin_command(command, sudo=True): """ Prefix a command with `sudo` unless it is explicitly not needed. Expects `command` to be a list. """ if sudo: ["sudo"] + command return command
Функция
admin_command()
принимает список в качестве входных данных с помощью аргументаcommand
, и при необходимости может добавлять списку префиксsudo
. Если для аргумента ключевого словаsudo
задано значениеFalse
, тогда возвращается та же команда, которая задана во входных данных.В том же файле добавьте тесты для
admin_command()
функции. Тесты используют вспомогательный метод, который возвращает пример команды:class TestAdminCommand: def command(self): return ["ps", "aux"] def test_no_sudo(self): result = admin_command(self.command(), sudo=False) assert result == self.command() def test_sudo(self): result = admin_command(self.command(), sudo=True) expected = ["sudo"] + self.command() assert result == expected
Примечание.
Как правило, тесты не находятся в том же файле, что и фактический код. Для простоты понимания примеры в этом упражнении будут содержать фактический код в одном файле. В реальных проектах Python тесты обычно отделяются файлами и каталогами от кода, который они тестируют.
Шаг 2. Выполнение тестов и обнаружение ошибок
Теперь, когда тестовый файл содержит функцию для тестирования и несколько тестов для проверки ее поведения, пора выполнить тесты и поработать со сбоями.
Выполните файл с помощью Python:
$ pytest test_exercise.py
В результате один тест должен пройти успешно, а другой — завершиться с ошибкой. Выходные данные теста с ошибкой должны выглядеть так:
=================================== FAILURES =================================== __________________________ TestAdminCommand.test_sudo __________________________ self = <test_exercise.TestAdminCommand object at 0x10634c2e0> def test_sudo(self): result = admin_command(self.command(), sudo=True) expected = ["sudo"] + self.command() > assert result == expected E AssertionError: assert ['ps', 'aux'] == ['sudo', 'ps', 'aux'] E At index 0 diff: 'ps' != 'sudo' E Right contains one more item: 'aux' E Use -v to get the full diff test_exercise.py:24: AssertionError =========================== short test summary info ============================ FAILED test_exercise.py::TestAdminCommand::test_sudo - AssertionError: assert... ========================= 1 failed, 1 passed in 0.04s ==========================
Выходные данные завершаются сбоем в тесте
test_sudo()
. Pytest предоставляет подробные сведения о двух списках, которые сравниваются. В этом случае переменнаяresult
не содержит командуsudo
, которая ожидается в рамках теста.
Шаг 3. Исправление ошибки и успешное выполнение теста
Прежде чем вносить какие-либо изменения, необходимо понять, почему в первую очередь произошел сбой. Хотя вы видите, что ожидание не выполняется (sudo
не в результате), вы должны выяснить, почему.
Просмотрите следующие строки кода из admin_command()
функции sudo=True
при выполнении условия:
if sudo:
["sudo"] + command
Операция списков не используется для возврата значения. Так как оно не возвращается, функция всегда возвращает команду без sudo
.
Обновите функцию
admin_command()
для возврата операции списка, чтобы измененный результат использовался при запросе командыsudo
. Обновленная функция должна выглядеть так:def admin_command(command, sudo=True): """ Prefix a command with `sudo` unless it is explicitly not needed. Expects `command` to be a list. """ if sudo: return ["sudo"] + command return command
Повторно запустите тест с помощью Pytest. Попробуйте увеличить детализацию выходных данных с помощью флага
-v
Pytest:$ pytest -v test_exercise.py
Теперь проверьте выходные данные. В них должно быть указано, что пройдены два теста:
============================= test session starts ============================== Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 cachedir: .pytest_cache rootdir: /private collected 2 items test_exercise.py::TestAdminCommand::test_no_sudo PASSED [ 50%] test_exercise.py::TestAdminCommand::test_sudo PASSED [100%] ============================== 2 passed in 0.00s ===============================
Примечание.
Так как функция теперь может принимать больше значений с разным регистром, для охвата этих вариантов следует добавить тесты. Это позволит предотвратить будущие изменения функции, вызывая другое (неожиданное) поведение.
Шаг 4. Добавление нового кода с тестами
После добавления тестов на предыдущих шагах вы должны чувствовать себя комфортно, внося дополнительные изменения в функцию и проверяя их с помощью тестов. Даже если изменения не охватываются существующими тестами, вы можете быть уверенны, что не нарушаете предыдущие предположения.
В этом случае функция admin_command()
слепо доверяет тому, что аргументcommand
всегда является списком. Давайте исправим это, обеспечив возникновение исключения с полезным сообщением об ошибке.
Сначала создайте тест, который фиксирует поведение. Хотя функция еще не обновлена, попробуйте тест-первый подход (также известный как разработка на основе тестов или TDD).
- Обновите файл test_exercise.py, чтобы он импортировал
pytest
вверху. Этот тест использует внутренний вспомогательный элемент из платформыpytest
:
import pytest
- Теперь добавьте новый тест в класс, чтобы проверить исключение. Этот тест должен ожидать
TypeError
от функции, если значение, переданное в него, не является списком:
def test_non_list_commands(self): with pytest.raises(TypeError): admin_command("some command", sudo=True)
- Обновите файл test_exercise.py, чтобы он импортировал
Запустите тесты еще раз с помощью Pytest, они должны соответствовать:
============================= test session starts ============================== Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /private/ collected 3 items test_exercise.py ... [100%] ============================== 3 passed in 0.00s ===============================
Тест подходит для того, чтобы проверить наличие
TypeError
, но было бы хорошо добавить код с полезным сообщением об ошибке.Обновите функцию, чтобы явно вызвать
TypeError
с помощью полезного сообщения об ошибке:def admin_command(command, sudo=True): """ Prefix a command with `sudo` unless it is explicitly not needed. Expects `command` to be a list. """ if not isinstance(command, list): raise TypeError(f"was expecting command to be a list, but got a {type(command)}") if sudo: return ["sudo"] + command return command
Наконец, обновите метод
test_non_list_commands()
, чтобы проверить наличие сообщения об ошибке:def test_non_list_commands(self): with pytest.raises(TypeError) as error: admin_command("some command", sudo=True) assert error.value.args[0] == "was expecting command to be a list, but got a <class 'str'>"
Обновленный тест использует
error
в качестве переменной, содержащей все сведения об исключении. Используяerror.value.args
, вы можете изучить аргументы исключения. В этом случае первый аргумент содержит строку ошибки, которую может проверить тест.
Проверьте свою работу
На этом этапе должен быть тестовый файл Python с именем test_exercise.py , который включает в себя:
- Функцией
admin_command()
, принимающей аргумент и аргумент ключевого слова. - Исключением
TypeError
с полезным сообщением об ошибке в функцииadmin_command()
. - Тестовый класс
TestAdminCommand()
с вспомогательным методомcommand()
и тремя методами тестирования, которые проверяют функциюadmin_command()
.
Все тесты должны передаваться без ошибок при их запуске в терминале.