Рубрики
Без рубрики

Введение в Pytest и проведение тестовых разработок с Repl.it

В этом уроке мы представим разработку, основанную на тестировании, и вы увидите, как использовать Pytest, чтобы гарантировать t … Tagged with Pytest, Python, Repit.

В этом уроке мы представим разработку, основанную на тестировании, и вы увидите, как использовать pytest Чтобы убедиться, что ваш код работает, как и ожидалось.

pytest Позволяет указать входы и ожидаемые выходы для ваших функций. Он запускает каждый вход через вашу функцию и подтверждает, что выход верен. pytest это библиотека Python и работает так же, как и любая другая библиотека Python: вы устанавливаете ее через диспетчер пакетов, и вы можете импортировать его в свой код Python. Тесты также записаны в Python, поэтому у вас будет тестирование кода другого кода.

Тестовая разработка или TDD-это практика написания тестов до Вы пишете код. Вы можете прочитать больше о TDD и почему он популярен на Википедия Анкет

В частности, вы:

  • Посмотрите, как структурировать свой проект, чтобы сохранить ваши тесты отдельными, но все же попросите их обратиться к вашим основным файлам кода
  • Выясните требования к функции, которая может разделить полное имя на компоненты имени и фамилии
  • Напишите тесты для этой функции
  • Напишите фактическую функцию.

Создание структуры проекта для Pytest

Для крупных проектов полезно сохранить ваш код тестирования отдельно от кода приложения. Чтобы это работало, вам понадобится ваши файлы, настроенные в определенных местах, и вам нужно будет создать отдельный Python модули так что вы можете легко обратиться к разным частям проекта.

Создайте новую Python Spurp под названием namesplitter Анкет Как всегда, у него уже будет main.py Файл, но мы собираемся поместить нашу функцию разделения имени в другой модуль под названием utils , который может разместить любой вспомогательный код, на который опирается наше основное приложение. Мы также хотим специального места для наших тестов.

Создайте две новые папки: одна называется utils и один называется Тесты , используя добавить папку кнопка. Обратите внимание, что когда вы нажимаете эту кнопку, она по умолчанию создает папку в вашей в настоящее время активной папке, поэтому выберите main.py Файл после создания первой папки или второй папки будет создан внутри первой папки.

Вы хотите, чтобы обе папки находились на корне вашего проекта.

Теперь добавьте файл на корневом уровне проекта под названием __init__.py Анкет Это специальный файл, который указывает на Python, который мы хотим, чтобы наш проект рассматривался как «модуль»: то, к чему другие файлы могут ссылаться по имени и импорту. Также добавьте __init__.py Файл внутри utils Папка и Тесты папка. Эти файлы останутся пустыми, но важно, чтобы они существовали для наших тестов. Их присутствие указывает, что наш основной проект должен рассматриваться как модуль и что любой код в нашем утилит и Тесты Папки следует рассматривать как подмодули основного.

Наконец, создайте файлы, где мы на самом деле напишем код. Внутри utils Папка Создайте файл с именем name_helper.py и внутри Тесты папка создать один называемый test_name_helper.py Анкет Ваш проект теперь должен выглядеть следующим образом. Убедитесь, что у вас есть все файлы и папки с именно этими именами, в правильных местах.

Определение примеров функции разделения имени

Разделение имен полезно во многих контекстах. Например, это является общим требованием, когда пользователи регистрируются на веб -сайтах со своими полными именами, а затем компании хотят отправлять персонализированные электронные письма, адресованные пользователям только по имени. Вы можете подумать, что это так же просто, как разделение имени на основе пространств, как в следующем примере.

def split_name(name):
    first_name, last_name = name.split()
    return [first_name, last_name]

print(split_name("John Smith"))
# >>> ["John", "Smith"]

Хотя это действительно работает во многих случаях, имена на удивление сложны, и очень часто совершает ошибки, имея дело с ними как программистами, как обсуждалось в Эта классическая статья Анкет Это был бы огромный проект, чтобы попытаться справиться с любой Имя, но давайте представим, что у вас есть требования к рассмотрению следующих видов имен:

  • Первый последний, например, Джон Смит
  • Первая средняя последняя, например, Джон Патрик Смит (Джон Патрик взят как имя)
  • Первая средняя средняя последняя, например, Джон Патрик Томсон Смит (Джон Патрик Томсон принял как имя)
  • Первый последний последний, например, Йохан Ван дер Берг (обратите внимание на строчные буквы, Йохан взят как имя, остальное как последнее)
  • Первая средняя последняя последняя, например, Йохан Патрик Ван дер Берг (обратите внимание на строчные буквы, Йохан взят как имя, остальное как последнее)
  • Последнее, например, Смит (мы можем предположить, что если нам дано только одно имя, это фамилия)

В частности, вы можете предположить, что, как только вы найдете имя, начинающееся с строчной буквы, оно означает начало фамилии, и что все другие имена, начинающиеся с заглавной буквы, являются частью первых и второстепенных имен. Отличные имена могут быть объединены с именами.

Конечно, это не охватывает все возможности, но это хорошая отправная точка с точки зрения требований.

Используя TDD, мы всегда пишем Неудачные тесты первый. Идея в том, что мы должны написать тест о том, как какой -то код должен Ведите себя, проверьте, чтобы убедиться, что он ломается так, как мы ожидаем (поскольку кода нет). Только тогда мы пишем фактический код и проверяем, что тесты теперь проходят.

Написание тестовых примеров для наших имен функция

Теперь, когда мы понимаем, что должна делать наша функция, мы можем написать тесты, чтобы проверить, что она это делает. В tests/test_name_helper.py Файл, добавьте следующий код.

from namesplitter.utils import name_helper

def test_two_names():
    assert name_helper.split_name("John Smith") == ["John", "Smith"]

Обратите внимание, что namesplitter В первой строке взята из имени вашего проекта Repl.it, который определяет имена родительского модуля. Если вы назвали свой проект кое -что еще, вам нужно использовать это имя в линии импорта. Важно не включать специальных символов в имя проекта (включая дефиса, поэтому такие имена, как my-tdd-demo

утверждение Ключевое слово просто проверяет, что конкретный оператор оценивается как Верно Анкет В этом случае мы называем нашу функцию с левой стороны и даем ожидаемое значение с правой стороны и спрашиваем утверждение Чтобы проверить, являются ли они одинаковыми.

Это наш самый основной случай: у нас есть два имени, и мы просто разделили их на одном пространстве. Конечно, мы не написали split_name Функция еще где угодно, поэтому мы ожидаем, что этот тест потерпит неудачу. Давайте проверим.

Обычно вы запускаете свои тесты, набрав py.test в ваш терминал, но используя Repl. Это работает лучше, если мы импортируем pytest в нашу кодовую базу и запустите ее оттуда. Это потому, что а) наш терминал всегда уже активируется в среду Python, а B) кэширование обновляется, когда мы нажимаем на Запустить Кнопка, так что вызывая наши тесты извне, означает, что они могут запустить старые версии нашего кода, вызывая путаницу.

Давайте запустим их из нашего main.py Файл пока, так как мы еще не используем его ни для чего еще. Добавьте следующее в этот файл.

import pytest
pytest.main()

Нажмите Запустить кнопка. pytest делает автоматическое обнаружение тестов Так что вам не нужно говорить, какие тесты запустить. Он будет искать файлы, которые начинаются с тест и для функций, которые начинаются с test_ и предположим, что это тесты. (Вы можете прочитать больше о том, как именно работает обнаружение теста и может быть настроено Здесь ..

Вы должны увидеть несколько страшных красных неудач, как показано ниже. ( pytest использует дивидоры, такие как ===== и ------ Для форматирования разделов, и они могут стать грязными, если ваша выходная панель слишком узкая. Если все выглядит немного шатко, попробуйте сделать его шире и повторно.)

Если вы прочитаете вывод сверху вниз, вы увидите, как произошла куча разных вещей. Сначала pytest Ran Test Discovery и нашел один тест. Он запустил это, и он не удался, так что вы видите первое красное F выше Неудачи раздел. Это говорит нам точно, какая линия теста не удалась и как. В этом случае это было Атрибут Как мы пытались использовать split_name который не был определен. Пойдем, исправим это.

Перейти к utils/name_helper.py Файл и добавьте следующий код.

def split_name(name):
    first_name, last_name = name.split()
    return [first_name, last_name]

Это очень простая версия, которую мы обсуждали ранее, которая может обрабатывать только два имена, но она решит ошибку имени, а TDD – это небольшие приращения. Пресс Запустить Чтобы перезапустить тесты, и теперь вы должны увидеть гораздо более дружелюбный зеленый выход, как показано ниже, что указывает на то, что все наши тесты прошли.

Прежде чем исправить нашу функцию, чтобы справиться с более сложными случаями, давайте сначала напишем тесты и проверим, что они терпят неудачу. Вернуться к tests/test_name_helper.py и добавьте следующие четыре тестовые функции под существующим.

from namesplitter.utils import name_helper

def test_two_names():
    assert name_helper.split_name("John Smith") == ["John", "Smith"]

def test_middle_names():
    assert name_helper.split_name("John Patrick Smith") == ["John Patrick", "Smith"]
    assert name_helper.split_name("John Patrick Thomson Smith") == ["John Patrick Thomson", "Smith"]

def test_surname_prefixes():
    assert name_helper.split_name("John van der Berg") == ["John", "van der Berg"]
    assert name_helper.split_name("John Patrick van der Berg") == ["John Patrick", "van der Berg"]

def test_split_name_onename():
    assert name_helper.split_name("Smith") == ["", "Smith"]

def test_split_name_nonames():
    assert name_helper.split_name("") == ["", ""]

Повторите тесты, и теперь вы должны увидеть гораздо больше выводов. Если вы прокрутите обратно к самым последним ===== Запуск тестового сеанса ===== Раздел, он должен выглядеть следующим образом.

В верхнем разделе .Ffff является сокращением для «пять тестов, проходивших, первые прошли, а следующие четыре вышли из строя» (зеленая точка указывает проход, а красный F указывает на сбой). Если бы у вас было больше файлов с тестами, вы бы увидели такую строку, как этот файл, с одним символом вывода за тест.

Сбои подробно описаны после этого, но все они составляют вариации одной и той же проблемы. Наш код в настоящее время предполагает, что мы всегда получим ровно два имени, поэтому он либо имеет слишком много или слишком мало значений после запуска split () На тестовых примерах.

Исправление нашей функции split_name

Вернуться к name_helper.py и изменить его, чтобы выглядеть следующим образом.

def split_name(name):
    names = name.split(" ")

    if not name:
        return ["", ""]

    if len(names) == 1:
        return ["", name]

    if len(names) == 2:
        firstname, lastname = name.split(" ")
        return [firstname, lastname]

Это должно справиться с случаем нуля, одного или двух имен. Давайте снова запустим наши тесты, чтобы увидеть, сделали ли мы прогресс, прежде чем мы рассмотрим более сложные случаи. Теперь вы должны получить гораздо меньше выводов и три зеленые точки, как показано ниже.

Остальная часть вывода указывает на то, что это вторые имена и примеры фамилии префикс, которые все еще спотыкают нашу функцию, поэтому давайте добавим код, который нам нужно исправить. Другим важным аспектом TDD является поддержание ваших функций максимально небольшими, чтобы их было легче понять, тестировать и повторно использовать, поэтому давайте напишем вторую функцию для обработки трех или более имен.

Добавьте новую функцию с именем split_name_three_plus () и добавить иначе пункт к существующему split_name функция, где вы называете эту новую функцию. Весь файл теперь должен выглядеть следующим образом.

def split_name_three_plus(names):
    first_names = []
    last_names = []

    for i, name in enumerate(names):
        if i == len(names) - 1:
            last_names.append(name)
        elif name[0].islower():
            last_names.extend(names[i:])
            break
        else:
            first_names.append(name)
    first_name = " ".join(first_names)
    last_name = " ".join(last_names)
    return [first_name, last_name]

def split_name(name):
    names = name.split(" ")

    if not name:
        return ["", ""]

    if len(names) == 1:
        return ["", name]

    if len(names) == 2:
        firstname, lastname = name.split(" ")
        return [firstname, lastname]
    else:
        return split_name_three_plus(names)

Новая функция работает, всегда добавляя имена к First_names Список до тех пор, пока он не дойдет до фамилии, или пока не встретит имя, которое начинается с строчной буквы, после чего она добавляет все оставшиеся имена к Last_names список. Если вы снова запустите тесты, они все должны пройти сейчас.

Тесты уже были полезны, чтобы убедиться, что мы понимали проблему и что наша функция работала для конкретных примеров. Если бы мы совершили какие-либо ошибки в нашем коде, которые касаются трех или более имен, наши тесты поймали бы их. Если нам нужно рефакторировать или изменить наш код в будущем, мы также можем использовать наши тесты, чтобы убедиться, что наш новый код не вводит никаких регрессий (где проблемы исправления заставляют кода нарушать другие примеры, которые работали до исправления.)

Используя нашу функцию

Давайте создадим очень простое приложение для использования нашей функции. Замените код тестирования в main.py со следующим.

from utils import name_helper

name = input("Please enter your full name: ")

first_name, last_name = name_helper.split_name(name)

print(f"Your first name is: {first_name}")
print(f"Your last name is: {last_name}")

Если вы запустите это, это предложит пользователю их имя, а затем отобразит их имя и фамилию.

Потому что вы используете main.py Файл сейчас, вы также можете вызвать pytest непосредственно из выходной консоли справа путем ввода импорт питест; pytest.main () Анкет Обратите внимание, что обновления вашего кода применяются только при нажатии Запустить Кнопка, хотя, поэтому обязательно запустите свой код между изменениями перед запуском тестов.

Сделайте это своим

Мы написали сплиттер с именем, который может справиться с некоторыми именами более сложными, чем просто «Джон Смит». Это не идеально, хотя: например, если вы положили имя с двумя последовательными пространствами, оно будет разбиться с нашей программой. Вы могли бы разжечь проект и исправить его, сначала написав тест с последовательными пространствами, а затем изменить код для обработки этого (и любые другие случаи краев, о которых вы можете подумать).

Где дальше

Вы научились делать TDD в этом проекте. Это популярный стиль программирования, но это не для всех. Даже если вы решите не использовать TDD, наличие тестов все еще очень полезно, и крупные проекты нередко имеют тысячи или миллионы тестов.

Взгляните на Большой список непослушных струн Для проекта, который собирает входные данные, которые часто вызывают разрыв программного обеспечения. Вы также можете прочитать Как протестирован SQLite Что объясняет, как SQLite, популярная легкая база данных, имеет 150 тысяч строк кода и почти 100 миллионов (!) Линий тестов.

В следующем уроке мы покажем вам, как стать Repl.it PowerUser, используя преимущества продуктивности, которые он предлагает.

Оригинал: “https://dev.to/ritza/an-introduction-to-pytest-and-doing-test-driven-development-with-repl-it-2c78”