はじめに
スマートコントラクトは、一度本番環境にデプロイしてしまうと修正ができないため、リリースまでに入念なテストが必要になる
Truffleにはコントラクトのテストを自動化する仕組みがあり、これを活用したい
Truffleのテストフレームワーク
Truffleのフレームワークでは、javascriptによるテストと、solidityによるテストの2通りがある
テストをどの言語で書くかが違うだけで、実施できるテストの内容に違いはないようだ
どちらも./test
フォルダに格納されており、
$ truffle test
ですべてのテストを実行できる。
どれかひとつのテストのみ実施したいときは、
$ truffle test ./path/to/your/test/file.js
と実行する。
それまでの開発で、ローカルネットワーク上にコントラクトをデプロイしたり、いろんなコマンドを試したとしても、Truffleでのテストは、まっさらな環境で実施され、それまでの影響を全く受けずにテストできる。
.sol
によるテスト
前回の学習で使ったMetacointプロジェクトを使って、どのようにテストを実施しているかを確認する。
テスト用コントラクトの内容は以下。
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
に以下の関数を追加する。
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
始まりではなくすると
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)
追加された関数は実行されない。
テストコントラクトの追加
次に、テスト用のコントラクトを追加してみる。
関数の内容は上記と同じにした。
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
と同じ。
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受信時処理の動作確認テストのやり方