zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    Selenium3+python3自动化(十六)--单选框和复选框(radiobox、checkbox)
    Selenium3+python3自动化(十五)--JS处理滚动条,window.scrollTo(xpos,ypos)
    Selenium3+python3自动化(十四)--alertsconfirmprompt
    Selenium3+python3自动化(十三)--select下拉框
    Selenium3+python3自动化(十二)--iframe
    UPC-5627 Boxes(思维)
    UPC-5007 High Score(计算)
    UPC-5003 Dunglish(模拟)
    Hrbust-1830 第一个重复出现的数(线段树区间最值查询)
    UPC-5599 Minimum Sum(线段树求区间最小值+二分区间计数)
  • 原文地址:https://www.cnblogs.com/zhayujie/p/12941838.html
Copyright © 2011-2022 走看看