Fabric Chaincode調試 —— 開發者模式和單元測試


在fabric開發中,chaincode的測試是一個令人比較頭疼的問題,一是由於實際情況中chaincode中的存儲和查詢是依賴於peer節點上的狀態數據庫的,所以無法在本地直接測試;二是由於chaincode是運行於容器中的,這導致我們很難獲取在代碼中打印的日志。

如果直接在實際開發環境中測試chaincode就更麻煩了,每一次調試都需要重啟整個網絡(有可能還是多機部署的),並且要創建和加入通道,安裝以及實例化鏈碼,這嚴重影響了測試的效率。下面介紹兩種測試鏈碼的手段,一種是開發者 (dev) 模式,在本地單機搭建一個簡單的網絡來進行測試;另一種是單元測試 (UT),可以無需啟動節點環境,自動化測試所有接口。

開發者模式

環境分析

使用開發者調試環境,需要先下載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的狀態數據庫,鏈碼調用的PutStat()GetStat() 其實是作用於內存中的map。

MockStub主要提供兩個函數來模擬背書節點對鏈碼的調用:MockInit()MockInvoke(),分別調用InitInvoke接口。接收的參數均為類型為string的uuid(隨便設置即可),以及一個二維byte數組(用於測試的提供參數)。

單元測試的要求:

  • 需要導入testing
  • 單元測試文件以_test.go結尾
  • 測試用例的函數必須以Test開頭

單元測試的例子
下面是對sacc.go的單元測試例子,由於該代碼較簡單,這里就將幾個接口的測試寫在一個case里。創建sacc_test.go文件,測試用例如下:

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)來進行測試,代碼如下:

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的富查詢操作。

原文鏈接:https://zhayujie.com/chaincode-test.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM