用 JavaScript 写测试用例

TronBox 使用 Mocha测试框架和 Chai进行断言,为你提供了一个可靠的框架来编写你的 JavaScript 测试。让我们深入了解 TronBox 如何在 Mocha 基础上方便测试合约。

注意:如果你不熟悉在 Mocha 中编写单元测试,请在继续之前查看 Mocha 文档

使用 contract() 而不是 describe()

在结构上,单元测试应该与 Mocha 的测试基本保持不变:测试文件应该存在于 ./test 目录中,它们应该以扩展名 .js 结尾,并且它们应该包含 Mocha 将识别为自动化测试的代码。TronBox 测试与 Mocha 测试的不同之处在于它的 contract() 功能。

  • 在运行每个单测的 contract() 函数之前,合约会重新部署到正在运行的波场网络中。因此,其中的测试过程都是在干净的合约状态下运行。
  • contract() 功能提供了当前波场网络可用的帐户列表,可以使用它来编写测试。

在测试中使用合约抽象

合约抽象(Contract abstraction)是通过 Javascript 与波场合约交互的基础。因为 TronBox 无法检测你在测试中需要与哪些合约进行交互,所以需要我们明确写出这些合约。你可以使用 TronBox 提供的 artifacts.require() 方法来执行此操作,该方法允许你为特定的 Solidity 合约请求一个可用的合约抽象。正如你将在下面的示例中看到的那样,可以使用此抽象来确保你的合约正常工作。

有关更多使用合约抽象的信息,请参阅 与合约进行交互 部分。

使用 artifacts.require()

在测试中使用 artifacts.require() 与在迁移中使用它的方式相同;你只需要传递合同的名称。有关详细用法,请参阅迁移部分中的 artifacts.require() 文档

使用 tronWarp

每个测试文件中都有一个 tronWarp 实例,可直接引用,就像 tronWrap.trx.getBalance(account) 就行了。

案例

使用 .then

这是MetaCoin TronBox Box中使用 async/await 表示法提供的示例测试。注意 contract() 函数的使用,使用 accounts 指定可用波场账户的数组,以及使用 artifacts.require() 来直接与合约交互。

文件: ./test/metacoin.js

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

contract('MetaCoin', accounts => {
  it('should put 10000 MetaCoin in the first account', () =>
    MetaCoin.deployed()
      .then(instance => instance.getBalance.call(accounts[0]))
      .then(balance => {
        assert.equal(balance.valueOf(), 10000, "10000 wasn't in the first account");
      }));

  it('should call a function that depends on a linked library', () => {
    let meta;
    let metaCoinBalance;
    let metaCoinConvertedBalance;

    return MetaCoin.deployed()
      .then(instance => {
        meta = instance;
        return meta.getBalance.call(accounts[0]);
      })
      .then(outCoinBalance => {
        metaCoinBalance = outCoinBalance.toNumber();
        return meta.getConvertedBalance.call(accounts[0]);
      })
      .then(outCoinConvertedBalance => {
        metaCoinConvertedBalance = outCoinConvertedBalance.toNumber();
      })
      .then(() => {
        assert.equal(metaCoinConvertedBalance, 2 * metaCoinBalance, 'Library function returned unexpected function, linkage may be broken');
      });
  });

  it('should send coin correctly', () => {
    let meta;

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

    let account_one_starting_balance;
    let account_two_starting_balance;
    let account_one_ending_balance;
    let account_two_ending_balance;

    const amount = 10;

    return MetaCoin.deployed()
      .then(instance => {
        meta = instance;
        return meta.getBalance.call(account_one);
      })
      .then(balance => {
        account_one_starting_balance = balance.toNumber();
        return meta.getBalance.call(account_two);
      })
      .then(balance => {
        account_two_starting_balance = balance.toNumber();
        return meta.sendCoin(account_two, amount, { from: account_one });
      })
      .then(() => meta.getBalance.call(account_one))
      .then(balance => {
        account_one_ending_balance = balance.toNumber();
        return meta.getBalance.call(account_two);
      })
      .then(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 outCoinConvertedBalance = await meta.getConvertedBalance.call(accounts[0]);
    let metaCoinConvertedBalance = outCoinConvertedBalance.toNumber();
    assert.equal(metaCoinConvertedBalance, 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");
  });
});

这个测试的输出与上一个例子相同。

特定测试

你可以单独运行 test 目录下的某一个特定文件,如下所示:

tronbox test ./test/metacoin.js

更多信息请参见完整的命令手册