Fabric链码测试⽅法(go语⾔单元测试性能测试)
Fabric chaincode测试 —— 开发者模式和单元测试
前⾔
在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的实际路径)。
测试过程
这⾥在以最简单的为例,该链码只涉及到简单的存储(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
go语言安装教程安装和实例化链码(实例化设置了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
单元测试
单元测试 (UT) 可以提⾼调试的效率和我们代码的质量。fabric中提供了⼀个MockStub类⽤于单元测试。
单元测试不需要启动任何⽹络节点,通过我们的测试⽂件就可以在本地对链码中的接⼝进⾏调⽤测试。
其原理就是在MockStub类中维护⼀个 map[string][]byte 来模拟 key-val 的状态数据库,链码调⽤的PutState() 和 GetState()其实是作⽤于内存中的map。
MockStub主要提供两个函数来模拟背书节点对链码的调⽤:MockInit()和MockInvoke(),分别调⽤Init和Invoke接⼝。接收的参数均为类型为string的uuid(随便设置即可),以及⼀个⼆维byte数组(⽤于测试的提供参数)。
单元测试的要求:
1.需要导⼊testing包;
2.单元测试⽂件以_结尾;
3.测试⽤例的函数必须以Test开头,可选的后缀名必须以⼤写字母开头!
单元测试的例⼦
下⾯是对的单元测试例⼦,由于该代码较简单,这⾥就将⼏个接⼝的测试写在⼀个case⾥。创建⽂件,测试⽤例如下:
package packageName(这⾥的包名与链码的包名相同!(为main))
import (
"fmt"
"github/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)来进⾏测试,代码如下:(将下⾯的代码添加到上⼀步中的 ⽂件中!)
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的富查询操作。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论