使用 JS 编写测试脚本

Truffle 使用 Mocha 测试框架, Chai 作为断言工具, 来提供JS测试用的可靠框架. 我们来详细看看 Truffle 是如何使用 Mocha 进行测试的.

注意: 如果使用 Mocha 进行单元测试并不熟悉, 请先了解一下 Mocha 文档

 使用 contract() 替代 describe()

从文件结构上, 大部分测试脚本的编写方式都不需要进行改动: 测试脚本存放在 ./test 目录内, 要以 .js 为文件扩展名, 并且包含 Mocha 标记. Truffle 测试脚本与经典 Mocha 测试脚本的区别是 contract() 方法, 这个方法的用途与 describe() 相同, 此外还会开启 Truffle 的 清洁沙箱.

  • 在执行每一个 contract() 函数前, 合约将重新部署到指定的 Ethereum 客户端, 以便于使用完全清洁的环境运行合约测试.
  • contract() 方法提供了一个可用的账号列表, 在测试过程中可以直接使用.

由于 Truffle 使用的是 Mocha, 还是可以直接使用 describe() 来运行普通测试的, 只是这样不会开启清洁沙箱.

 在测试中引入 contract 概念定义

合约概念是JS能与合约通信的基础, 是基于 flux capacitor, 由于 Truffle 无法知晓 测试脚本中需要与哪些合约进行交互, 需要进行显示的声明, 通过 Truffle 的内置方法 artifacts.require() 来引入指定的可用的合约概念, 进而引入合约. 你可以参考下面的例子, 使用合约概念保证合约正常运行.

更多详细关于合约概念的内容, 参考 如何与合约进行交互 部分.

 使用 artifacts.require()

在测试脚本中使用 artifacts.require() 与在部署脚本中的使用方法是一样的. 只需要传入合约名称即可. 详细内容参考合约部署部分的 artifacts.require() 文档 .

 使用 web3

web3 实例在每个测试脚本中都可以直接使用, 使用的指定的客户端连接. 直接调用 web3.eth.getBalance 即可!

 例子

 使用 .then

这是 MetaCoin Truffle Box 中的例子. 注意 contract() 方法的使用, 回调函数的参数 accounts 代表 Ethereum 账号列表, 以及使用 artifacts.require() 来直接与合约交互.

文件名: ./test/metacoin.js

// 引入合约
var MetaCoin = artifacts.require("./MetaCoin.sol");

// 类似 describe, 返回账号列表
contract('MetaCoin', function(accounts) {
  it("should put 10000 MetaCoin in the first account", function() {
    return MetaCoin.deployed().then(function(instance) {
      return instance.getBalance.call(accounts[0]);
    }).then(function(balance) {
      assert.equal(balance.valueOf(), 10000, "10000 wasn't in the first account");
    });
  });
  it("should call a function that depends on a linked library", function() {
    var meta;
    var metaCoinBalance;
    var metaCoinEthBalance;

    return MetaCoin.deployed().then(function(instance) {
      meta = instance;
      return meta.getBalance.call(accounts[0]);
    }).then(function(outCoinBalance) {
      metaCoinBalance = outCoinBalance.toNumber();
      return meta.getBalanceInEth.call(accounts[0]);
    }).then(function(outCoinBalanceEth) {
      metaCoinEthBalance = outCoinBalanceEth.toNumber();
    }).then(function() {
      assert.equal(metaCoinEthBalance, 2 * metaCoinBalance, "Library function returned unexpected function, linkage may be broken");
    });
  });
  it("should send coin correctly", function() {
    var meta;

    // Get initial balances of first and second account.
    var account_one = accounts[0];
    var account_two = accounts[1];

    var account_one_starting_balance;
    var account_two_starting_balance;
    var account_one_ending_balance;
    var account_two_ending_balance;

    var amount = 10;

    return MetaCoin.deployed().then(function(instance) {
      meta = instance;
      return meta.getBalance.call(account_one);
    }).then(function(balance) {
      account_one_starting_balance = balance.toNumber();
      return meta.getBalance.call(account_two);
    }).then(function(balance) {
      account_two_starting_balance = balance.toNumber();
      return meta.sendCoin(account_two, amount, {from: account_one});
    }).then(function() {
      return meta.getBalance.call(account_one);
    }).then(function(balance) {
      account_one_ending_balance = balance.toNumber();
      return meta.getBalance.call(account_two);
    }).then(function(balance) {
      account_two_ending_balance = balance.toNumber();

      assert.equal(account_one_ending_balance, account_one_starting_balance - amount, "Amount wasn't correctly taken from the sender");
      assert.equal(account_two_ending_balance, account_two_starting_balance + amount, "Amount wasn't correctly sent to the receiver");
    });
  });
});

测试将输出下面的内容:

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


  3 passing (293ms)

 使用 async/await

这有一个类似的例子, 但是使用的是 async/await 语法.

const MetaCoin = artifacts.require("MetaCoin");

contract('2nd MetaCoin test', async (accounts) => {

  it("should put 10000 MetaCoin in the first account", async () => {
     let instance = await MetaCoin.deployed();
     let balance = await instance.getBalance.call(accounts[0]);
     assert.equal(balance.valueOf(), 10000);
  })

  it("should call a function that depends on a linked library", async () => {
    let meta = await MetaCoin.deployed();
    let outCoinBalance = await meta.getBalance.call(accounts[0]);
    let metaCoinBalance = outCoinBalance.toNumber();
    let outCoinBalanceEth = await meta.getBalanceInEth.call(accounts[0]);
    let metaCoinEthBalance = outCoinBalanceEth.toNumber();
    assert.equal(metaCoinEthBalance, 2 * metaCoinBalance);
  });

  it("should send coin correctly", async () => {

    // Get initial balances of first and second account.
    let account_one = accounts[0];
    let account_two = accounts[1];

    let amount = 10;


    let instance = await MetaCoin.deployed();
    let meta = instance;

    let balance = await meta.getBalance.call(account_one);
    let account_one_starting_balance = balance.toNumber();

    balance = await meta.getBalance.call(account_two);
    let account_two_starting_balance = balance.toNumber();
    await meta.sendCoin(account_two, amount, {from: account_one});

    balance = await meta.getBalance.call(account_one);
    let account_one_ending_balance = balance.toNumber();

    balance = await meta.getBalance.call(account_two);
    let account_two_ending_balance = balance.toNumber();

    assert.equal(account_one_ending_balance, account_one_starting_balance - amount, "Amount wasn't correctly taken from the sender");
    assert.equal(account_two_ending_balance, account_two_starting_balance + amount, "Amount wasn't correctly sent to the receiver");
  });

});

这个例子将会输出和上面一样的内容.

 指定测试文件

可以直接指定某个文件来运行:

$ truffle test ./test/metacoin.js

参考 命令行 部分了解详细内容.

 进阶部分

Truffle 支持通过修改 Mocha 配置来更改 Mocha 的运行机制. 参考 项目配置 部分了解更多.