Часть шестая: Параметризация и тестирование на основе свойств

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

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

Давайте вернемся к одному из наших предыдущих тестовых примеров ERC20:

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

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

Параметризация теста

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

В Brownie мы достигаем этого с помощью декоратора @pytest.mark.parametrize. Вот тот же тест, что и выше, но с использованием параметризации:

Этот тест проверяет то же поведение, что и наш первый пример, но он выполняется три раза с разными суммами, передаваемыми каждый раз. Это полезно по двум причинам:

  1. Мы значительно усилили тестирование этого поведения. Если есть ошибка в том, как настраиваются балансы, наш набор тестов с большей вероятностью обнаружит ее.
  2. В будущем, если мы захотим расширить или изменить способ тестирования этого поведения, нам нужно будет изменить только один тестовый пример вместо четырех.

Довольно хорошо, правда? С помощью всего лишь одной строчки кода мы прошли наш первоначальный тест и сделали его значительно более эффективным.

… Но можем ли мы сделать лучше?

Есть много ситуаций тестирования, когда параметризация - правильный инструмент для работы. Но по-прежнему существует проблема, которую не удается решить должным образом: в конечном итоге мы всего лишь проверяем наши собственные ожидания относительно того, как должен функционировать контракт. Даже с помощью параметризованных тестов мы производим наборы данных, с которыми выполняются наши тесты. А где бывают крайние случаи? Именно туда, куда мы не смотрим!

Так как же нам преодолеть этот парадокс? Как мы можем писать тесты на то, чего не ожидаем? Ответ: тестирование на основе свойств.

Тестирование на основе собственности

Тестирование на основе свойств - мощный инструмент для выявления крайних случаев и выявления ошибочных предположений в вашем коде.

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

Это эффективно, поскольку автоматизирует одну из самых трудоемких частей написания тестов - составление примеров. А это означает, что ваш контракт с большей вероятностью будет заключен в случаях, которые вы не рассматривали, а именно в них ошибки и любят прятаться.

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

При выполнении этого теста логика выполняется пятьдесят раз. Во время каждого прогона amount - это другое значение где-то между 0 и 10¹⁸. Выбранные значения сохраняются в базе данных, и в последующих тестовых прогонах используются другие значения. Если обнаружено значение, которое приводит к сбою теста, это значение будет всегда включаться в последующие тесты.

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

Написание тестов на основе свойств

Написание тестов на основе свойств - довольно простой процесс. Магия исходит от двух методов:

  • @given - декоратор, преобразующий тестовую функцию в рандомизированный тест. Он определяет, какие аргументы в функции должны быть параметризованы, и как этот процесс должен обрабатываться.
  • strategy - это метод создания тестовых стратегий на основе типов ABI смарт-контрактов. Стратегия тестирования - это рецепт для описания типа данных, которые вы хотите сгенерировать. Обычно он определяет тип данных, нижнюю и верхнюю границы и значения, которые следует исключить.

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

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

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

Где использовать тесты на основе свойств

Если вы не знаете, с чего начать добавлять тесты на основе свойств в свой набор тестов, вот несколько идей:

  1. Ищите простые тесты, которые проверяют «когда X, то Y», где существует прямая взаимосвязь между X и Y. Балансы, суммы одобрения, разрешения пользователей - смарт-контракты полны ситуаций, когда такого рода тестирование может пригодиться.
  2. Ищите дублирование кода в своем наборе тестов. Есть ли случаи, когда у вас есть несколько тестов, подтверждающих одно и то же поведение? Можете ли вы преобразовать эти тесты в единый случай, основанный на свойствах?

Узнать больше

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

Что дальше

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

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