在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
【参考链接】:https://blog.csdn.net/zhayujie5200/article/details/84561825 前言在fabric开发中,chaincode的测试是一个令人比较头疼的问题,一是由于实际情况中chaincode中的存储和查询是依赖于peer节点上的状态数据库的,所以无法在本地直接测试;二是由于chaincode是运行于容器中的,这导致我们很难获取在代码中打印的日志。如果直接在实际开发环境中测试chaincode就很麻烦了,每一次调试都需要重启整个网络(有可能还是多机部署的),并且要创建和加入通道,安装以及实例化链码,这严重影响了测试的效率。 下面介绍两种测试链码的手段,一种是开发者 (dev) 模式,在本地单机搭建一个简单的网络来进行测试;另一种是单元测试 (UT),可以无需启动节点环境,自动化测试所有接口。 开发者模式(说明一下,当实验完毕后,最好将此过程中创建的所有容器都删除,下次测试时按照流程重新来一遍。否则,如果重用的这些容器的话,cli 容器可能会启动失败!)环境分析
有两点需要注意的: 在cli容器的command项中可以看见,启动后会自动执行当前目录下的script.sh脚本,该脚本会自动创建名为myc的通道,并且将节点加入。所以我们只需要安装和实例化链码即可。 - ./../chaincode:/opt/gopath/src/chaincode
说明fabric-samples/chaincode目录会映射到容器内部,这也是我们待测试链码需要放置的地方。为了方便管理,我们可以在该目录下为每个链码再分配一个目录,然后把要测试的链码放在其中。(当然也可以直接修改映射指向自己chaincode的实际路径)。 测试过程 终端一:启动网络 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成功加入。 终端二:编译链码 docker exec -it chaincode bash
编译想要测试的chaincode: cd sacc
go build
成功执行后单当前目录下会出现生成的可执行文件。此时需要启动这个可执行文件: CORE_PEER_ADDRESS=peer:7052 CORE_CHAINCODE_ID_NAME=mycc:0 ./sacc
终端三:在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
进行测试: 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
单元测试单元测试 (UT) 可以提高调试的效率和我们代码的质量。fabric中提供了一个MockStub类用于单元测试。 单元测试不需要启动任何网络节点,通过我们的测试文件就可以在本地对链码中的接口进行调用测试。 其原理就是在MockStub类中维护一个 map[string][]byte 来模拟 key-val 的状态数据库,链码调用的PutState() 和 GetState() 其实是作用于内存中的map。 MockStub主要提供两个函数来模拟背书节点对链码的调用:MockInit()和MockInvoke(),分别调用Init和Invoke接口。接收的参数均为类型为string的uuid(随便设置即可),以及一个二维byte数组(用于测试的提供参数)。 单元测试的要求:
单元测试的例子
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的接口是面向状态数据库的,而这里是用内存的读写来模拟的,所以这里的性能测试显得意义不是很大,但是如果链码中存在一些比较耗时的计算等操作,还是可以性能测试一下的。 总结
单元测试:
|
请发表评论