Dappsでゲームを作ってみる(Part.3) コントラクトのテストを書いてみる

木内智史之介(シャッチョー)
ミンカさんけっこんしてくださいおねがいします(ズザー SEGAさん、DIVAの筐体ください(ズザー

前回まで

前回は、定義したコントラクトのデプロイを行いました。
実は並行してテストも書いたりしていたので、そちらも記事にしたいと思います。

テストとは?

今更、僕が説明するまでもないですよね?(笑)

今時珍しいかもしれませんが、「テストなんかめんどーだから書かねーぜ」なんて人もいるかもしれませんが、 殊、Dapps開発においては、テストは必須です。

もし、テストを書かない場合、常にデプロイして、動作確認をしないとなりません。
何より、dappsはコントラクトの変更時、どうしてもアドレスが変わってしまいます。

非常に面倒ですので、面倒を回避するためにもテストを書きましょう。

テストの作成

truffle はテストの作成もコマンドを通して行う事ができます。

$ truffle create test BoxFactory

デフォルトで作成される中身は、こんな感じです。

test/box_factory.js
contract('BoxFactory', function(accounts) {
it("should assert true", function(done) {
var box_factory = BoxFactory.deployed();
assert.isTrue(true);
done();
});
});

BoxFactoryがなんの require もなしに記述されているので、このままではもちろん動作しません。
最低限の変更をおこない、testを実行してみましょう。

test/box_factory.js
+ const BoxFactory = artifacts.require("BoxFactory");
+
contract('BoxFactory', function(accounts) {
it("should assert true", function(done) {
var box_factory = BoxFactory.deployed();
assert.isTrue(true);
done();
});
});
$ truffle test
Using network 'development'.
Compiling ./contracts/BoxFactory.sol...
Compiling ./contracts/Migrations.sol...
Contract: BoxFactory
✓ should assert true
1 passing (242ms)

truetrueかどうか判定しているだけなので、当然passします(笑)

パッと見た感じ、テスティングフレームワークは、chai が使われているようですね。

テストを書く

ブログの順序だと、

  1. コントラクトを書く
  2. コントラクトをデプロイする
  3. コントラクトのテストを書く

という順序になってしまいましたが、実際の時間軸としては、

  1. コントラクトのテストを書く && コントラクトを書く
  2. コントラクトをデプロイする

です。

テストのないプロダクトを、ネットワーク上にデプロイするなんて、無謀もいいところですよね ((((;゚Д゚))))

というわけで、テストを書く

test/box_factory.js
// エラーが発生する事を期待するアサーションがないので、
// https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/test/helpers/expectThrow.js
// から拝借してきました
// OpenZeppelinは偉大やで、ほんまに...(土下座
import { expectThrow } from "./helpers/expectThrow";
const BoxFactory = artifacts.require("BoxFactory");
// テスト対象のコントラクトを指定する
// requireまで自動でやってくれてもいいような気はするが...
// 引数の関数では、アカウントの一覧が渡ってくるので、そのアカウントを必要に応じてテストで使用します
contract("BoxFactory", (accounts) => {
it("Boxを作成できる", async () => {
const instance = await BoxFactory.deployed();
const account = accounts[0];
// solidity側で定義した処理を呼びだしています
// 本当に簡単に呼び出せる事に感動
// 非同期処理になるので、適宜awaitでレスポンスを待つ必要性があります
let count = await instance.getBoxCount();
assert.equal(count.valueOf(), 0);
await instance.createBox(parseInt("FFFFFF", 16), 0, 0);
count = await instance.getBoxCount();
assert.equal(count.valueOf(), 1);
});
it("同じ座標に同じ人が作る事はできない", async () => {
const instance = await BoxFactory.deployed();
const account = accounts[1];
// {from: account} の指定を行わない場合、accounts[0]がデフォルトで使用されるようです
let count = await instance.getBoxCount({from: account});
await instance.createBox(parseInt("FFFFFF", 16), 0, 0, {from: account});
await expectThrow(instance.createBox(parseInt("FFFFFF", 16), 0, 0, {from: account}));
count = await instance.getBoxCount({from: account});
assert.equal(count.valueOf(), 1);
});
it("自分だけのBoxを取得する事ができる", async () => {
const instance = await BoxFactory.deployed();
const account = accounts[2];
await instance.createBox(parseInt("FFFFFF", 16), 0, 0, {from: account});
await instance.createBox(parseInt("FFFFFF", 16), 0, 1, {from: account});
await instance.createBox(parseInt("FFFFFF", 16), 0, 2, {from: account});
const result = await instance.getBoxes({from: account});
assert.equal(result.valueOf().length, 4); // owners, colors, x, y
// solidityが構造体を返却する事ができないので、こんなレスポンスになる...
// これは将来なんとかなってほしい項目ですよね...
const [owners, colors, x, y] = result;
for (const owner of owners) {
assert.equal(owner.valueOf(), account);
}
});
});

ひとまず、こんな感じでよろしいかなと。

さあ、実行してみましょう!

$ truffle test
Using network 'development'.
Contract: BoxFactory
✓ Boxを作成できる (759ms)
✓ 同じ座標に同じ人が作る事はできない (233ms)
✓ 自分だけのBoxを取得する事ができる (367ms)
3 passing (2s)

パーペキです…!

テストにあたってのMigrationに関して

テストの実行時、solidity側に変更があった場合、自動的にコンパイルされ、テスト用にデプロイまで行われるようです。
なので、テストの実行時、いちいちマイグレーションを実行する必要性はありません。

振り返り

truffleでは、テストの実行環境も躓くことなく実行できるので、非常に心強いですね。
早くもこんなに素晴らしいフレームワークが登場している事に感動を覚えます。

次回は、testnet上へのデプロイを試してみようと思います。