Мы видели это уже довольно много раз: какой-то блокчейн-проект решает собрать средства на разработку и пишет для этого смарт-контракт. Кто-то делает небольшую ошибку (или много, много ошибок) и - упс! - контракт взломан и средства потеряны.

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

Поэтому крайне важно, чтобы мы писали автоматические тесты, проверяющие наш код. Это помогает нам гарантировать качество разрабатываемого программного обеспечения. Здесь, в Neufund, разработка и активное обслуживание комплексного испытательного комплекта - один из наших главных приоритетов. Это особенно верно для распределенного приложения (dApp), которое мы создали для сбора средств в рамках Механизма начального наращивания капитала (ICBM).

Вы можете написать несколько уровней тестов, но в этой статье я собираюсь сосредоточиться на сквозных (E2E) тестах. Они предназначены для тестирования всей системы в конфигурации, близкой к производственной среде, имитируя взаимодействие конечного пользователя с ней. Важно отметить, что мы собираемся протестировать не только интерфейсное приложение, но и смарт-контракты, работающие на блокчейне Ethereum. Чтобы обнаружить регрессии, мы запускаем тесты E2E для каждой фиксации с помощью TravisCI.

Подготовка смарт-контрактов

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

Нарушение недоверия

Одним из преимуществ использования смарт-контрактов для поддержки вашей компании является то, что вашим пользователям не нужно доверять вашим благим намерениям. Вместо этого они доверяют исходному коду программы, работающей в цепочке блоков. Все прозрачно. Конечно, мы сделали то же самое в Neufund (у нас тоже добрые намерения!;)). Результатом является система, которая дает вам множество гарантий и надежно обрабатывает ваши средства. Это здорово для наших пользователей, но делает практически невозможным написание тестов E2E. Причина в том, что нам нужен простой способ управления всей системой для тестирования различных сценариев. Для выполнения этой задачи нам потребовалось изменить исходный код и нарушить эти гарантии. Конечно, это делается только во время тестов.

Докеризация

Docker упрощает запуск процессов со сложными зависимостями. В нашем случае мы собираемся создать образ докера с помощью testrpc и скомпилировать контракты, готовые к запуску с контейнером. Dockerfile не особо интересный, поэтому я не буду его сюда вставлять. Единственное, что заслуживает внимания, - это сценарий запуска, который гарантирует, что testrpc получит PID 1 и не завершит работу слишком рано:

#!/usr/bin/env bash
set -e
cd “$(dirname “$0”)”
cd ..
wait_for_testrpc() {
 ./scripts/wait-for-it.sh localhost:8545 -t 5
 yarn deploy:fast
}
wait_for_testrpc&
exec ./scripts/testrpc.sh

Мы запускаем testrpc в детерминированном режиме, чтобы позже можно было быстро получить адрес контракта.

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

В наши дни существует множество различных подходов к написанию тестов E2E. Вы можете использовать один из драйверов Selenium для управления множеством браузеров или просто выбрать один из безголовых браузеров, с которым проще работать большую часть времени. Мы решили использовать новый блестящий безголовый Chrome и Кукловод, чтобы управлять им. Он очень прост в настройке и работает с минимальной настройкой на серверах CI, таких как Travis.

E2E-тесты по своей природе асинхронны, поэтому подготовьтесь к работе с обещаниями - много. К счастью, новые версии Node имеют встроенную поддержку синтаксиса await / async, что значительно упрощает работу с асинхронным кодом. В частности, Typescript (о котором мы уже писали) упрощает работу с обещаниями - вы получите сообщение об ошибке, если не ждете на обещании.

Шаблон объекта страницы

Шаблон объекта страницы - это подход, при котором вы объединяете все взаимодействие со страницей в одном месте. Это помогает значительно сократить объем кода в самом тестовом примере, а также делает его более читабельным. Вы просто говорите «перейти на страницу регистрации», «нажмите кнопку ОК», «введите текст в это поле» вместо «выберите кнопку с id = 'accept-btn'», «щелкните по ней» и т. Д. Это позволяет нам писать код вроде этого:

const homepage = await HomePage.create(puppeteerInstance);
await homepage.beforeIcoDetails.waitFor();
expect(await homepage.countdownDays.text()).to.be.eq("00");
expect(await homepage.countdownHours.text()).to.be.eq("00");
expect(await homepage.countdownMinutes.text()).to.be.eq("50");
expect(await homepage.countdownSeconds.text()).to.be.eq("00");

Web3-инъекция

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

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

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

await page.evaluate((web3Raw: any) => {
      eval(web3Raw);
      (window as any).web3 = new (window as any).Web3(
        new (window as any)
        .Web3.providers.HttpProvider("https://localhost:9090/node")
      );
}, web3Raw as any);

Это один из редких случаев, когда Typescript на самом деле не дает никаких преимуществ, поскольку тело этой функции будет сериализовано и оценено в совершенно другой среде - внутри Chrome без головы.

Другая проблема заключается в том, что информация о типе Кукловода не точна. Вот почему нам нужны все эти уродливые любые приведения. Однако хорошей новостью является то, что TS 2.6 будет поставляться с комментариями // @ ts-ignore как способ заглушить любые проблемы с типом.

Путешествие во времени

Хорошо, возможно, мы не собираемся буквально путешествовать во времени, но Chrome сделает это за нас! Наше приложение dApp показывает множество счетчиков и, вообще говоря, очень «чувствительно ко времени»: оно меняет свой макет в зависимости от того, запускается ли оно до или после даты запуска межконтинентальной баллистической ракеты.

Работая над этим, я задавался вопросом, есть ли реальный способ изменить время браузера для тестирования различных сценариев. Оказывается, есть! Мы можем использовать библиотеку lolex (подмножество sinonjs) для управления работой таймеров в браузере. Мы можем, например, заморозить время, чтобы сделать некоторые утверждения с уверенностью и т. Д. Единственная сложность здесь - убедиться, что оно загружается ПЕРЕД кодом вашего приложения. По сути, это тот же подход, что и при внедрении Web3 - просто используйте функцию rating.

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

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

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

Чтобы узнать больше, посетите страницу МБР Neufund или технический документ для более глубокого нырять. Вопросов? Спросите нас о чем угодно в Twitter и Telegram!