Fabric 2.0,go链码单元测试

之前一直是完成链码的逻辑,然后打包部署在fabric网络之后,才知道链码写的正不正确,但是这样返工一方面浪费时间,另一方面,在开发时心底也是虚的。

比较理想的开发方法是首先为接口写好自动化测试,运行,出错,然后再去开发代码,来通过测试用例之后才算完成开发,这也是一种测试驱动开发的思想,好处就是之后即使修改代码也可以很方便的完成回归测试,再配合git,就可以更大胆的进行开发了。

在fabric环境下进行测试的话一个难点在于上下文环境的模拟,但是关于这点,事实上官方给出了一个测试编写的样例,如果是最2.3.0的fabric,可以在fabric-samples/asset-transfer-basic/chaincode-go/chaincode/下找到这个smartcontract_test.go文件,如果熟悉Java的spring开发框架的话,可以类比到对Spring框架进行测试时使用的Mock类,它提供了一个虚拟的账本交互环境,可以供我们模拟账本调用并且手动抛出错误等,这样就可以用go自带的单元测试功能来测试链码的功能。本文接下来首先会参考smartcontract_test.go总结一下编写测试的套路,然后使用这个框架对我们之前使用的链码编写一下单元测试。

1.引入必要依赖

测试文件首先需要写包名和必要的依赖,首先包名与被测试的链码有关,比如被测试的链码使用的包名为chaincode,那么测试文件的包名就必须为chaincode_test,否则运行测试会报错,这点应该是go的测试规定的,依赖的话主要是四个方面,这里分别来介绍一下。

第一是go自带的相关依赖,包括json的序列化工具包"encoding/json"格式化输入输出"fmt"以及自带的单元测试框架"testing"。

第二种就是fabric所提供的运行环境包,包括"github.com/hyperledger/fabric-chaincode-go/shim"、“github.com/hyperledger/fabric-contract-api-go/contractapi”、“github.com/hyperledger/fabric-protos-go/ledger/queryresult”。

第三种是不属于go自带也不是fabric官方提供的包,在这里只有这个"github.com/stretchr/testify/require",他提供了一种断言机制,可以类比为JUnit的Assert。

最后一种是引用当前目录下的包,这里有两个,第一个是"github.com/hyperledger/fabric-samples/asset-transfer-basic/chaincode-go/chaincode",也就是被测试的链码,第二个是"github.com/hyperledger/fabric-samples/asset-transfer-basic/chaincode-go/chaincode/mocks",是官方提供的对上下文环境的模拟实现,看注释这几个文件似乎是自动生成的,有上千行,使用时直接复制过去改一下引用之后用就好了。

作为go的初学者,我当时看到"github.com/hyperledger/fabric-samples/asset-transfer-basic/chaincode-go/chaincode"这种引用结构,以为这两个包是从网络上现下的,但是通过查看go.mod,可以看到下面一句话:

module github.com/hyperledger/fabric-samples/asset-transfer-basic/chaincode-go

这个module可以当做一个绝对路径,可以试一下去掉这里和代码里的-go部分,测试同样可以跑通。

之后代码中还有一个接口组合,比如将shim.ChaincodeStubInterface的所有方法都加到chaincodeStub接口上创建出一个等效接口,这里我实在看不懂他的用法,而事实上,把他们注释掉也不影响测试,移除时还会顺手把fabric运行 环境包一起移除了,如果有看懂用法的同学欢迎评论区留言,我这里的猜测是严谨起见保证接口的一致性,即使是在测试类中的接口也要和fabric官方的接口保持一致。

保留必须包之后的代码如下:

package chaincode_test
import (
	"encoding/json"
	"fmt"
	"testing"

	"github.com/hyperledger/fabric-protos-go/ledger/queryresult"
	"github.com/hyperledger/fabric-samples/asset-transfer-basic/chaincode-go/chaincode"
	"github.com/hyperledger/fabric-samples/asset-transfer-basic/chaincode-go/chaincode/mocks"
	"github.com/stretchr/testify/require"
)

2.使用

2.1.概述

Mocks提供了链码中的GetState、PutState等方法的对应的返回值设置方法GetStateReturns和PutStateReturns方法等用于设置链码中调用GetState等方法的返回值及错误情况等,如果把链码类比为service层,那么这里我们可以把我们的返回值手动设置看作service层调用了我们自己实现的dao层,由此可以实现对service层的间接控制,所以到这里我们也可以看出来,其实链码测试时是无状态的,即调用存入的API之后并没有保存这个存入记录的状态,取不出对应的数据。

在使用之前,我们需要用一些代码来初始化返回值设置的桩:

chaincodeStub := &mocks.ChaincodeStub{
   }
transactionContext := &mocks.TransactionContext{
   }
transactionContext.GetStubReturns(chaincodeStub)

初始化好之后,我们就可以使用chaincodeStub来调用相应的返回值设置方法。

比如说链码是如下的方法:

// AssetExists returns true when asset with given ID exists in world state
func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
   
	assetJSON, err := ctx.GetStub().GetState(id)
	if err != nil {
   
		return false, fmt.Errorf("failed to read from world state: %v", err)
	}
	return assetJSON != nil, nil
}

那么相应的,我们可以调用GetStateReturns方法来设置其返回值:

func TestAssetExists(t *testing.T) {
   
	chaincodeStub := &mocks.ChaincodeStub{
   }
	transactionContext := &mocks.TransactionContext{
   }
	transactionContext.GetStubReturns(chaincodeStub)

	assetTransfer := chaincode.SmartContract{
   }
	expectedAsset := &chaincode.Asset{
   ID: "asset1"}
	bytes, err := json.Marshal(expectedAsset)
	require.NoError(t, err)
	// 方法一
	chaincodeStub.GetStateReturns(bytes, nil)
	exist, err := assetTransfer.AssetExists(transactionContext, "asset1")
	require.NoError(t, err)
	require.Equal(t, true, exist)
	// 方法二
	chaincodeStub.GetStateReturns(nil, fmt.Errorf("unable to retrieve asset"))
	_, err = assetTransfer.AssetExists(transactionContext, "asset1")
	require.EqualError(t, err, "failed to read from world state: unable to retrieve asset")
}

从代码中可以看出,方法一里面我们手动设置了返回值为一个序列化的json对象,所以链码返回值为true,方法二中我们则手动设置了返回时产生的错误,因此方法执行之后获得到我们事先设定的错误,最后我们的断言都是用require的方法来实现的。

最后有一个测试起来稍微麻烦点的方法,就是获取多个数据,因为链码中,是通过范围查询返回迭代器,然后不停的Next来输出所有数据的,因此在写测试桩时,返回的迭代器是我们重写了HasNext和Next返回值的版本,所以相对较复杂一些。

首先我们需要定义返回的迭代器,使用StateQueryIterator{}来进行创建,然后分别设置HasNext和Next方法的返回值,其中HasNext方法的返回值通过HasNextReturnsOnCall(times, boolValue)来设置,其中times为第几次调用HasNext,boolValue为调用的时候返回的布尔值,如果只有一次调用,那么可以直接调用HasNextReturns,传入布尔值,为下次调用HasNext方法的返回值。Next方法的返回值则通过NextReturns方法来设置,第一个参数为下一次调用Next()方法的返回值,类似队列,每次设置都是给队尾加入元素,Next方法调用则是从队头拿出元素,第二个参数则为Next方法抛出的错误,如果没有错误,则设置为nil。

之后就是设置返回值了,GetStateByRange方法使用GetStateByRangeReturns方法来设置,第一个参数是一个迭代器,第二个参数则为返回时产生的错误,如果没有就返回nil。具体代码如下:

// 创建返回的json序列
asset := &chaincode.Asset{
   ID: "asset1"}
bytes, err := json.Marshal(asset)
require.NoError(t, err)
// 新建迭代器
iterator := &mocks.StateQueryIterator{
   }
// 设置迭代器有两个值,第三次HasNext返回没有更多
iterator.HasNextReturnsOnCall(0
### 回答1: 下面是一个简单的 Hyperledger Fabric 2.0 Go语言链码示例: ``` package main import ( "fmt" "github.com/hyperledger/fabric-chaincode-go/shim" pb "github.com/hyperledger/fabric-protos-go/peer" ) // SimpleChaincode example simple Chaincode implementation type SimpleChaincode struct { } func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { fmt.Println("ex02 Init") _, args := stub.GetFunctionAndParameters() var A, B string // Entities var Aval, Bval int // Asset holdings var err error if len(args) != 4 { return shim.Error("Incorrect number of arguments. Expecting 4") } // Initialize the chaincode A = args[0] Aval, err = strconv.Atoi(args[1]) if err != nil { return shim.Error("Expecting integer value for asset holding") } B = args[2] Bval, err = strconv.Atoi(args[3]) if err != nil { return shim.Error("Expecting integer value for asset holding") } fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval) // Write the state to the ledger err = stub.PutState(A, []byte(strconv.Itoa(Aval))) if err != nil { return shim.Error(err.Error()) } err = stub.PutState(B, []byte(strconv.Itoa(Bval))) if err != nil { return shim.Error(err.Error()) } return shim.Success(nil) } func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { fmt.Println("ex02 Invoke") function, args := stub.GetFunctionAndParameters() if function == "invoke" { // Make payment of X units from A to B return t.invoke(stub, args) } else if function == "delete" { // Deletes an entity from its state return t.delete(stub, args) } else if function == "query" { // the old "Query" is now implemtned in invoke return t.query(stub, args) } return shim.Error("Invalid invoke function name. Expecting \"invoke\" \"delete\" \"query\"") } // Transaction makes payment of X units from A to B func (t *SimpleChaincode) invoke(stub shim.ChaincodeStubInterface, args []string) pb.Response { var A, B string // Entities var Aval, Bval int // Asset holdings var X int // Transaction value var err error ### 回答2: Hyperledger Fabric 是一个开源的区块链平台,可以用于构建企业级的去中心化应用程序。而 Hyperledger Fabric 2.0 是其最新版本,引入了许多新特性和改进。 编一个 Hyperledger Fabric 2.0 的 Go 语言链码需要按照一定的流程进行: 1. 准备开发环境:首先,需要安装 Go 语言的开发环境和 Hyperledger Fabric 的相关工具,如 Hyperledger Fabric SDK 和 Hyperledger Fabric CA。 2. 编链码逻辑:使用 Go 语言链码的逻辑,链码是在 Hyperledger Fabric 上运行的智能合约。可以根据项目需求和业务逻辑定义相关的数据结构和函数。 3. 定义链码接口:需要定义链码接口,包括 Init 和 Invoke 两个核心函数。Init 函数用于链码的初始化操作,而 Invoke 函数用于链码的业务逻辑执行。 4. 部署链码:将编好的链码部署到 Hyperledger Fabric 网络中。可以使用 Hyperledger Fabric SDK 提供的工具和 API 来进行链码的部署操作。 5. 测试链码:编相应的测试用例,对链码逻辑进行测试。可以使用模拟的 Fabric 网络进行测试,或者与实际的 Fabric 网络交互进行测试。 6. 部署链码应用程序:将编好的链码应用程序部署到 Hyperledger Fabric 网络上。可以使用 Hyperledger Fabric SDK 提供的工具和 API 来进行链码应用程序的部署操作。 Go 语言是一种高性能的编程语言,适合于开发区块链平台和链码。编 Hyperledger Fabric 2.0 的 Go 语言链码需要熟悉 Go 语言的基本语法和特性,以及了解 Hyperledger Fabric 的相关知识。通过合理的设计和编码,可以实现各种复杂的业务逻辑和功能。 ### 回答3: 编一个Hyperledger Fabric 2.0的Go语言链码可以分为以下几个步骤: 1. 准备开发环境:首先,需要在开发机器上安装Go语言和Hyperledger Fabric的相关依赖。可以通过配置Golang环境变量,并使用Golang包管理器安装Fabric的Go SDK。 2. 创建链码项目:使用Go语言的IDE或文本编辑器创建一个新的文件夹,作为链码项目的根目录。 3. 定义链码结构:创建一个新的Go文件,并定义链码结构。链码结构应该实现fabric的Chaincode接口,并实现Init和Invoke两个方法。 4. 实现Init方法:Init方法在链码被实例化时调用,并进行初始化设置。可以在该方法中初始化链码的状态数据和其他必要的准备工作。 5. 实现Invoke方法:Invoke方法在链码接收到调用请求时被调用。在该方法中处理具体的业务逻辑,并根据请求中的操作类型执行相应的操作。 6. 将链码打包:使用Fabric提供的命令行工具将链码打包成压缩文件,以便于后续部署和安装。 7. 部署和安装链码:使用Fabric提供的链码生命周期管理工具,将链码部署到指定的Fabric网络中,并安装到指定的通道上。 8. 实例化链码:在指定的通道上实例化链码,使其可以被其他参与方调用。 9. 调用链码:使用Fabric提供的客户端SDK或命令行工具,向链码发送调用请求,验证链码的功能和逻辑是否正确。 10. 测试链码:编一些测试用例,用于对链码的功能和性能进行验证。 以上是一个简要的步骤,编Hyperledger Fabric 2.0的Go语言链码还需要进一步了解链码开发的相关知识和Fabric的API,以有效地实现业务逻辑,并与Fabric网络进行交互。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值