Вправи

Завершено

У цій вправі ви використовуєте Pytest для перевірки функції. Потім ви знайдете та виправите деякі потенційні проблеми з функцією, яка спричиняє невдалі перевірки. Аналіз помилок і використання багатого звітування про помилки Pytest має важливе значення для виявлення та виправлення проблемних тестів або помилок у коді виробництва.

Для цієї вправи використовується функція, яка називається admin_command(), яка приймає системну команду як ввід, і за потреби префіксує її за допомогою засобу sudo. Функція містить помилку, яку ви виявляєте, записуючи тести.

Крок 1. Додавання файлу з тестами для цієї вправи

  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, вона повертає таку саму команду, що й ввід.

  2. У цьому ж файлі додайте тести для функції 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 завжди.

  1. Оновіть функцію 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
    
  2. Повторіть перевірку за допомогою Pytest. Спробуйте збільшити рівень словесного наповнення виводу, використовуючи позначку -v з Pytest.

    $ pytest -v test_exercise.py
    
  3. Тепер перевірте результати. Тепер має відобразитися два тести, що проходять:

    ============================= 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 завжди є списком. Покращимо це, переконавшись, що піднімається виняток із корисного повідомлення про помилку.

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

  3. Оновіть функцію, щоб піднести 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
    
  4. Нарешті, оновіть метод 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().

Усі перевірки мають проходити без помилок під час їх запуску в терміналі.