8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

はじめに

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

Truffleのテストフレームワーク

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

$ truffle test

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

$ truffle test ./path/to/your/test/file.js

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

.solによるテスト

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

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に以下の関数を追加する。

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始まりではなくすると

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)

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

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

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

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と同じ。

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

8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?