0xTTEPX

Just do it, deeply...

Follow me on GitHub

Truffle测试合约

write by donaldhan, 2020-05-19 10:01

引言

上一篇我们首先简单罗列了区块链合约开发相关的工具(npm,ide:vscode,ganache:以太坊区块链)。使用ganache我们可以快速启动一个个人版以太坊区块链, 方便通过操作控制区块链来运行测试, 执行命令, 检查区块链相关的状态;通过ganache我们可以简单的管理区块链账户、区块、交易、合约、事件及日志。同时我们可以使用ganache的设置区块相关的配置,比如初始化账户,账户金额,是否锁定账户,配置RCP地址和端口,上链配置(燃气值,燃气价格),以及通过工作空间配置,将ganache和truffle工程关联起来,一般将truffle工程中的合约部署到,ganache创建的本地区块链上。在了解了以太坊客户端ganache使用之后,我们使用truffle box创建了最基础的metacoin, 使用truffle的相关命令,编译,以及部署了相关合约,并根据使用Ganache客户端,对合约的编译,和部署进行了分析。针对合约,一个交易主要包含 交易地址、部署合约账户地址、合约操作(创建合约、调用合约),交易额(针对转账),消耗燃气,燃气价格 ,燃气限制(如果创建交易的过程中,超过燃气值,交易失败,但部署的过程燃气值不会退回),关联区块,交易数据及事件等信息。针对合约,一个区块主要包含,区块编号,消耗燃气,燃气限制,区块形成时间,区块地址,交易地址 交易类型(创建合约、调用合约),创建合约账户,合约地址,交易额(针对转账)。合约详情中包括基本信息(合约地址,账户余额,合约创建时的交易地址),合约相关数据,合约上产生的交易信息,合约相关的事件。今天我们来测试一下合约

目录

Truffle测试合约简介

Truffle 集成自动化测试框架让测试工作变得简单. 框架支持两种语言进行编写:

  • Javascript, 一般用于接口测试, 就像从应用调用一样.
  • Solidity, 一般用于高级测试, 测试更底层的内容. 每一种方式都有对应的优劣, 看下面的部分来看各自优劣.

JS编写测试脚本

Truffle 使用 Mocha 测试框架, Chai 作为断言工具, 来提供JS(javascript-tests)测试用的可靠框架. 我们来详细看看 Truffle 是如何使用 Mocha 进行测试的. 注意: 如果使用 Mocha 进行单元测试并不熟悉, 请先了解一下 Mocha 文档

Solidity 测试合约

Solidity 测试合约(solidity-tests)与 JS 测试合约并驾齐驱, 只是以 .sol 为扩展名. 当执行 truffle test 的时候, 每个测试文件都会单独的被引用执行. 这些合约保持与 JS 测试脚本相同的优势, 每个测试都会创建 清洁的沙箱环境, 可以直接访问部署好的合约, 支持导入任何合约依赖. 除了这些功能之外, Truffle 的 Solidity 测试框架创建的时候考虑到了下面的问题:

  • Solidity 测试代码不能使用继承 (例如 Test 合约基类). 这使得测试脚本尽可能的简短, 并且能够极大化的控制测试合约的功能.
  • Solidity 测试代码无需引入第三方断言库. Truffle 提供默认的断言库, 支持在使用的时候修改断言库的参数来满足个性化需求.
  • 可以直接基于 Ethereum 客户端使用 Solidity 测试脚本.

清洁的沙箱环境

Truffle 提供了一个清洁的沙箱测试环境. 当测试环境连接本地 Ganache 或者 Truffle 开发环境 (truffle develop), Truffle 将使用高级的快照特性, 保证每个测试的环境并不相互影响. 当使用其他客户端, 例如 go-ethereum 的时候, Truffle 将会在运行每个测试文件之前重新部署所有部署合约, 以便保证有一个全新的合约用于测试.

高效可信的测试环境选择

在运行测试的时候, 使用 Ganache 和 Truffle 开发环境都相对其他客户端更快更便捷. 此外, 两个环境都包含与 Truffle 吻合的高级特性, 用于加速近90%的速度. 一般工作流, 我们强烈推荐在开发和测试过程使用 Ganache 或者 Truffle 开发环境, 然后再在部署正式网络环境之前, 以 go-ethereum 或者其他客户端为环境测试一次.

## 测试代码存放的位置 所有的测试代码必须存放在 ./test 目录内. Truffle 将会自动识别以 .js, .es, .es6, .jsx 和 .sol 为扩展名的测试代码. 其他扩展名将被忽略

JS测试合约

我们先来一下JS测试合约脚本

const MetaCoin = artifacts.require("MetaCoin");
//JS模式测试合约
contract('MetaCoin', (accounts) => {
  it('should put 10000 MetaCoin in the first account', async () => {
    const metaCoinInstance = await MetaCoin.deployed();
    const balance = await metaCoinInstance.getBalance.call(accounts[0]);
    //断言部署合约的账户的初始化账户余额为10000 
    assert.equal(balance.valueOf(), 10000, "10000 wasn't in the first account");
  });
  it('should call a function that depends on a linked library', async () => {
    const metaCoinInstance = await MetaCoin.deployed();
    const metaCoinBalance = (await metaCoinInstance.getBalance.call(accounts[0])).toNumber();
    const metaCoinEthBalance = (await metaCoinInstance.getBalanceInEth.call(accounts[0])).toNumber();
    //验证ConvertLib 库的账户余额转换为以太币的功能
    assert.equal(metaCoinEthBalance, 2 * metaCoinBalance, 'Library function returned unexpected function, linkage may be broken');
  });
  it('should send coin correctly', async () => {
    //获取部署合约对象
    const metaCoinInstance = await MetaCoin.deployed();

    // Setup 2 accounts. 取区块链上的两个账户
    const accountOne = accounts[0];
    const accountTwo = accounts[1];

    // Get initial balances of first and second account. 获取账户初始化值
    const accountOneStartingBalance = (await metaCoinInstance.getBalance.call(accountOne)).toNumber();
    const accountTwoStartingBalance = (await metaCoinInstance.getBalance.call(accountTwo)).toNumber();

    // Make transaction from first account to second.
    //从账户1转账10数量的ETH,当前所以账户显示的数据,精确到分,可以简单理解为转移0.01个ETH
    const amount = 10;
    await metaCoinInstance.sendCoin(accountTwo, amount, { from: accountOne });

    // Get balances of first and second account after the transactions. 获取转账交易后的账户余额
    const accountOneEndingBalance = (await metaCoinInstance.getBalance.call(accountOne)).toNumber();
    const accountTwoEndingBalance = (await metaCoinInstance.getBalance.call(accountTwo)).toNumber();

    //断言账户1减少响应的金额,断言账户2增加响应的金额
    assert.equal(accountOneEndingBalance, accountOneStartingBalance - amount, "Amount wasn't correctly taken from the sender");
    assert.equal(accountTwoEndingBalance, accountTwoStartingBalance + amount, "Amount wasn't correctly sent to the receiver");
  });
});

这个测试合约主要测试了一下3个功能

  1. 断言部署合约的账户的初始化账户余额为10000
  2. 验证ConvertLib 库的账户余额转换为以太币的功能
  3. 验证两个账户的转账,及转账后的金额;

我们来执行一下JS测试脚本:

PS F:\github\solidity-demo> truffle test .\test\metacoin.js
Using network 'development'.


Compiling your contracts...
===========================
> Compiling .\contracts\ConvertLib.sol
> Compiling .\contracts\MetaCoin.sol
> Compiling .\contracts\ConvertLib.sol



  Contract: MetaCoin
    √ should put 10000 MetaCoin in the first account (85ms)
    √ should call a function that depends on a linked library (163ms)
    √ should send coin correctly (621ms)


  3 passing (949ms)

从日志可以看出,3个验证点,全部通过;

在这个测试的过程产生的6个区块 js_test_block

对应6个交易

js_test_transactions

我们来看最后一个交易,最后一个交易为合约转账调用,即最后的合约转账测试

来看一下, 合约转账交易的详情 js_test_transactions_transfer_event

我们再来看相关的事件,在这个测试过程中产生了1个事件 ganache_events

EVENT NAME:事件名 Transfer CONTRACT:事件涉及合约 MetaCoin TX HASH:产生事件的交易 0x6dd6767d3ffc79a472d5193d07f587a3c7aa57c8e8ff9382159988909a1ead06 LOG INDEX 0 BLOCK TIME:交易区块产生时间 2020-02-18 21:29:28

来看事件详情 ganache_events_details

CONTRACT NAME MetaCoin CONTRACT ADDRESS:合约地址 0x5695bbe862611d644AFd5FE9e8087B673Fdf26D9 SIGNATURE (DECODED):事件签名(合约操作) Transfer(_from: address, _to: address, _value: uint256) TX HASH 0x6dd6767d3ffc79a472d5193d07f587a3c7aa57c8e8ff9382159988909a1ead06 LOG INDEX 0 BLOCK TIME 2020-02-18 21:29:28 RETURN VALUES _from:账户0 0x209f9f4f4f38a8b966f905f90f451f235136420c _to:账户1 0xe4320a733636ca9534a745e25782452d75ab559a _value 10

从上面可以看出,合约转账交易事件主要有事件名,事件涉及合约,,交易区块产生时间,产生事件的交易地址;合约转账交易事件详情除合约交易相关信息,还有事件签名(合约操作)及相关操作数。

我们来看一下转移后的账户余额 js_ganache_account_transfer 账户0:0x209F9f4F4f38a8b966f905F90f451F235136420C, 部署合约后为99.98ETH;转出0.01个ETH, 还剩99.97 ETH。

账户1:0xe4320a733636cA9534a745E25782452D75Ab559A,入账后的账户余额应该为100.01ETH,但是实际为100.00ETH??? 如果有清楚的,可以@me。

Solidity测试合约

// solium-disable linebreak-style
pragma solidity >=0.4.25 <0.6.0;

import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/MetaCoin.sol";
// solidity模式测试合约
contract TestMetacoin {
  function testInitialBalanceUsingDeployedContract() public {
    MetaCoin meta = MetaCoin(DeployedAddresses.MetaCoin());
    // log ("metacoin address:", meta.getBalance);
    uint expected = 10000;
    //断言产生部署Metacoin交易的原始账户余额为10000
    Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially");
  }

  function testInitialBalanceWithNewMetaCoin() public {
    MetaCoin meta = new MetaCoin();
    uint expected = 10000;
    //断言产生部署Metacoin交易的原始账户余额为10000
    Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially");
  }

}

执行测试

PS F:\github\solidity-demo> truffle test .\test\TestMetacoin.sol
Using network 'development'.


Compiling your contracts...
===========================
> Compiling .\contracts\ConvertLib.sol
> Compiling .\contracts\MetaCoin.sol
> Compiling .\contracts\ConvertLib.sol
> Compiling .\test\TestMetacoin.sol



  TestMetacoin
    √ testInitialBalanceUsingDeployedContract (371ms)
    √ testInitialBalanceWithNewMetaCoin (287ms)


  2 passing (20s)

PS F:\github\solidity-demo>

从上面来看两个测试全部通过,产生升了22区块(12-33)对应22交易,有3个合约调用,19创建合约为什么是这样,待考究????

solidity_test_block

待着账户转账余额不对的问题,再来看账户余额: solidity_test_block

但账户0余额上,只有99.67 ETH,为什么少了,交易消耗了燃气值,如果这样可以理解,如果想一想账户转账余额金额为什么对不上??? 一脸懵逼。

后面我们抽时间可以使用ganache-cli, 看看能不能使用命令行的模式,一探究竟。

总结

Truffle的集成自动化测试框架让测试工作变得简单. 框架支持两种语言进行编写。Javascript, 一般用于接口测试, 就像从应用调用一样;Solidity, 一般用于高级测试, 测试更底层的内容; Truffle 使用 Mocha 测试框架, Chai 作为断言工具, 来提供JS(javascript-tests)测试用的可靠框架。Solidity 测试代码不能使用继承 (例如 Test 合约基类)。 这使得测试脚本尽可能的简短, 并且能够极大化的控制测试合约的功能;同时测试代码无需引入第三方断言库. Truffle 提供默认的断言库, 支持在使用的时候修改断言库的参数来满足个性化需求。 最重要的是,可以直接基于 Ethereum 客户端使用 Solidity 测试脚本。

合约转账交易事件主要有事件名,事件涉及合约,,交易区块产生时间,产生事件的交易地址;合约转账交易事件详情除合约交易相关信息,还有事件签名(合约操作)及相关操作数。

待解决问题

  1. js测试合约转账后的账户金额不对??? 创建合约,调用合约交易消耗gas???

断点确认这个问题metacoin.js

执行所有单元测试

如果我们想执行所有的单元测试,可以执行如下命令 run solidity unit test

truffle test

控制台输出

PS F:\github\solidity-demo> truffle test
Using network 'development'.


Compiling your contracts...
===========================
> Compiling .\contracts\ConvertLib.sol
> Compiling .\contracts\MetaCoin.sol
> Compiling .\contracts\ConvertLib.sol
> Compiling .\test\TestMetacoin.sol



  TestMetacoin
    √ testInitialBalanceUsingDeployedContract (466ms)
    √ testInitialBalanceWithNewMetaCoin (492ms)

  Contract: MetaCoin
    √ should put 10000 MetaCoin in the first account (91ms)
    √ should call a function that depends on a linked library (131ms)
    √ should send coin correctly (465ms)


  5 passing (19s)

PS F:\github\solidity-demo>