Fabric chaincode測試 —— 開發者模式和單元測試
【參考鏈接】:https://blog.csdn.net/zhayujie5200/article/details/84561825
前言
在fabric開發中,chaincode的測試是一個令人比較頭疼的問題,一是由於實際情況中chaincode中的存儲和查詢是依賴於peer節點上的狀態數據庫的,所以無法在本地直接測試;二是由於chaincode是運行於容器中的,這導致我們很難獲取在代碼中打印的日志。如果直接在實際開發環境中測試chaincode就很麻煩了,每一次調試都需要重啟整個網絡(有可能還是多機部署的),並且要創建和加入通道,安裝以及實例化鏈碼,這嚴重影響了測試的效率。
下面介紹兩種測試鏈碼的手段,一種是開發者 (dev) 模式,在本地單機搭建一個簡單的網絡來進行測試;另一種是單元測試 (UT),可以無需啟動節點環境,自動化測試所有接口。
開發者模式(說明一下,當實驗完畢后,最好將此過程中創建的所有容器都刪除,下次測試時按照流程重新來一遍。否則,如果重用的這些容器的話,cli 容器可能會啟動失敗!)
環境分析
使用開發者調試環境,需要先下載fabric-samples,置於$GOPATH/src下。開發者調試目錄位於:fabric-samples/chaincode-docker-devmode
- 首先分析一下目錄中的docker-compose-simple.yaml文件:該網絡中包含1個orderer節點,1個peer節點,1個chaincode容器(負責運行我們要測試的鏈碼),1個cli容器(負責發送請求來測試鏈碼)。
有兩點需要注意的:
在cli容器的command項中可以看見,啟動后會自動執行當前目錄下的script.sh腳本,該腳本會自動創建名為myc的通道,並且將節點加入。所以我們只需要安裝和實例化鏈碼即可。
在chaincode容器的volumes中可以看見這樣一條映射:
- ./../chaincode:/opt/gopath/src/chaincode
說明fabric-samples/chaincode目錄會映射到容器內部,這也是我們待測試鏈碼需要放置的地方。為了方便管理,我們可以在該目錄下為每個鏈碼再分配一個目錄,然后把要測試的鏈碼放在其中。(當然也可以直接修改映射指向自己chaincode的實際路徑)。
測試過程
這里在以最簡單的sacc.go為例,該鏈碼只涉及到簡單的存儲(set)和查詢(get)功能。整個過程需要啟動三個終端:
終端一:啟動網絡
首先進入開發者模式目錄:
cd fabric-samples/chaincode-docker-devmode
啟動網絡:
docker-compose -f docker-compose-simple.yaml up
當看到Going to wait for newer blocks時表示啟動成功,此時網絡中存在四個容器(1 orderer,1 peer, 1 chaincode, 1 cli),創建了通道myc並將peer成功加入。
終端二:編譯鏈碼
進入chaincode容器
docker exec -it chaincode bash
編譯想要測試的chaincode:
cd sacc
go build
成功執行后單當前目錄下會出現生成的可執行文件。此時需要啟動這個可執行文件:
CORE_PEER_ADDRESS=peer:7052 CORE_CHAINCODE_ID_NAME=mycc:0 ./sacc
注:有個小問題,官網教程中的端口是peer:7051,並且當前peer確實也在監聽7051,但是寫成7051就會報錯:Error starting SimpleAsset chaincode: error sending chaincode REGISTER。
這個問題,其實在官方英文文檔里有說明,如果遇到報錯請自行上網查詢或者直接去英文文檔里運行鏈碼相關部分尋找答案。
當出現starting up ...的提示就說明鏈碼啟動成功了,在這個終端二里可以輸出chaincode中的日志(比如通過fmt.Print()打印的內容)。
終端三:在cli中測試鏈碼
進入cli容器:
docker exec -it cli bash
安裝和實例化鏈碼(實例化設置了a的初始值10):
peer chaincode install -p chaincodedev/chaincode/sacc -n mycc -v 0
peer chaincode instantiate -n mycc -v 0 -c '{"Args":["a","10"]}' -C myc
進行測試:
調用 set() 接口將 a 的值設置為20:
peer chaincode invoke -n mycc -c '{"Args":["set", "a", "20"]}' -C myc
調用 get() 接口查詢 a 的值,發現a的值已經更新為20,測試完畢。
peer chaincode query -n mycc -c '{"Args":["get","a"]}' -C myc
在開發者環境中加入couchdb
如果實際開發的鏈碼中使用了couchdb提供的富查詢,則需要在測試環境中加入couchdb容器。只需要對docker-compose-simple.yaml文件進行修改即可:
首先在文件中添加couchdb段的配置:couchdb: container_name: couchdb image: hyperledger/fabric-couchdb environment: - COUCHDB_USER= - COUCHDB_PASSWORD= ports: - 5984:5984 networks: - default
在peer的environment部分添加:
environment: - CORE_LEDGER_STATE_STATEDATABASE=CouchDB - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb:5984 - CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME= - CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=
在peer的depends_on部分添加:
depends_on: - couchdb
執行docker-compose命令后就可以啟動couchdb容器,同時在瀏覽器中輸入地址http://localhost:5984/_utils還可以進入couchdb的web端管理界面,更清晰的看到存入的數據,從而方便配合我們進行測試。
單元測試
單元測試 (UT) 可以提高調試的效率和我們代碼的質量。fabric中提供了一個MockStub類用於單元測試。
單元測試不需要啟動任何網絡節點,通過我們的測試文件就可以在本地對鏈碼中的接口進行調用測試。
其原理就是在MockStub類中維護一個 map[string][]byte 來模擬 key-val 的狀態數據庫,鏈碼調用的PutState() 和 GetState() 其實是作用於內存中的map。
MockStub主要提供兩個函數來模擬背書節點對鏈碼的調用:MockInit()和MockInvoke(),分別調用Init和Invoke接口。接收的參數均為類型為string的uuid(隨便設置即可),以及一個二維byte數組(用於測試的提供參數)。
單元測試的要求:
1.需要導入testing包;
2.單元測試文件以_test.go結尾;
3.測試用例的函數必須以Test開頭,可選的后綴名必須以大寫字母開頭!
單元測試的例子
下面是對sacc.go的單元測試例子,由於該代碼較簡單,這里就將幾個接口的測試寫在一個case里。創建sacc_test.go文件,測試用例如下:
package packageName(這里的包名與鏈碼的包名相同!(為main)) import ( "fmt" "github.com/hyperledger/fabric/core/chaincode/shim" "testing" ) func TestFunc(t *testing.T) { cc := new(SimpleAsset) // 創建Chaincode對象 stub := shim.NewMockStub("sacc", cc) // 創建MockStub對象 // 調用Init接口,將a的值設為90 stub.MockInit("1", [][]byte{[]byte("a"), []byte("90")}) // 調用get接口查詢a的值 res := stub.MockInvoke("1", [][]byte{[]byte("get"), []byte("a")}) fmt.Println("The value of a is ", string(res.Payload)) // 調用set接口設置a為100 stub.MockInvoke("1", [][]byte{[]byte("set"), []byte("a"), []byte("100")}) // 再次查詢a的值 res = stub.MockInvoke("1", [][]byte{[]byte("get"), []byte("a")}) fmt.Println("The new value of a is ", string(res.Payload)) }
在當前目錄執行go test,輸出結果如下:
還可以查看更詳細的測試結果,如覆蓋率:
go test -cover -covermode count -coverprofile ./cover.out
輸出結果,可以看見覆蓋率為68.8%,覆蓋率越高說明測試用例寫的越完整。
進一步執行以下命令可以將剛剛生成的 cover.out 文件轉化為html頁面在瀏覽器中更具體的看見測試的覆蓋程度。
go tool cover -html=./cover.out
實際測試的時候對每個接口都應該有不止一個case,需要考慮到反例或其他邊界條件。還可以在測試時將預期得到的結果與實際得到的結果進行比較,如果不一致就報錯,使用例不顯示PASS。
性能測試
性能測試的函數必須以 Benchmark 開頭,接收的參數類型為 *testing.B。這里我將一次存儲和查詢合並為一次操作(operation)來進行測試,代碼如下:(將下面的代碼添加到上一步中的 sacc_test.go文件中!)
func BenchmarkFunc(b *testing.B) {
cc := new(SimpleAsset)
stub := shim.NewMockStub("sacc", cc)
for i :=0 ; i< b.N; i++ {
stub.MockInvoke("1", [][]byte{[]byte("set"), []byte("a"), []byte("100")})
stub.MockInvoke("1", [][]byte{[]byte("get"), []byte("a")})
}
}
循環的次數為 b.N,並且每次測試時整個函數會被執行三次,N的數量會不斷增加,如100, 10k, 300k。
執行測試:
go test --benchmem -bench=.
測試結果如圖,ns/op 指的是平均每次操作花費的納秒數,B/op指平均每次操作占用的內存大小。
由於實際情況下chaincode的接口是面向狀態數據庫的,而這里是用內存的讀寫來模擬的,所以這里的性能測試顯得意義不是很大,但是如果鏈碼中存在一些比較耗時的計算等操作,還是可以性能測試一下的。
總結
開發者 (dev) 模式測試:
好處是網絡規模簡單,可以在終端中直接看到鏈碼打印的日志,使用cli命令行容器測試也比較方便(可以寫成測試腳本映射到cli容器中自動執行)。
不足之處為每次修改鏈碼后還是需要重新啟動整個網絡,再次編譯、安裝和實例化鏈碼,不過這些操作都可以寫成一個腳本一鍵完成。
單元測試:
好處是不需要啟動網絡環境,一條簡單的命令就可以在本地自動化執行,且可以幫助我們很規范地對接口進行完整的測試。
不足之處是目前還無法測試基於couchDB的富查詢操作。