Dappsでゲームを作ってみる(Part.3) コントラクトのテストを書いてみる
前回まで
前回は、定義したコントラクトのデプロイを行いました。
実は並行してテストも書いたりしていたので、そちらも記事にしたいと思います。
テストとは?
今更、僕が説明するまでもないですよね?(笑)
今時珍しいかもしれませんが、「テストなんかめんどーだから書かねーぜ」なんて人もいるかもしれませんが、 殊、Dapps開発においては、テストは必須です。
もし、テストを書かない場合、常にデプロイして、動作確認をしないとなりません。
何より、dappsはコントラクトの変更時、どうしてもアドレスが変わってしまいます。
非常に面倒ですので、面倒を回避するためにもテストを書きましょう。
テストの作成
truffle
はテストの作成もコマンドを通して行う事ができます。
$ truffle create test BoxFactory
デフォルトで作成される中身は、こんな感じです。
contract('BoxFactory', function(accounts) {
it("should assert true", function(done) {
var box_factory = BoxFactory.deployed();
assert.isTrue(true);
done();
});
});
BoxFactoryがなんの require
もなしに記述されているので、このままではもちろん動作しません。
最低限の変更をおこない、testを実行してみましょう。
+ 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)
true
をtrue
かどうか判定しているだけなので、当然passします(笑)
パッと見た感じ、テスティングフレームワークは、chai
が使われているようですね。
テストを書く
ブログの順序だと、
- コントラクトを書く
- コントラクトをデプロイする
- コントラクトのテストを書く
という順序になってしまいましたが、実際の時間軸としては、
- コントラクトのテストを書く && コントラクトを書く
- コントラクトをデプロイする
です。
テストのないプロダクトを、ネットワーク上にデプロイするなんて、無謀もいいところですよね ((((;゚Д゚))))
というわけで、テストを書く
// エラーが発生する事を期待するアサーションがないので、
// 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上へのデプロイを試してみようと思います。