• -------------------------------------------------------------
  • ====================================

Fabric chaincode测试 —— 开发者模式和单元测试

区块链 dewbay 4年前 (2020-08-10) 2068次浏览 已收录 2个评论 扫描二维码

前言

在 fabric 开发中,chaincode 的测试是一个令人比较头疼的问题,一是由于实际情况中 chaincode 中的存储和查询是依赖于 peer 节点上的状态数据库的,所以无法在本地直接测试;二是由于 chaincode 是运行于容器中的,这导致我们很难获取在代码中打印的日志。

如果直接在实际开发环境中测试chaincode 就很麻烦了,每一次调试都需要重启整个网络(有可能还是多机部署的),并且要创建和加入通道,安装以及实例化链码,这严重影响了测试的效率。下面介绍两种测试链码的手段,一种是开发者 (dev) 模式,在本地单机搭建一个简单的网络来进行测试;另一种是单元测试 (UT),可以无需启动节点环境,自动化测试所有接口。

开发者模式

环境分析

使用开发者调试环境,需要先下载fabric-samples,置于$GOPATH/src下。开发者调试目录位于:

<code class="prism language-shell has-numbering">fabric-samples/chaincode-docker-devmode
</code>
  • 1

首先分析一下目录中的docker-compose-simple.yaml文件:
该网络中包含 1 个 orderer 节点,1 个 peer 节点,1 个 chaincode 容器(负责运行我们要测试的链码),1 个 cli 容器(负责发送请求来测试链码)。

有两点需要注意的:

  • 在 cli 容器的command项中可以看见,启动后会自动执行当前目录下的script.sh脚本,该脚本会自动创建名为myc的通道,并且将节点加入。所以我们只需要安装和实例化链码即可。
  • 在 chaincode 容器的volumes中可以看见这样一条映射:
    <code class="prism language-yaml has-numbering"><span class="token punctuation">-</span> ./../chaincode<span class="token punctuation">:</span>/opt/gopath/src/chaincode
    </code>
    • 1

    说明fabric-samples/chaincode目录会映射到容器内部,这也是我们待测试链码需要放置的地方。为了方便管理,我们可以在该目录下为每个链码再分配一个目录,然后把要测试的链码放在其中。(当然也可以直接修改映射指向自己 chaincode 的实际路径)。

测试过程

这里在以最简单的sacc.go为例,该链码只涉及到简单的存储(set)和查询(get)功能。整个过程需要启动三个终端:

终端一:启动网络
首先进入开发者模式目录:

<code class="prism language-shell has-numbering"><span class="token function">cd</span> fabric-samples/chaincode-docker-devmode
</code>

启动网络:

<code class="prism language-shell has-numbering">docker-compose -f docker-compose-simple.yaml up
</code>

当看到Going to wait for newer blocks时表示启动成功,此时网络中存在四个容器(1 orderer,1 peer, 1 chaincode, 1 cli),创建了通道 myc 并将 peer 成功加入。

终端二:编译链码
进入 chaincode 容器

<code class="prism language-shell has-numbering">docker <span class="token function">exec</span> -it chaincode <span class="token function">bash</span>
</code>

编译想要测试的 chaincode:

<code class="prism language-shell has-numbering"><span class="token function">cd</span> sacc
go build
</code>

成功执行后单当前目录下会出现生成的可执行文件。此时需要启动这个可执行文件:

<code class="prism language-shell has-numbering">CORE_PEER_ADDRESS<span class="token operator">=</span>peer:7052 CORE_CHAINCODE_ID_NAME<span class="token operator">=</span>mycc:0 ./sacc
</code>

注:这里有个不解的小问题,官网教程中的端口是 peer:7051,并且当前 peer 确实也在监听 7051,但是写成 7051 就会报错:Error starting SimpleAsset chaincode: error sending chaincode REGISTER

当出现starting up ...的提示就说明链码启动成功了,在这个终端二里可以输出 chaincode 中的日志(比如通过fmt.Print()打印的内容)。

终端三:在 cli 中测试链码
进入 cli 容器:

<code class="prism language-shell has-numbering">docker <span class="token function">exec</span> -it cli <span class="token function">bash</span>
</code>

安装和实例化链码(实例化设置了 a 的初始值 10):

<code class="prism language-shell has-numbering">peer chaincode <span class="token function">install</span> -p chaincodedev/chaincode/sacc -n mycc -v 0
peer chaincode instantiate -n mycc -v 0 -c <span class="token string">'{"Args":["a","10"]}'</span> -C myc
</code>

进行测试:
调用 set()接口将 a 的值设置为 20:

<code class="prism language-shell has-numbering">peer chaincode invoke -n mycc -c <span class="token string">'{"Args":["set", "a", "20"]}'</span> -C myc
</code>

调用 get()接口查询 a 的值,发现 a 的值已经更新为 20,测试完毕。

<code class="prism language-shell has-numbering">peer chaincode query -n mycc -c <span class="token string">'{"Args":["get","a"]}'</span> -C myc
</code>

在开发者环境中加入 couchdb
如果实际开发的链码中使用了 couchdb 提供的富查询,则需要在测试环境中加入 couchdb 容器。

只需要对docker-compose-simple.yaml文件进行修改即可:
首先在文件中添加 couchdb 段的配置:

<code class="prism language-yaml has-numbering"><span class="token key atrule">couchdb</span><span class="token punctuation">:</span>
    <span class="token key atrule">container_name</span><span class="token punctuation">:</span> couchdb
    <span class="token key atrule">image</span><span class="token punctuation">:</span> hyperledger/fabric<span class="token punctuation">-</span>couchdb
    <span class="token key atrule">environment</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> COUCHDB_USER=
      <span class="token punctuation">-</span> COUCHDB_PASSWORD=
    <span class="token key atrule">ports</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> 5984<span class="token punctuation">:</span><span class="token number">5984</span>
    <span class="token key atrule">networks</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> default
</code>

在 peer 的 environment 部分添加:

<code class="prism language-yaml has-numbering"><span class="token key atrule">environment</span><span class="token punctuation">:</span>
	<span class="token punctuation">-</span> CORE_LEDGER_STATE_STATEDATABASE=CouchDB
	<span class="token punctuation">-</span> CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb<span class="token punctuation">:</span><span class="token number">5984</span>
	<span class="token punctuation">-</span> CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=
	<span class="token punctuation">-</span> CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=
</code>

在 peer 的 depends_on 部分添加:

<code class="prism language-yaml has-numbering"><span class="token key atrule">depends_on</span><span class="token punctuation">:</span>
	<span class="token punctuation">-</span> couchdb
</code>

执行 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 数组(用于测试的提供参数)。

单元测试的要求:
1.需要导入testing
2.单元测试文件以_test.go结尾
3.测试用例的函数必须以Test开头

单元测试的例子
下面是对 sacc.go 的单元测试例子,由于该代码较简单,这里就将几个接口的测试写在一个 case 里。创建sacc_test.go文件,测试用例如下:

<code class="prism language-go has-numbering"><span class="token keyword">func</span> <span class="token function">TestFunc</span><span class="token punctuation">(</span>t <span class="token operator">*</span>testing<span class="token punctuation">.</span>T<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    cc <span class="token operator">:=</span> <span class="token function">new</span><span class="token punctuation">(</span>SimpleAsset<span class="token punctuation">)</span>          				<span class="token comment">// 创建 Chaincode 对象</span>
    stub <span class="token operator">:=</span> shim<span class="token punctuation">.</span><span class="token function">NewMockStub</span><span class="token punctuation">(</span><span class="token string">"sacc"</span><span class="token punctuation">,</span> cc<span class="token punctuation">)</span>			<span class="token comment">// 创建 MockStub 对象</span>
    <span class="token comment">// 调用 Init 接口,将 a 的值设为 90</span>
    stub<span class="token punctuation">.</span><span class="token function">MockInit</span><span class="token punctuation">(</span><span class="token string">"1"</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">{</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span><span class="token string">"a"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span><span class="token string">"90"</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token comment">// 调用 get 接口查询 a 的值</span>
    res <span class="token operator">:=</span> stub<span class="token punctuation">.</span><span class="token function">MockInvoke</span><span class="token punctuation">(</span><span class="token string">"1"</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">{</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span><span class="token string">"get"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span><span class="token string">"a"</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">)</span>
    fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"The value of a is"</span><span class="token punctuation">,</span> <span class="token function">string</span><span class="token punctuation">(</span>res<span class="token punctuation">.</span>Payload<span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token comment">// 调用 set 接口设置 a 为 100</span>
    stub<span class="token punctuation">.</span><span class="token function">MockInvoke</span><span class="token punctuation">(</span><span class="token string">"1"</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">{</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span><span class="token string">"set"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span><span class="token string">"a"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span><span class="token string">"100"</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token comment">// 再次查询 a 的值</span>
    res <span class="token operator">=</span> stub<span class="token punctuation">.</span><span class="token function">MockInvoke</span><span class="token punctuation">(</span><span class="token string">"1"</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">{</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span><span class="token string">"get"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span><span class="token string">"a"</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">)</span>
    fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"The new value of a is"</span><span class="token punctuation">,</span> <span class="token function">string</span><span class="token punctuation">(</span>res<span class="token punctuation">.</span>Payload<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code>

在当前目录执行go test,输出结果如下
Fabric chaincode测试 —— 开发者模式和单元测试

 

还可以查看更详细的测试结果,如覆盖率:

<code class="prism language-shell has-numbering">go <span class="token function">test</span> -cover -covermode count -coverprofile ./cover.out
</code>

输出结果,可以看见覆盖率为 68.8%,覆盖率越高说明测试用例写的越完整。

Fabric chaincode测试 —— 开发者模式和单元测试

进一步执行以下命令可以将刚刚生成的 cover.out 文件转化为 html 页面在浏览器中更具体的看见测试的覆盖程度。

<code class="prism language-shell has-numbering">go tool cover -html<span class="token operator">=</span>./cover.out
</code>

Fabric chaincode测试 —— 开发者模式和单元测试

实际测试的时候对每个接口都应该有不止一个 case,需要考虑到反例或其他边界条件,还可以在测试时将预期得到的结果与实际得到的结果进行比较,如果不一致就报错使用例不显示 PASS。

性能测试

性能测试的函数必须以 Benchmark 开头,接收的参数类型为*testing.B。这里我将一次存储和查询合并为一次操作(operation)来进行测试,代码如下:

<code class="prism language-go has-numbering"><span class="token keyword">func</span> <span class="token function">BenchmarkFunc</span><span class="token punctuation">(</span>b <span class="token operator">*</span>testing<span class="token punctuation">.</span>B<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    cc <span class="token operator">:=</span> <span class="token function">new</span><span class="token punctuation">(</span>SimpleAsset<span class="token punctuation">)</span>
    stub <span class="token operator">:=</span> shim<span class="token punctuation">.</span><span class="token function">NewMockStub</span><span class="token punctuation">(</span><span class="token string">"sacc"</span><span class="token punctuation">,</span> cc<span class="token punctuation">)</span>
    <span class="token keyword">for</span> i <span class="token operator">:=</span><span class="token number">0</span> <span class="token punctuation">;</span> i<span class="token operator"><</span> b<span class="token punctuation">.</span>N<span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">{</span>
        stub<span class="token punctuation">.</span><span class="token function">MockInvoke</span><span class="token punctuation">(</span><span class="token string">"1"</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">{</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span><span class="token string">"set"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span><span class="token string">"a"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span><span class="token string">"100"</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">)</span>
        stub<span class="token punctuation">.</span><span class="token function">MockInvoke</span><span class="token punctuation">(</span><span class="token string">"1"</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">{</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span><span class="token string">"get"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span><span class="token string">"a"</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code>

循环的次数为 b.N,并且每次测试时整个函数会被执行三次,N 的数量会不断增加,如 100, 10k, 300k。

执行测试:

<code class="prism language-shell has-numbering">go <span class="token function">test</span> --benchmem -bench<span class="token operator">=</span>.
</code>

测试结果如图,ns/op 指的是平均每次操作花费的纳秒数,B/op 指平均每次操作占用的内存大小。
Fabric chaincode测试 —— 开发者模式和单元测试
由于实际情况下 chaincode 的接口是面向状态数据库的,而这里是用内存的读写来模拟的,所以这里的性能测试显得意义不是很大,但是如果链码中存在一些比较耗时的计算等操作,还是可以性能测试一下的。

小结

使用开发者 (dev) 模式进行测试:

  • 好处是网络规模简单,可以在终端中直接看到链码打印的日志,使用 cli 命令行容器测试也比较方便(可以写成测试脚本映射到 cli 容器中自动执行)。
  • 不足之处为每次修改链码后还是需要重新启动整个网络,再次编译、安装和实例化链码,不过这些操作都可以写成一个脚本一键完成。

使用单元测试:

  • 好处是不需要启动网络环境,一条简单的命令就可以在本地自动化执行,且可以帮助我们很规范地对接口进行完整的测试。
  • 不足之处是目前还无法测试基于 couchDB 的富查询操作。

露水湾 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:Fabric chaincode测试 —— 开发者模式和单元测试
喜欢 (0)
[]
分享 (0)
关于作者:
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
(2)个小伙伴在吐槽
  1. Viel Glück in Ihrem Blog, wie ich weiterhin regelmäßig zu folgen. Aimee Jared Ingles
    Aimee Jared Ingles2020-08-23 19:43 回复 Windows 10 | Chrome 84.0.4147.105
  2. A good blog! I will bookmark a few of these.. Golda Lammond Alfeus
    Golda Lammond Alfeus2020-08-23 20:03 回复 Windows 10 | Chrome 84.0.4147.105