Упражнения

Завершено

В этом упражнении вы используете 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. Сначала создайте тест, который фиксирует поведение. Хотя функция еще не обновлена, попробуйте тест-первый подход (также известный как разработка на основе тестов или 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().

Все тесты должны передаваться без ошибок при их запуске в терминале.