Solidity で、スマートコントラクトのテスト用モックアップを作成する方法
「モックオブジェクト (opens in a new tab)」は、オブジェクト指向プログラミングにおいて一般的なデザインパターンです。 「モック」という言葉は、古フランス語で「からかう」という意味を持つ「mocquer」が由来で、「本物を模倣する」という意味に発展しました。これこそ、プログラミングにで「モックアップ」を作成する作業です。 あなたが作成したスマートコントラクトについて「からかう」必要はありませんが、「モックアップ」の作成はできる限り必ず行ってください。 後々、楽になります。
モックを使ったコントラクトの単体テスト
コントラクトのモックアップを作成するとは、究極的に、オリジナルとほぼ同様に動作するものの、デベロッパが簡単に管理できる第2のバージョンを作成することです。 複雑なコントラクトを扱うことになると、コントラクトのごく一部だけを単体テストしたい場合がよくあります。 問題となるのは、この一部分をテストする上で、非常に具体的なコントラクトの状態を再現する必要があるものの、再現するのが難しい場合です。
テストに求められる状態を再現するために、テストを設定するための複雑なロジックを作成する代わりに、モックアップを作成すればよいのです。 コントラクトのモックアップは、継承を使って簡単に作成できます。 オリジナル版を継承したモックアップのコントラクトを作成するだけです。 モックアップでは、機能をオーバーライドすることができます。 この点については、具体例で見てみましょう。
例: Private ERC20
ここでは、当初にプライベート期間が設定された ERC-20 コントラクトを使って説明します。 トークン所有者はプライベートユーザーを管理でき、当初トークンを受け取ることができるのはプライベートユーザーのみになります。 設定した時間が経過した後は、すべてのユーザーがトークンを使用できるようになります。 ちなみに、新しいOpenZeppelin contracts v3の_beforeTokenTransfer (opens in a new tab)フックを使用しています。
1pragma solidity ^0.6.0;23import "@openzeppelin/contracts/token/ERC20/ERC20.sol";4import "@openzeppelin/contracts/access/Ownable.sol";56contract PrivateERC20 is ERC20, Ownable {7 mapping (address => bool) public isPrivateUser;8 uint256 private publicAfterTime;910 constructor(uint256 privateERC20timeInSec) ERC20("PrivateERC20", "PRIV") public {11 publicAfterTime = now + privateERC20timeInSec;12 }1314 function addUser(address user) external onlyOwner {15 isPrivateUser[user] = true;16 }1718 function isPublic() public view returns (bool) {19 return now >= publicAfterTime;20 }2122 function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override {23 super._beforeTokenTransfer(from, to, amount);2425 require(_validRecipient(to), "PrivateERC20: invalid recipient");26 }2728 function _validRecipient(address to) private view returns (bool) {29 if (isPublic()) {30 return true;31 }3233 return isPrivateUser[to];34 }35}すべて表示さっそく、このコントラクトのモックアップを作成しましょう。
1pragma solidity ^0.6.0;2import "../PrivateERC20.sol";34contract PrivateERC20Mock is PrivateERC20 {5 bool isPublicConfig;67 constructor() public PrivateERC20(0) {}89 function setIsPublic(bool isPublic) external {10 isPublicConfig = isPublic;11 }1213 function isPublic() public view returns (bool) {14 return isPublicConfig;15 }16}すべて表示以下のエラーメッセージのどちらかが表示されます:
PrivateERC20Mock.sol: TypeError: Overriding function is missing "override" specifier.PrivateERC20.sol: TypeError: Trying to override non-virtual function. Did you forget to add "virtual"?.
新しいSolidityバージョン0.6を使用しているため、オーバーライドされる関数にはvirtualキーワードを、オーバーライドする関数にはoverrideを追加する必要があります。 では、両方のisPublic関数にそれらを追加しましょう。
これで、単体テストで代わりにPrivateERC20Mockを使用できます。 プライベート利用期間中の動作をテストしたい場合はsetIsPublic(false)を、同様にパブリック利用期間のテストにはsetIsPublic(true)を使用します。 もちろん、この例ではタイムヘルパー (opens in a new tab)を使って、時間も同様に変更することもできます。 しかし、すでにモックアップの有用性について理解できたでしょう。単に時間を進めるよりも、モックアップを利用した方が望ましいシナリオがすぐに思い浮かぶはずです。
多数のコントラクトのモック
モックアップごとに新たなコントラクトを作成するのは煩雑な作業ですね。 これが気になる場合は、MockContract (opens in a new tab)ライブラリをご覧ください。 これにより、コントラクトの動作をその場でオーバーライドして変更できます。 ただしこのライブラリは、別のコントラクトに対する呼び出しのみに対応していますので、今回は使用できません。
モックはさらに強力になりうる
モックアップ作成のメリットは、他にもあります。
- 関数の追加:特定の関数をオーバーライドする機能だけでなく、関数を追加できることも有益です。 トークンの良い例として、追加の
mint関数を持たせることで、どのユーザーでも新しいトークンを無料で取得できるようにすることが挙げられます。 - テストネットでの使用:Dapp と共に、テストネット上でコントラクトをデプロイ、テストする際は、モックアップの活用を検討すべきです。 本当に必要な場合を除き、関数をオーバーライドすることは避けるべきです。 結局のところ、実際のロジックをテストする必要があるからです。 しかし例えば、コントラクトのステートを、新たにデプロイする必要なしで開始時点にリセットするだけのリセット機能は、有益な場合があるでしょう。 言うまでもなく、メインネット用のコントラクトにはこのようなリセット機能を含めてはなりません。
最終更新: 2025年8月25日