はじめに
Hyperledger FabricのChaincodeをネットワークにデプロイする前に、機能チェックしておきたい。
ということで、Go初学者がFabricのChaincodeを題材にして、Goで単体テストする方法を書き残しました。
環境
- Hyperledger Fabric v2.2.0
- サンプルソース、バイナリツール、Dockerイメージのインストール方法はこちら
- Go 1.15.8
- Visual Studio Codeのおすすめ拡張機能
注意
本記事では、Chaincode開発用のGoパッケージとしてshimの利用を前提としていますが、v2.0以降ではshimより高水準なAPIとしてcontractapiが提供されています。
今後はこちらを使っていく方が望ましいかもしれません。
(参考)
What is the difference between fabric-chaincode-go and fabric-contract-api-go?
https://stackoverflow.com/questions/60900762/what-is-the-difference-between-fabric-chaincode-go-and-fabric-contract-api-go
準備知識
Goのパッケージについて
-
FabricのChaincode開発ではshimパッケージが提供するAPIを使います。
-
Goでは依存パッケージを
import
で指定します。
import (
"github.com/hyperledger/fabric-chaincode-go/shim"
)
- コマンド
$go mod tidy
で、import
で記載したパッケージをダウンロードできます。このときgo.mod
とgo.sum
という2つのファイルも更新されます。-
go.mod
は依存パッケージを管理するファイル。 -
go.sum
はパッケージに更新があったかを判断するためのファイルで、依存モジュールのチェックサムが記録される。
-
- パッケージ自体は
$GOPATH/pkg/mod
配下に格納されます。 - コマンド
$go get
でも任意のパッケージを引数に指定してダウンロードできます。ただし、$go mod tidy
の方がimport
で記載した依存パッケージに応じて過不足なくダウンロードできる点が良さそうです。
Goのテストについて
- Goは標準で自動テストが組み込まれています。(コマンド
$go test
) - カバレッジやベンチマーク測定なども、オプションで組み込まれています。(
-cover
、-bench
) - テストする上で命名規則が決まってます。
-
XXX.go
がテスト対象の場合、テストプログラムのファイル名はXXX_test.go
とする。 - テスト関数名の接頭に
Test
をつける。
-
- テストプログラムはテスト対象と同じパッケージにします。
XXX.go
と同ディレクトリにXXX_test.go
を置いておくのが吉。
(参考)
はじめてのGoTest(Qiita)
https://qiita.com/marnie_ms4/items/e51cc6d879cc9ad07af3
GoによるChaincode開発/単体テスト
既に述べた通り、Chaincode開発ではshimパッケージを使います。
そして、Chaincodeの単体テストではshimtestパッケージを使います。
Fabric v1.4.xでの注意点
Fabric v1.4.xではパッケージ体系が異なるのでご注意ください。
Fabric v2.x以降から変更があったようです。
-
v1.4.x
-
v2.x以降
また、v1.4.xではshimに単体テスト用のAPIも含まれていて、shimtestは提供されていません。
v1.4.xで開発/単体テストする場合は、バージョンを指定してパッケージをダウンロードしましょう。
$ go mod init ...
$ go get github.com/hyperledger/fabric/core/chaincode/shim@v1.4
(参考)
Work with Hyperledger Fabric Chaincode Development Tools #4415
https://github.com/SAPDocuments/Tutorials/issues/4415
実践
ここではmarbles02(Tag v2.2.0)の単体テストを実行する方法を記します。(カバレッジ100%を目指すものではありません)
サンプルコードを使うのでテスト対象のmarbles_chaincode.go
は実装完了しているものとします。
単体テスト
marbles_chaincode.go
と同じディレクトリに、命名規則に従ってテストプログラム(空ファイル)を作成します。
$ cd fabric-samples/chaincode/marbles02/go/
$ touch marbles_chaincode_test.go
一部メソッドのみを対象とした、簡易なテストコードを実装します。
Chaincodeの実装ではバイト配列を扱うことが多いので、標準パッケージの他にも幾つかimportしてます。
package main
import (
"strconv" // 文字列変換を扱う標準パッケージ
"strings" // 文字列を扱う標準パッケージ
"testing" // テスト用の関数・構造体を扱う標準パッケージ
simplejson "github.com/bitly/go-simplejson" // JSON型式を扱うパッケージ
"github.com/stretchr/testify/assert" // アサーション機能を扱うパッケージ
funk "github.com/thoas/go-funk" // Sliceに対する便利機能(Map, Filterなど)を扱うパッケージ
"github.com/hyperledger/fabric-chaincode-go/shim"
"github.com/hyperledger/fabric-chaincode-go/shimtest"
)
var simpleChaincode = new(SimpleChaincode)
var mockStub = shimtest.NewMockStub("Test Creation", simpleChaincode)
func TestInitMarble(t *testing.T) {
t.Run("success InitMarble()", func(t *testing.T) {
functionName := "initMarble"
marbleName := "marble1"
marbleColor := "blue"
marbleSize := "35"
marbleOwner := "tom"
functionAndArgs := [][]byte{[]byte(functionName), []byte(marbleName), []byte(marbleColor), []byte(marbleSize), []byte(marbleOwner)}
// Invoke Function
res := mockStub.MockInvoke("InitMarble001_1", functionAndArgs)
if res.Status != shim.OK {
t.Error("Invoke", functionAndArgs, "failed", string(res.Message))
t.FailNow()
} else {
functionAndArgsMap := funk.Map(functionAndArgs, func(b []byte) string { return string(b) })
t.Log("Invoke", "(", strings.Join(functionAndArgsMap.([]string), ", "), ")", "successful", string(res.Message))
}
// Check StateDB
bytes, err := mockStub.GetState(marbleName)
if err != nil {
t.Error("Failed to get state")
t.FailNow()
} else if bytes == nil {
t.Error("State", marbleName, "failed to get value")
t.FailNow()
}
state, err := simplejson.NewJson(bytes)
assert.Equal(t, marbleName, state.Get("name").MustString())
assert.Equal(t, marbleColor, state.Get("color").MustString())
assert.Equal(t, marbleSize, strconv.Itoa((state.Get("size").MustInt())))
assert.Equal(t, marbleOwner, state.Get("owner").MustString())
})
}
func TestReadMarble(t *testing.T) {
t.Run("success ReadMarble()", func(t *testing.T) {
functionName := "readMarble"
marbleName := "marble1"
marbleColor := "blue"
marbleSize := "35"
marbleOwner := "tom"
functionAndArgs := [][]byte{[]byte(functionName), []byte(marbleName)}
// Invoke Function
res := mockStub.MockInvoke("ReadMarble001_1", functionAndArgs)
if res.Status != shim.OK {
t.Error("Invoke", functionAndArgs, "failed", string(res.Message))
t.FailNow()
} else {
functionAndArgsMap := funk.Map(functionAndArgs, func(b []byte) string { return string(b) })
t.Log("Invoke", "(", strings.Join(functionAndArgsMap.([]string), ", "), ")", "successful", string(res.Message))
}
// Check Response
payload := res.Payload
value, _ := simplejson.NewJson(payload)
assert.Equal(t, marbleName, value.Get("name").MustString())
assert.Equal(t, marbleColor, value.Get("color").MustString())
assert.Equal(t, marbleSize, strconv.Itoa((value.Get("size").MustInt())))
assert.Equal(t, marbleOwner, value.Get("owner").MustString())
})
}
依存パッケージダウンロード
テストコードを書いたら依存パッケージをダウンロードします。
go.mod
がなければ$ go mod init
で作成する必要があります。
ただし、marbles02(Tag v2.2.0)では既にgo.mod
も含まれているため省略します。
$ go mod tidy
コマンドで必要なパッケージをダウンロードしましょう。
$ go mod tidy
テスト実行
コマンド$ go test
でテスト実行です。
$ go test
> invoke is running initMarble
> - start init marble
> - end init marble
> invoke is running readMarble
> PASS
> ok github.com/hyperledger/fabric-samples/chaincode/marbles02/go 0.006s
カバレッジ取得
カバレッジ結果はファイルに出力できます。
HTMLに変換することもでき、カバーされている箇所を見やすくできます。
$ go test -coverprofile=cover.out .
> ok github.com/hyperledger/fabric-samples/chaincode/marbles02/go 0.005s coverage: 15.0% of statements
$ go tool cover -html=cover.out -o cover.html
(参考)
Go でコードカバレッジを取得する
https://qiita.com/kkohtaka/items/965fe08821cda8c9da8a
Visual Studio Codeなら、html-preview-vscodeという拡張機能でHTMLをプレビューできます。ブラウザ要らずで便利です。
Some content has been disabled in this document
というメッセージが表示される場合は、「change Preview Security Settings」を「Disable」に設定しましょう。
(参考)
VSCodeのhtmlPreviewでscriptタブの箇所がうまく表示されない場合の解決方法
https://entlife.net/2020/10/26/vscode%E3%81%AEhtmlpreview%E3%81%A7script%E3%82%BF%E3%83%96%E3%81%AE%E7%AE%87%E6%89%80%E3%81%8C%E3%81%86%E3%81%BE%E3%81%8F%E8%A1%A8%E7%A4%BA%E3%81%95%E3%82%8C%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88/
おわり
個人的にはGoの勉強になりました。