Вправи
У цій вправі ви використовуєте 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 завжди є списком. Покращимо це, переконавшись, що піднімається виняток із корисного повідомлення про помилку.
Спочатку створіть тест, який фіксує поведінку. Хоча функція ще не оновлюється, спробуйте перший тестовий підхід (також відомий як Test Driven Development або 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().
Усі перевірки мають проходити без помилок під час їх запуску в терміналі.