【Ethereum】Truffleによるコントラクトのテスト

# はじめに
スマートコントラクトは、一度本番環境にデプロイしてしまうと修正ができないため、リリースまでに入念なテストが必要になる
Truffleにはコントラクトのテストを自動化する仕組みがあり、これを活用したい

# Truffleのテストフレームワーク
Truffleのフレームワークでは、javascriptによるテストと、solidityによるテストの2通りがある
テストをどの言語で書くかが違うだけで、実施できるテストの内容に違いはないようだ
どちらも`./test`フォルダに格納されており、

```
$ truffle test
```
ですべてのテストを実行できる。
どれかひとつのテストのみ実施したいときは、

```
$ truffle test ./path/to/your/test/file.js
```
と実行する。
それまでの開発で、ローカルネットワーク上にコントラクトをデプロイしたり、いろんなコマンドを試したとしても、Truffleでのテストは、まっさらな環境で実施され、それまでの影響を全く受けずにテストできる。

## `.sol`によるテスト
前回の学習で使ったMetacointプロジェクトを使って、どのようにテストを実施しているかを確認する。
テスト用コントラクトの内容は以下。

```solidity:test/TestMetacoin.sol
pragma solidity ^0.4.2;

import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/MetaCoin.sol";

contract TestMetacoin {

  function testInitialBalanceUsingDeployedContract() public {
    MetaCoin meta = MetaCoin(DeployedAddresses.MetaCoin());

    uint expected = 10000;

    Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially");
  }

  function testInitialBalanceWithNewMetaCoin() public {
    MetaCoin meta = new MetaCoin();

    uint expected = 10000;

    Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially");
  }

}
```
読んでみると、`function testInitialBalanceUsingDeployedContract()`では、すでにデプロイされたMetaCoinコントラクトを取得し、MetaCoinオーナーの残高が、`10000 Metacoin`であるかをテストしている。
`function testInitialBalanceWithNewMetaCoin()`では、デプロイ済みのコントラクトではなく、関数内でコントラクトを`new`し、同じくオーナーの残高が`10000 Metacoin`であるかをテストしている。

### テストの実行

実行すると以下の情報が出力される

```
$ truffle test test/TestMetacoin.sol 
Using network 'development'.

Compiling ./contracts/ConvertLib.sol...
Compiling ./contracts/MetaCoin.sol...
Compiling ./test/TestMetacoin.sol...
Compiling truffle/Assert.sol...
Compiling truffle/DeployedAddresses.sol...


  TestMetacoin
    ✓ testInitialBalanceUsingDeployedContract (124ms)
    ✓ testInitialBalanceWithNewMetaCoin (146ms)


  2 passing (1s)
```
両方ともpassしていることが確認できる。このテストにより、MetaCoinコントラクトの`constructor()`にて、`10000 Metacoin`をオーナー付与していること、また、`function getBalance(address addr)`にて、指定アドレスのMetacoin残高が取得できていることが確認できる。
わざと、`testInitialBalanceWithNewMetaCoin()`の`expected`を20000に変えてみる。

```
  function testInitialBalanceWithNewMetaCoin() public {
    MetaCoin meta = new MetaCoin();

    uint expected = 20000; // 20000に変更.

    Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially");
  }
```
出力は以下のように変わり、`testInitialBalanceWithNewMetaCoin`のテストに失敗していることが確認できる。

```
$ truffle test test/TestMetacoin.sol 
Using network 'development'.

Compiling ./contracts/ConvertLib.sol...
Compiling ./contracts/MetaCoin.sol...
Compiling ./test/TestMetacoin.sol...
Compiling truffle/Assert.sol...
Compiling truffle/DeployedAddresses.sol...


  TestMetacoin
    ✓ testInitialBalanceUsingDeployedContract (116ms)
    1) testInitialBalanceWithNewMetaCoin

    Events emitted during test:
    ---------------------------

    TestEvent(result: <indexed>, message: Owner should have 10000 MetaCoin initially (Tested: 10000, Against: 20000))

    ---------------------------


  1 passing (2s)
  1 failing

  1) TestMetacoin
       testInitialBalanceWithNewMetaCoin:
     Error: Owner should have 10000 MetaCoin initially (Tested: 10000, Against: 20000)
      at /usr/local/lib/node_modules/truffle/build/webpack:/packages/truffle-core/lib/testing/soliditytest.js:61:1
      at Array.forEach (<anonymous>)
      at processResult (/usr/local/lib/node_modules/truffle/build/webpack:/packages/truffle-core/lib/testing/soliditytest.js:59:1)
      at process._tickCallback (internal/process/next_tick.js:68:7)
```

### テスト関数の追加
ここに、`getBalanceInEth`のテストを追加してみる
テストの追加にはルールがある

- テストコントラクトの名前は`Test`で始まること
- テスト関数の名前は`test`で始まること

試しに、`contract TestMetacoin`に以下の関数を追加する。

```solidity:test/TestMetacoin.sol
contract TestMetacoin {
  :
  :
  function testgetBalanceInEth() public {
    MetaCoin meta = new MetaCoin();

    uint expected = 20000;

    Assert.equal(meta.getBalanceInEth(tx.origin), expected, "Owner should have 20000 Eth initially");    
  }
}
```
`1 Metacoin = 2 Eth`という仕様なので、20000 Ethを期待値としている。
実行結果は以下。

```
$ truffle test test/TestMetacoin.sol 
Using network 'development'.

Compiling ./contracts/ConvertLib.sol...
Compiling ./contracts/MetaCoin.sol...
Compiling ./test/TestMetacoin.sol...
Compiling truffle/Assert.sol...
Compiling truffle/DeployedAddresses.sol...


  TestMetacoin
    ✓ testInitialBalanceUsingDeployedContract (102ms)
    ✓ testInitialBalanceWithNewMetaCoin (132ms)
    ✓ testgetBalanceInEth (95ms)


  3 passing (1s)

```
追加した`testgetBalanceInEth`が実行され、passしたことが確認できた。
ルールに従わず、関数名を`test`始まりではなくすると

```solidity:test/TestMetacoin.sol
  function examgetBalanceInEth() public {
    MetaCoin meta = new MetaCoin();

    uint expected = 20000;

    Assert.equal(meta.getBalanceInEth(tx.origin), expected, "Owner should have 20000 Eth initially");    
  }
```

```
$ truffle test test/TestMetacoin.sol 
Using network 'development'.

Compiling ./contracts/ConvertLib.sol...
Compiling ./contracts/MetaCoin.sol...
Compiling ./test/TestMetacoin.sol...
Compiling truffle/Assert.sol...
Compiling truffle/DeployedAddresses.sol...


  TestMetacoin
    ✓ testInitialBalanceUsingDeployedContract (141ms)
    ✓ testInitialBalanceWithNewMetaCoin (116ms)


  2 passing (1s)

```
追加された関数は実行されない。

### テストコントラクトの追加

次に、テスト用のコントラクトを追加してみる。
関数の内容は上記と同じにした。

```solidity:test/TestMetacoin.sol
contract TestConvertLibrary {

  function testgetBalanceInEth() public {

    MetaCoin meta = new MetaCoin();

    uint expected = 20000;

    Assert.equal(meta.getBalanceInEth(tx.origin), expected, "Owner should have 20000 Eth initially");    
  }
}
```
実行する

```
$ truffle test test/TestMetacoin.sol 
Using network 'development'.

Compiling ./contracts/ConvertLib.sol...
Compiling ./contracts/MetaCoin.sol...
Compiling ./test/TestMetacoin.sol...
Compiling truffle/Assert.sol...
Compiling truffle/DeployedAddresses.sol...


  TestMetacoin
    ✓ testInitialBalanceUsingDeployedContract (108ms)
    ✓ testInitialBalanceWithNewMetaCoin (135ms)


  2 passing (1s)
```
実行されない。
調べてみると、Truffleでは、ファイル名とコントラクト名が一致していなければならない。
そこで、コントラクト名と名前を一致させた別ファイルを追加した。
ファイルの中身は、コントラクト以外は`TestMetacoin.sol`と同じ。

```solidity:test/TestConvertLibrary.sol
pragma solidity ^0.4.2;

import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/MetaCoin.sol";

contract TestConvertLibrary {

  function testgetBalanceInEth() public {

    MetaCoin meta = new MetaCoin();

    uint expected = 20000;

    Assert.equal(meta.getBalanceInEth(tx.origin), expected, "Owner should have 20000 Eth initially");    
  }
}
```
実行すると無事にテストが実行された。

```
$ truffle test test/TestConvertLibrary.sol 
Using network 'development'.

Compiling ./contracts/ConvertLib.sol...
Compiling ./contracts/MetaCoin.sol...
Compiling ./test/TestConvertLibrary.sol...
Compiling truffle/Assert.sol...
Compiling truffle/DeployedAddresses.sol...


  TestConvertLibrary
    ✓ testgetBalanceInEth (103ms)


  1 passing (954ms)
```
追加したテストがpassしていることが確認できる。


# 最後に
Truffleフレームワークでテスト関数およびテストコントラクトの追加と、それらの実行を試した。
いくつか把握しておかないといけないルールはあるが、簡単にテストの追加と動作確認ができる。
今回は能動的な関数の試験しか試せなかったが、Eth受信時の処理などの受動的な関数の動作テストの方法はまた後日。

# 宿題
- [ ] Eth受信時処理の動作確認テストのやり方

# 参考文献
[Truffle Suite | TESTING YOUR CONTRACTS](https://truffleframework.com/docs/truffle/testing/testing-your-contracts)