TruffleによるDapp開発のテストについて
はじめに
- EthereumによるDapp開発では、Truffle、Ganacheといった開発ツールを利用できる。
- スマートコントラクトは、一度ブロックチェーンに取り込まれると変更できないため、テストを事前に実施すべきである。
- とは言っても、ETHの支払いや時間の進め方など、スマートコントラクトならではのテストをどのように書くべきかわからなかった。
- そこで今回は、Truffle Ganache環境における、スマートコントラクトのテスト方法について調べた内容を記載する。
- 動作確認バージョンは、以下の通り。
- Truffle : 4.1.14
- Ganache : 1.1.0
Solidityのテストコードで利用可能な言語
Truffleのドキュメントには、テストコードで利用可能な言語として以下を挙げている。
- Solidity
- 特徴は、依存ライブラリが必要なく、コントラクトのテストが可能なこと。
- Javascript
以降では、Javascriptによるテストコードの書き方について説明する。
テストコードの書き方
テスト対象のコード
ブロックチェーンの入門書でよくあるクラウドファンディングのコントラクトをテスト対象とする。 このコントラクトでは、以下の処理が行える。(セキュアでない処理があるがサンプルということで...)
- 資金の調達者(Owner)が、「目標金額(通貨:ETH)」と、「締め切り期日」を設定できる。
- Ownerが、期日までに援助者(Investor)から目標金額を獲得できたら、Ownerに全額資金を送付できる。
- Ownerが、期日までに援助者(Investor)から目標金額を獲得できなかったら、Investorに資金を返済できる。
CrowdFunding.sol
pragma solidity ^0.4.23; contract CrowdFunding { struct Investor { address addr; uint amount; } address public owner; uint public numInvestors; uint public deadline; string public status; bool public ended; uint public goalAmount; uint public totalAmount; mapping (uint => Investor) public investors; event Fund(address indexed investor, uint amount); event CheckGoalReached(address indexed owner); modifier onlyOwner () { require(msg.sender == owner); _; } constructor(uint _duration, uint _goalAmount) public { owner = msg.sender; deadline = now + _duration; goalAmount = _goalAmount; status = "Funding"; ended = false; numInvestors = 0; totalAmount = 0; } function fund() public payable { require(!ended); Investor storage inv = investors[numInvestors++]; inv.addr = msg.sender; inv.amount = msg.value; totalAmount += inv.amount; emit Fund(msg.sender, msg.value); } function checkGoalReached() public onlyOwner { require(!ended); require(now >= deadline); if(totalAmount >= goalAmount) { status = "Campaign Succeeded"; ended = true; if(!owner.send(address(this).balance)) { revert("Failed to send the balance to the owner"); } } else { uint i = 0; status = "Campaign Failed"; ended = true; while(i <= numInvestors) { if(!investors[i].addr.send(investors[i].amount)) { revert("Failed to send the balance to investors"); } i++; } } emit CheckGoalReached(msg.sender); } function kill() public onlyOwner { selfdestruct(owner); } }
1. 基本的なテスト
- mochaで一般的に使われるdescribe()の代わりにcontract()を使う。
- contractでは、addressの配列がパラメータとして与えられる。
- [owner, investor1, investor2]のようにテスト実行時に使われる各アドレスの役割を変数名にするとわかりやすい。
- artifacts.require()でテスト対象のコントラクトの型を宣言する。
- web3が利用可能なので、web3.eth.getBalanceといった処理が書ける。
- テストコードは非同期処理として記載するので async/awaitを利用するとネストが少なくすっきり書ける。
- chai-as-promised等使うとアサーションコードもすっきり書ける。
- Solidityのuint型は、javascript(web3.js)のBigNumber型として扱われるので、アサーションは、chai-bignumberを使うと簡潔に書ける。
const BigNumber = web3.BigNumber require('chai') .use(require('chai-as-promised')) .use(require('chai-bignumber')(BigNumber)) .should(); var CrowdFunding = artifacts.require("CrowdFunding"); contract('CrowdFunding', ([owner, investor1, investor2]) => { const DURATION = 1800; // 30 minutes const GOAL_AMOUNT = new web3.BigNumber(web3.toWei(1, 'ether')); let instance; beforeEach(async () => { instance = await CrowdFunding.new(DURATION, GOAL_AMOUNT, { from: owner }); }); it('should be Funding state initially', async () => { (await instance.status()).should.equal('Funding'); (await instance.ended()).should.be.false; }); });
2. 例外処理のテスト
- chai-as-promisedのrejectedWithを利用してerrorメッセージ文字列にrevertが含まれているかをチェックする。
it('should fail if checkGoalReaced is called before campaign end', async() => { instance.checkGoalReached({ from: owner }).should.be.rejectedWith('revert'); });
3. 時間を進めたい場合のテスト
- Ganacheに対して、jsonrpc経由で非標準メソッド
evm_increaseTime
を呼び出す。 - その他の非標準メソッドとして以下がある。
- evm_snapshot, evm_revert, evm_increaseTime, evm_mine
it('should fail if fund is called after the campaign end', async () => { const amount1 = new web3.BigNumber(web3.toWei(1, 'ether')); await instance.fund({ from: investor1, value: amount1 }); await increaseTime(duration.hours(1)); await instance.checkGoalReached({ from: owner }); instance.fund({ from: investor1, value: amount1 }).should.be.rejectedWith('revert'); });
increaseTime.js
export const duration = { seconds: function (val) { return val; }, minutes: function (val) { return val * this.seconds(60); }, hours: function (val) { return val * this.minutes(60); }, days: function (val) { return val * this.hours(24); }, weeks: function (val) { return val * this.days(7); }, years: function (val) { return val * this.days(365); }, }; export function increaseTime(duration) { return new Promise((resolve, reject) => { web3.currentProvider.sendAsync({ jsonrpc: '2.0', method: 'evm_increaseTime', params: [duration], id: Date.now(), }, err => { if (err) return reject(err) resolve() }) }) };
4. Eventのテスト
- 原因不明であるが、私の環境では、初回呼び出しのみイベントが2回発火されてしまう。
- event.watchの結果を確認するよりも、event.getで呼ばれた回数を確認する方法をとっている。
it('should fire an event after calling fund', async () => { const amount1 = new web3.BigNumber(web3.toWei(0.1, 'ether')); const event = instance.Fund({}, {fromBlock: 0, toBlock: 'latest'}); // event.watch(function (error, result) { // if (!error) { // console.log(result.args); // sometimes an event is called twice // result.args['investor'].should.equal(investor1); // result.args['amount'].should.be.bignumber.equal(amount1); // } else { // assert.fail('error occured'); // } // }); await instance.fund({ from: investor1, value: amount1 }); await instance.fund({ from: investor2, value: amount1 }); await instance.fund({ from: investor1, value: amount1 }); assert.equal(3, event.get().length) });
5. ETH支払のテスト
it('should success the campaign if totalAmount is reached by deadline, then send balance to owner', async () => { const amount1 = new web3.BigNumber(web3.toWei(1, 'ether')); const pre = web3.eth.getBalance(owner); await instance.fund({ from: investor1, value: amount1 }); await increaseTime(duration.hours(1)); const res = await instance.checkGoalReached({ from: owner }); const gasCost = getTransactionGasCost(res['tx']); const post = web3.eth.getBalance(owner); post.minus(pre).plus(gasCost).should.be.bignumber.equal(amount1); (await instance.status()).should.be.equal("Campaign Succeeded"); (await instance.ended()).should.be.true; (await web3.eth.getBalance(instance.address)).should.bignumber.equal(0); });
export function getTransactionGasCost(tx) { const transaction = web3.eth.getTransactionReceipt(tx); const amount = transaction.gasUsed; const price = web3.eth.getTransaction(tx).gasPrice; return new web3.BigNumber(price * amount); }
ソースコード
今回のサンプルコードは、Githubに格納している。
参考
- Truffle TESTING
- Mocha
- Chai
- chai-as-promised
- chai-bignumber
- Ganache
- web3.js
- Solidityでの時間表現の注意すべきポイントとそのテスト方法
//
My Cheat
Markdown
Markdown Sample - Trial and error
Slack
Action | Command |
---|---|
クイックスイッチャー | Command + K |
スレッド画面を開く | Command + Shift + T |
右サイトバーを開く/閉じる | Command + , |
アクティビティ@ | Command + Shift + M |
スター付きアイテムを表示 | Command + Shift + S |
現在開いているチャンネルや会話の中で検索 | Command + F |
メッセージを未読にする | Option + Click the message |
自分の最後のメッセージを編集 | Command + ↑ |
Google Docs
Action | Command |
---|---|
箇条書き | Command + Shift + 8 |
tmux
https://qiita.com/vintersnow/items/be4b29652ff665c45198
Vim
Category | Function | Command |
---|---|---|
基本 | 編集中のファイルの場所をカレントディレクトリにする | :cd %:h |
基本 | インデント挿入 | >> |
基本 | インデント削除 | << |
基本 | インデント挿入(インサートモード) | Ctrl + t |
基本 | インデント削除(インサートモード) | Ctrl + d |
折りたたみ | 折りたたみの有効無効きりかえ | zi |
折りたたみの作成 | zf | |
折りたたみの開け閉め | za | |
折りたたみを削除 | zd | |
置換 | 構文 | :[範囲]S/[置換前文字列]/[置換後文字列]/[オプション] |
ページ全体を確認ありで置換 | :%s/from/to/gc | |
インデント | 現在行のインデント | == |
指定範囲のインデント | =[範囲] | |
現在行を左にインデント | > | |
現在行を右にインデント | < | |
Surround Vim | 文を囲んでいる'を消す | ds' (delete surround) |
'で囲まれた部分を消す | di' (delete inside) | |
'を"に変更 | cs'" (change surround) | |
'で囲まれた部分を消して、インサートモードに入る | ci' (change inside) | |
ビジュアルモードで選択した部分を'で囲む | S' | |
文を'で囲む | yss' (yank surround sentence) | |
カーソルがある単語を'で囲む | ysiw' (yank surround inner word) | |
マイカスタマイズ | インサートモードで移動 | Ctrl + j, k, h, l |
最後に編集された位置に移動 | gb | |
カーソル位置の単語をyank | vy | |
ビジュアルモードで行末まで選択 | v | |
対応する括弧に移動 | ( | |
NERDTreeオンオフ | Ctrl + e | |
NERDTree 隠しファイル表示 | I | |
NERDTree ファイル追加 | m | |
NERDTree 親ディレクトリに移動 | u | |
NERDTree ヘルプ | ? | |
Unite 全部乗せ | ,fu | |
Unite ファイル一覧 | ,ff | |
Unite バッファ一覧 | ,fb | |
Unite 常用セット | ,fu | |
Unite 最近使用したファイル一覧 | ,fm | |
Unite 現在のバッファのカレントディレクトリからファイル一覧 | ,fd | |
カーソル下のURLをブラウザで開く | ,o | |
カーソル下の語をググる | ,g | |
選択した文字列を検索 | // | |
選択した文字列を置換 | /r | |
ヘルプ | Ctrl + i | |
カーソル下のキーワードをヘルプでひく | Ctrl + i, Ctrl + i | |
XMLファイルをフォーマットする | :Formatxml | |
.vimrcの編集 | :Ev | |
.vimrcの反映 | :Rv | |
指定文字コードでファイルを開く | :Cp932, :Eucjp, :Iso2022jp, :Utf8, :Jis, :Sjis |
Vimの使い方 よく使うコマンドまとめ | Memo on the Web
easy-align-plugin
1.キー 2. オプション: Enterキーを押すごとにどちらに揃えるか (left, right,or center) 3. オプション: 何番目のデリミタか (default: 1) * 1: 1番目のデリミタ * 2: 2番目のデリミタ * *: すべてのデリミタ * **: すべてのデリミタ (左揃え、右揃えを交互に) * -: 後ろから数えて1番目のデリミタ(-1) * -2: 最後から数えて2番目のデリミタ 4. デリミタキー ( , =, :, ., |, &, #, ,)
- 1番目の [space] で左揃え [Enter][space]
Paul McCartney 1942 May George Harrison 1943 April Ringo Starr 1940 June Pete Best 1941 January
- 2番目の[space]で左揃え [Enter]2[space]
Paul McCartney 1942 May George Harrison 1943 April Ringo Starr 1940 June Pete Best 1941 January
- すべての[space]で左揃え [Enter]*[space]
Paul McCartney 1942 May George Harrison 1943 April Ringo Starr 1940 June Pete Best 1941 January
- すべての[space]で左揃え右揃えを交互に [Enter]**[space]
Paul McCartney 1942 May George Harrison 1943 April Ringo Starr 1940 June Pete Best 1941 January
- 後ろから2番目の[space]で右揃え [Enter][Enter]-2[space]
Paul McCartney 1942 May George Harrison 1943 April Ringo Starr 1940 June Pete Best 1941 January
-すべての[space]で中央揃え [Enter][Enter][Enter]*[space]
Paul McCartney 1942 May George Harrison 1943 April Ringo Starr 1940 June Pete Best 1941 January
Junit
git
Command | Description | Example | Note |
---|---|---|---|
git reset | 直前のコミットを取り消す | git reset --soft HEAD^ | 作業ツリーに加えた変更点まで取り消したい場合は--hard |
git revert | 作業ツリーを指定したコミット時点の状態に戻す | git revert <コミット名> | git resetと違い作業ツリーを差し戻した情報が作業履歴に残る |
git stash | 現在の作業ツリーの状態を一時的に保管する | git stash <保存名もしくはコメントなど> | 作業ツリーを再度呼び出すには git stash pop, 一時保存されている作業ツリー一覧を見るには git stash list |
git rebase | ブランチの派生元(上流)を変更する | git rebase <派生元ブランチ> |
IntelliJ IDEA
Function | Command(Mac) | Command(Windows) |
---|---|---|
ビューを開く(*1) | Command + #[0-9] | Alt + #[0-9] |
設定ダイアログ | Command + , | Ctrl + Alt + S |
プロジェクト構造ダイアログ | Command + ; | Ctrl + Alt + Shift + S |
アクションを探す | Command + Shift + A | Ctrl + Shift + A |
どこでも検索 | Double Shift | Double Shift |
最近のファイルをポップアップ | Command + E | Ctrl + E |
クラスに移動 | Command + O | Ctrl + N |
ファイルに移動 | Command + Shift + O | Ctrl + Shift + N |
宣言に移動 | Command + B | Ctrl + B |
ファイル構造ポップアップ | Command + F12 | Ctrl + F12 |
呼び出し階層 | Ctrl + Option + H | Ctrl + Alt + H |
エディタ閉じる | Command + W | |
実行/デバッグ | Ctrl + R/D | Shift + F10/F9 |
コードブロックを展開/折りたため | Command + +/- | Ctrl + +/- |
すべて展開 | Command + Shift + + | Ctrl + Shift + + |
すべて折りたたむ | Command + Shift + - | Ctrl + Shift + - |
インポート文の整理 | Ctrl + Option + O | Ctrl + Alt + O |
フォーマット | Command + Alt + L | Ctrl + Alt + L |
戻る | Command + Alt + ← | Ctrl + Alt + ← |
進む | Command + Alt + → | Ctrl + Alt + → |
(*1)
- 1: Project
- 2: Favorite
- 3: Find
- 4: Run
- 5: Debug
- 6: ToDo
- 7: Structure
- 8: Version Control
- Option + F12: Terminal
- F12: まえのウィンドウに戻る
- Esc: エディタに移動
Eclipse
Function | Command(Mac) | Command(Windows) |
---|---|---|
クイックフィックス | command + 1 | Ctrl + 1 |
コメントアウト(Java, Javascript) | command + / | Ctrl + / |
コメントアウト(xml, html, css) | command + Shift + C | Ctrl + Shift + C |
クイックアクセス | command + 3 | Ctrl + 3 |
インデントの訂正 | command + 1 | Ctrl + 1 |
インポートの編成 | shift + command + O | Ctrl + Shift + O |
1行下へ移動 | option + Down | Alt + Down |
宣言を開く | F3 | F3 |
型階層を開く | F4 | F4 |
名前を指定してファイルを開く | command + Shift + R | Ctrl + Shift + R |
Javaのタイプ名を指定してファイルを開く | command + Shift + T | Ctrl + Shift + T |
ヒストリーを戻る | command + [ | Alt + Left |
ヒストリーを進む | command + ] | Alt + Right |
新規ファイル | command + N | Ctrl + N |
ファイルを閉じる | command + W | Ctrl + W |
ファイルをすべて閉じる | command + Shift + W | Ctrl + Shift + W |
次のビュー | command + F7 | Ctrl + F7 |
次のエディター | command + F6 | Ctrl + F6 |
次のパースペクティブ | command + F8 | Ctrl + F8 |
実行(RUN) | shift + command + F11 | Ctrl + F11 |
デバッグ(Debug) | command + F11 | F11 |
Maven Update Project | option+ F5 | |
TM Terminalを開く | Crl + Alt + T | Crl + Alt + T |
Vrapper
Function | Command |
---|---|
スクロール(Up) | |
スクロール(Down) | |
対応する括弧に移動 | ] or [ |
左タブへ移動 | gr |
右タブへ移動 | gt |
次のメンバ(フィールド、メソッド) | mm |
前のメンバ(フィールド、メソッド) | mp |
tig
Function | Command |
---|---|
add | ファイルごと u |
commit | stasu viewでC |
コミット毎の差分 | main viewでd |
ステータス | main viewでS |
ブランチをみる | main viewでH |
ヘルプをみる | main viewでh |
Presentation 素材
PoEAA 0.Introduction
Patterns of Enterprise Application Architecture (Addison-Wesley Signature Series (Fowler))
- 作者: Martin Fowler
- 出版社/メーカー: Addison-Wesley Professional
- 発売日: 2002/11/05
- メディア: ハードカバー
- 購入: 4人 クリック: 36回
- この商品を含むブログ (41件) を見る
Architecture
アーキテクチャとは
- システムの主要な部品と、その間の相互関係
- 後になって変更することが難しいような決定事項 (「変更することが難しい」は主観的で変わり得る)
Enterprise Applications
Enterprise Applicationは以下の特徴をもつ
- 永続的データを扱う
- データ量が多い
- データは並列にアクセスされる
- 大量のユーザーインターフェース画面がある
- 別のエンタープライズアプリケーションと連携する
- (別のエンタープライズアプリケーションとの)データの概念的不協和が存在する
- 複雑なビジネス非論理性を扱う
Kind of Enterprise Application
以下の3種類を代表例として考える
- B2Cオンライン小売業(ロジックは簡潔だが、処理量大)
- 賃貸契約の自動化システム(処理量小だが、ロジックが複雑)
- 小規模企業の費用追跡システム(将来の拡張性が重要)
それぞれ異なる困難性を持ち、全てに適用可能な唯一のアーキテクチャというものはない
Thinking About Performance
- アーキテクチャに関する決定の多くは性能に関わるものである
- 原則としてパフォーマンス最適化は計測に基づくべきだが、性能に影響するアーキテクチャ上の決定は後半の最適化では修正が難しい場合がある
- いくつかのガイドラインは提供するが、やっぱり計測に基づくべき
- コンフィギュレーションが変わったら、やっぱり計測すべき
- 設計上の決定が性能指標のすべてに同じように作用するとは限らない
- エンタープライズシステムでは、キャパシティや効率性よりスケーラビリティが重要
- 用語は以下のように定義する
用語 | 定義 |
---|---|
応答時間 | 外部からの要求を処理するのにかかった時間 |
応答性 | 要求にたいしてどの程度すばやく受付応答するか |
レイテンシ | なんらかの応答を得るまでの最小時間 |
スループット | 一定時間内処理量 |
ロード | システムがどの程度負荷がかかった状態にあるか |
ロードセンシティビティ | ロードによりどの程度応答時間が変化するか |
効率性 | 性能 / リソース |
キャパシティ | 最大の有効スループットまたはロード |
スケーラビリティ | リソースの追加が性能に与える影響 |
垂直スケーラビリティ または スケールアップ | 1つのサーバーに能力を追加すること |
水平スケーラビリティ または スケールアウト | サーバーを追加すること |
Patterns
- パターンは実践にもとづいている
- パターンのすべてを詳細に理解する必要はない
- パターンは問題にそのまま適用できるものではない
- いくつかのパターンは独立しているがいくつかは関連している
The Structure of the Pattern
- 名前
- 目的: 短い要約
- 図: 視覚表現(大体UML図)
- 動機となる問題: 複数の問題が動機となり得るが、著者が強い動機となる問題を記載)
- How it works: 解決法を記載(特定のプラットフォームに依存する内容はインデントして記載)
- When to Use: いつ使うべきか。他のパターントントレードオフも記載。
- Future Reading: パターンに関する議論への参照
- 例: JavaかC#
Limitations of These Pattern
- エンタープライズアプリケーション開発の包括的なガイドではない
- パターンの使用は出発点であって終着点ではない
Markdown Sample
見出し1
見出し2
見出し3
見出し4
見出し5
# 見出し1 ## 見出し2 ### 見出し3 #### 見出し4 ##### 見出し5
- 箇条書きリスト
- 箇条書きリスト(字下げ階層化)
- 箇条書きリスト(字下げ階層化)
- 箇条書きリスト
- 箇条書きリスト - 箇条書きリスト(字下げ階層化) - 箇条書きリスト(字下げ階層化) - 箇条書きリスト
- a
- b
- c
9. a 9. b 9. c
none | center | right |
---|---|---|
a | b | c |
dd | ee | ff |
|none|center|right| |---|---|---| |a|b|c| |dd|ee|ff|
id:leokite
id:leokite
id:leokite `id:leokite`
引用記法 なお、引用記法内でMarkdown記法も使える。
- リスト
- リスト
入れ子引用
>引用記法 > なお、引用記法内でMarkdown記法も使える。 > > - リスト > - リスト >> 入れ子引用
require 'redcarpet' markdown = Redcarpet.new("Hello World!") puts markdown.to_html
```ruby
require 'redcarpet'
markdown = Redcarpet.new("Hello World!")
puts markdown.to_html
```
斜体
太字
太字の斜体
*斜体* **太字** ***太字の斜体***
--- *** ___
検索エンジン[Google](http://www.google.com)を使う。
- [リンクその1][1] - [長いURL][longURL] [1]: http://www.leokite.hatenablog.com [longURL]: http://ja.wikipedia.org/wiki/%E3%81%AF%E3%81%A6%E3%81%AA%E3%81%AE%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E4%B8%80%E8%A6%A7 "はてなのサービス一覧 - Wikipedia"
![formula][matrix] [matrix]:http://www.codecogs.com/eq.latex?%20%5Cbegin{pmatrix}a_{11}%20&%20a_{12}%20%5C%5C%20a_{21}%20&%20a_{22}%20%5Cend{pmatrix}
![formula][root] [root]:http://www.codecogs.com/eq.latex?\sqrt{variance}