[Код этого сообщения в блоге можно найти здесь: https://github.com/gustavoguimaraes/smart-contract-testing-javascript-example-]

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

В этом посте я покажу вам, как начать тестирование смарт-контрактов с самого начала.

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

- [email protected]
- truffle@^4.0.0
- ethereumjs-testrpc@^4.0.1

Разобравшись с зависимостями, давайте настроим проект.

mkdir smart-contract-test-example && cd "$_" && truffle init webpack

Приведенный выше фрагмент создает каталог, переходит в него и инициализирует Truffle с помощью webpack.

Теперь создайте тестовый файл для FundRaise смарт-контракта, который мы создадим.

touch test/fundRaise.js

Откройте файл в вашем любимом текстовом редакторе и добавьте самую простую структуру теста.

const FundRaise = artifacts.require('./FundRaise.sol')
contract('FundRaise', function () {
})

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

Затем мы создаем функцию contract(), которая похожа на функцию describe() в Mocha, за исключением того, что Truffle добавляет некоторые функции, такие как обеспечение развертывания контрактов перед запуском тестов. Кстати, Truffle использует под капотом фреймворк тестирования Mocha, а также библиотеку утверждений Chai.

Теперь давайте запустим этот простой тест.

Первый:

testrpc

Затем откройте новое окно командной строки и введите

truffle test test/fundRaise.js

Результат:

Error: Could not find artifacts for ./FundRaise.sol from any sources
    at Resolver.require (/usr/local/lib/node_modules/truffle/build/cli.bundled.js:49072:9)
    at TestResolver.require (/usr/local/lib/node_modules/truffle/build/cli.bundled.js:89733:30)
...

Это потому, что мы еще не написали смарт-контракт FundRaise. Давай исправим это.

touch contracts/FundRaise.sol

Затем добавьте код

pragma solidity ^0.4.17;
contract FundRaise {
}

Снова запустите тест:

 0 passing (1ms)

Верно. Еще нет написанных тестов.

Спецификация смарт-контракта и добавление тестов

Смарт-контракт FundRaise будет простым контрактом. Его простая спецификация:

  • у него есть владелец
  • он принимает средства
  • он может приостанавливать и возобновлять деятельность по сбору средств
  • Владелец контракта может в любой момент снять средства с контракта себе.

Начнем с первой спецификации - у контракта есть собственник.

const FundRaise = artifacts.require('./FundRaise.sol')
contract('FundRaise', function ([owner]) {
    let fundRaise
    beforeEach('setup contract for each test', async function () {
        fundRaise = await FundRaise.new(owner)
    })
    it('has an owner', async function () {
        assert.equal(await fundRaise.owner(), owner)
    })
})

В приведенном выше тестовом коде мы делаем несколько вещей.

1- причудливое назначение деструктурирующей переменной ES2015 в function([owner] первый параметр, заданный функции contract, представляет собой массив с учетными записями, поступающими из testrpc. Берем первый и присваиваем ему переменную owner.

2- создать переменную fundRaise

3- иметь функцию beforeEach, которая будет запускаться перед каждым тестом, каждый раз создавая новый экземпляр fundRaise. Обратите внимание на использование async/await для обещаний. Это позволяет сделать тестовый код более читаемым. Если вы хотите узнать больше о новых async/await функциях JavaScript, это хорошая запись в блоге.

4- создайте первый тест в функциональном блоке it(). Здесь мы утверждаем, что fundRaise.owner() - это owner, который мы передали при создании контракта.

Перед повторным запуском тестов перейдите к truffle.js и потребуйте babel-polyfill, так как он нам нужен для использования async/await.

truffle.js

// Allows us to use ES6 in our migrations and tests.
require('babel-register')
require('babel-polyfill')
module.exports = {
  networks: {
    development: {
      host: 'localhost',
      port: 8545,
      network_id: '*' // Match any network id
    }
  }
}

Запустите тесты еще раз, и вы обнаружите эту ошибку:

...
1 failing
1) Contract: FundRaise has an owner:
     AssertionError: expected undefined to equal '0x676c48fb3979cf2e47300e8ce80a99087589650d'
...

Пришло время написать код, который сделает первый тест пройденным. Давайте немного уточним наш смарт-контракт.

pragma solidity ^0.4.17;
contract FundRaise {
    address public owner;
    // @dev constructor function. Sets contract owner 
    function FundRaise() {
        owner = msg.sender;
    }
}

Запустите тест снова, т.е.truffle test test/fundRaise.js:

Contract: FundRaise
    ✓ has an owner (41ms)
1 passing (138ms)

Большой! Давайте продолжим и добавим следующий.

const FundRaise = artifacts.require('./FundRaise.sol')
  contract('FundRaise', function ([owner, donor]) {
    let fundRaise
    beforeEach('setup contract for each test', async function () {
      fundRaise = await FundRaise.new(owner)
    })
    it('has an owner', async function () {
      assert.equal(await fundRaise.owner(), owner)
    })
    it('is able to accept funds', async function () {
      await fundRaise.sendTransaction({ value: 1e+18, from: donor })
      const fundRaiseAddress = await fundRaise.address
      assert.equal(web3.eth.getBalance(fundRaiseAddress).toNumber(), 1e+18)
    })
})

На этот раз ошибка:

1 failing
1) Contract: FundRaise is able to accept funds:
     Error: VM Exception while processing transaction: invalid opcode

Хорошо, нам нужно позволить нашему контракту получать эфир. Давай исправим это.

pragma solidity ^0.4.17;
contract FundRaise {
    address public owner;
// @dev constructor function. Sets contract owner 
    function FundRaise() {
        owner = msg.sender;
    }
    
    // fallback function that allows contract to accept ETH 
    function () payable {}
}

И вот результат:

Contract: FundRaise
    ✓ has an owner (38ms)
    ✓ is able to accept funds (234ms)
2 passing (473ms)

Красивый. Это процесс, который нужно систематически выполнять, чтобы покрыть смарт-контракты тестами, написанными на JavaScript. Просто продолжайте этот процесс, пока не будут выполнены все спецификации смарт-контрактов.

Для простоты я собираюсь ускорить процесс и добавить полный набор тестов для FundRaise contract, чтобы вы имели представление о том, как это будет выглядеть в итоге.

а вот полный код смарт-контракта:

Запустите тесты в последний раз…

Contract: FundRaise
    ✓ has an owner (46ms)
    ✓ accepts funds (193ms)
    ✓ is able to pause and unpause fund activity (436ms)
    ✓ permits owner to remove funds (653ms)
4 passing (2s)

Чудесный!

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

Код для этого сообщения в блоге можно найти здесь: https://github.com/gustavoguimaraes/smart-contract-testing-javascript-example-