Часть третья: написание базовых тестов

Эта статья является частью серии. Если вы еще не сделали этого, ознакомьтесь с предыдущими статьями:

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

Пора писать наши первые тесты! Мы будем тестировать функциональность в простом контракте ERC20. Если вы хотите использовать Solidity, вы можете создать локальную копию контракта с помощью следующей команды:

brownie bake token

или, если вы предпочитаете Vyper:

brownie bake vyper-token

Обратите внимание, что каждый из этих проектов поставляется с полным набором тестов. Не стесняйтесь просматривать файлы в каталоге tests/, чтобы найти еще несколько примеров.

Написание наших первых тестов

Начнем с простого теста:

  • В строке 1 мы импортируем объекты Brownie, необходимые для теста. accounts дает нам доступ к финансируемым и разблокированным счетам, а Token используется для развертывания нашего контракта ERC20.
  • Строка 3 - это начало нашего теста. При использовании pytest все функции, начинающиеся с test_, считаются тестовыми.
  • В строках 4 и 5 мы выполняем нашу тестовую настройку. Сначала мы развертываем Token контракт, а затем ведем учет баланса accounts[0], который мы будем использовать позже в нашем утверждении.
  • В строке 7 мы выполняем тестируемое действие, передавая 10¹⁸ токенов с accounts[0] на accounts[1].
  • В строке 9 мы делаем наше утверждение: баланс токенов accounts[0] должен уменьшиться ровно на 10¹⁸.

Здесь происходит довольно много всего, но как только вы освоитесь, синтаксис станет довольно простым.

А теперь время для второго теста:

Ключевое различие между этим тестом и первым состоит в том, что в первом мы пошли по «счастливому пути» - по тому, где код выполняется успешно; но в этом тесте мы ожидаем возврата транзакции.

Важный раздел здесь - это строки 7 и 8, где мы используем диспетчер контекста brownie.reverts. Чтобы тест прошел, транзакция в диспетчере контекста должна вернуться с указанным сообщением об ошибке. Обратите внимание, что указывать сообщение об ошибке необязательно - мы могли бы просто сказать brownie.reverts() в качестве обобщающего слова для любой откатывающейся транзакции.

Светильники

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

Наш новый тест будет почти идентичен первому, поэтому у вас может возникнуть соблазн скопировать-вставить и внести несколько изменений:

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

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

Чтобы создать прибор, мы применяем декоратор @pytest.fixture к функции. Давайте создадим приспособление для развертывания нашего контракта:

Затем мы передаем результат прибора в наши тесты, добавляя имя прибора в качестве входного аргумента теста:

Дублированного кода больше нет, и наши тесты теперь легче поддерживать!

Совместное использование приборов в тестовых модулях

Если вам требуется одно и то же устройство в нескольких тестовых файлах, вы можете переместить его в файл с именем conftest.py в той же папке или в общей родительской папке. Pytest автоматически обнаруживает эти фикстуры и делает их доступными - вам не нужно ничего импортировать вручную.

Связывание приспособлений

Помимо передачи приборов в тестовые функции, вы также можете передавать приборы в другие приборы. Таким образом, вы можете построить сложные процессы настройки тестирования с минимальным повторением кода.

В качестве примера давайте создадим приспособление для распределения токенов, которое требует нашего исходного приспособления для токенов:

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

Встроенные светильники

Brownie предоставляет нам набор встроенных приспособлений для доступа к различным функциям. В качестве примера давайте продолжим рефакторинг наших текущих тестов, чтобы использовать встроенные accounts и Token фикстуры:

Нам удалось удалить оператор import, вместо этого получив доступ к той же функции через встроенные фикстуры.

Большинство основных компонентов Brownie и объектов проекта доступны через приспособления - Документация Brownie содержит полный список.

Объем приспособления

По умолчанию прибор должен выполняться каждый раз, когда он требуется для проверки. Добавляя параметр scope в декоратор, мы можем изменить частоту выполнения фикстуры:

Установив наш прибор в область видимости модуля, он теперь разворачивает только один Token контракт, передавая одно и то же значение в оба теста. С помощью сложных наборов тестов это может сэкономить вам значительное количество времени.

Приспособления с более высокими областями видимости (например, session или module) всегда создаются перед приборами с более низкими областями видимости (например, function). Порядок выполнения фикстур одной и той же области видимости определяется порядком, в котором они объявлены в качестве входных аргументов фикстуры и теста. Единственное исключение из этого правила - изолирующие приспособления, которые мы рассмотрим далее.

Тестовая изоляция

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

Brownie предоставляет два приспособления, которые используются для изоляции:

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

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

Чтобы применить прибор изоляции ко всем тестам в модуле, потребуйте его в другом приспособлении и включите аргумент ключевого слова autouse:

Собираем все вместе

Давайте проведем последний рефакторинг наших тестов, объединив все, что мы обсуждали до сих пор:

  • В строках 5–7 мы объявляем приспособление, которое разворачивает Token контракт. Мы установили его как модуль с областью видимости, чтобы он разворачивал только один контракт для всех тестов.
  • В строках 10–12 мы добавляем приспособление fn_isolation, чтобы гарантировать, что наши тесты должным образом изолированы. Снимок локальной цепочки блоков будет сделан сразу после выполнения token фикстуры, и каждый тест будет запускаться с этого снимка.
  • Строки 15–19 - это наш первый тест. Мы используем встроенный прибор accounts, а также прибор token, который мы объявили ранее. В этом тесте мы переводим несколько токенов и делаем утверждение о балансе отправителя.
  • Строки 22–26 - это наш второй тест. Очень похоже на предыдущий тест, за исключением того, что на этот раз мы делаем утверждение о балансе приемника.
  • Строки 29–31 - это наш третий и последний тест. В этом тесте мы используем brownie.reverts диспетчер контекста, чтобы подтвердить возврат транзакции, когда мы пытаемся передать слишком много токенов.

Сохраните тесты как tests/test_first.py внутри проекта токена. Затем запустите их с помощью следующей команды:

brownie test tests/test_first.py

Вы должны получить примерно такой результат:

Каждая зеленая точка представляет собой один из наших тестов. Все прошло! 🎉

Что дальше

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

Вы также можете подписаться на Twitter account Brownie, прочитать другие мои Medium статьи и присоединиться к нам в Gitter.