实战以太坊智能合约测试【Truffle】
Truffle开发框架提供了以太坊智能合约测试的两种方法:区块链级别的Solidity测试和DApp级别的JavaScript测试。在这个教程中,我们将介绍这两种以太坊智能合约测试方法的用途、区别与应用场景,并通过一个具体的示例来学习如何综合利用Solitiy测试用例和JavaScript测试用例对以太坊智能
合约进行单元测试和集成测试。
七种开发语言的以太坊教程:Java | Php | Python | .Net / C# | Golang | Node.JS | Flutter / Dart
1、以太坊智能合约测试概述
作为软件开发者,我们都知道要让代码正常运行,测试是非常重要的一个环节。基于区块链的去中心化软件也不例外,而且由于区块链的不可修改特性,测试对于区块链软件来说就更重要了。
总体上来说有两种类型的软件测试:单元测试和集成测试。单元测试聚焦于单个函数的测试,而集成测试的目标则是确保各部分代码组合起来也可以按期望的方式运行。
Truffle是应用最广的以太坊智能合约与DApp开发框架,它提供了两种用于测试以太坊智能合约的方法:Solidity测试和JavaScript测试。问题是,我们应该选择哪一种方法?
答案是都需要。
用Solidity编写智能合约的测试用例让我们可以在区块链层级进行测试。这种测试用例可以调用合约方法,就像用例部署在区块链里一样。为了测试智能合约的内部行为,我们可以:
- 编写Solidity单元测试来检查智能合约函数的返回值以及状态变量的值
- 编写Solidity集成测试来检查智能合约之间的交互。这些集成测试可以确保像继承或者 依赖注入这样的机制的运行符合预期
我们也需要确保智能合约能够表现出正确的外部行为。为了从区块链外部测试智能合约,我们在JavaScript测试用例中使用web3.js,就像在开发DApp时一样。我们需要对DApp前端可以正确调用智能合约建立信心。这方面的测试属于集成测试。
因此,简单地说,Solidity测试用例主要用于智能合约内部实现逻辑的验证,可以用于单元测试和集成测试;而JavaScript用例则主要用于智能合约外部行为的验证,通常用于集成测试。
2、以太坊智能合约测试的示例项目
假设我们有两个以太坊智能合约需要测试:Background和Entrypoint。
Background是一个内部合约,我们的DApp前端不会直接和它交互。EntryPoint则是专门供DApp交互的智能合约,在EntryPoint合约内部会访问Background合约。
下面是Background合约的solidity代码:
pragma solidity >=0.5.0; contract Background { uint[] private values; function storeValue(uint value) public { values.push(value); } function getValue(uint initial) public view returns(uint) { return values[initial]; } function getNumberOfValues() public view returns(uint) { return values.length; } }
在上面,我们看到Background合约提供了三个函数:
- storeValue(uint):写入值
- getValue(uint) :读取值
- getNumberOfValues():获取值的数量
这三个合约函数都很简单,因此也很容易进行单元测试。
下面是EntryPoint合约的Solidity代码:
pragma solidity >=0.5.0; import "./Background.sol"; contract EntryPoint { address public backgroundAddress; constructor(address _background) public{ backgroundAddress = _background; } function getBackgroundAddress() public view returns (address) { return backgroundAddress; } function storeTwoValues(uint first, uint second) public { Background(backgroundAddress).storeValue(first); Background(backgroundAddress).storeValue(second); } function getNumberOfValues() public view returns (uint) { return Background(backgroundAddress).getNumberOfValues(); } }
在EntryPoint合约的构造函数中,我们注入了Background合约的部署地址,并将其存入一个状态变量backgroundAddress。EntryPoint合约暴露出三个函数:
- getBackgroundAddress():返回Background合约的部署地址
- storeTwoValues(uint, uint):保存两个值
- getNumberOfValues():返回值的数量
由于storeTwoValues(uint, uint)函数两次调用Background合约中的一个函数,因此对这个函数进行单元测试比较困难。getNumberOfValues()也有同样的问题,因此这两个函数更适合进行集成测试。
3、以太坊智能合约测试的Solidity用例
在这一部分,我们学习如何为智能合约编写Solidity单元测试用例和集成测试用例。让我们先从简单一点的单元测试开始。
下面是TestBackground测试的代码:
pragma solidity >=0.5.0; import "truffle/Assert.sol"; import "truffle/DeployedAddresses.sol"; import "../../../contracts/Background.sol"; contract TestBackground { Background public background; // Run before every test function function beforeEach() public { background = new Background(); } // Test that it stores a value correctly function testItStoresAValue() public { uint value = 5; background.storeValue(value); uint result = background.getValue(0); Assert.equal(result, value, "It should store the correct value"); } // Test that it gets the correct number of values function testItGetsCorrectNumberOfValues() public { background.storeValue(99); uint newSize = background.getNumberOfValues(); Assert.equal(newSize, 1, "It should increase the size"); } // Test that it stores multiple values correctly function testItStoresMultipleValues() public { for (uint8 i = 0; i < 10; i++) { uint value = i; background.storeValue(value); uint result = background.getValue(i); Assert.equal(result, value, "It should store the correct value for multiple values"); } } }
这个单元测试的目的是确保Background合约可以:
- 在values数组中保存新的值
- 按索引返回values
- 在values数组中保存多个值
- 返回values数组的大小
下面的TestEntryPoint测试中包含了一个单元测试testItHasCorrectBackground() 用于验证EntryPoint合约的功能符合预期:
pragma solidity >=0.5.0; import "truffle/Assert.sol"; import "truffle/DeployedAddresses.sol"; import "../../../contracts/Background.sol"; import "../../../contracts/EntryPoint.sol"; contract TestEntryPoint { // Ensure that dependency injection working correctly function testItHasCorrectBackground() public { Background backgroundTest = new Background(); EntryPoint entryPoint = new EntryPoint(address(backgroundTest)); address expected = address(backgroundTest); address target = entryPoint.getBackgroundAddress(); Assert.equal(target, expected, "It should set the correct background"); } }
这个函数对依赖注入进行测试。如前所述,EntryPoint合约中的其他函数需要与Background合约交互,因此我们没有办法单独测试这些函数,需要在集成测试中进行验证。下面是集成测试的代码:
pragma solidity >=0.5.0; import "truffle/Assert.sol"; import "truffle/DeployedAddresses.sol"; import "../../../contracts/Background.sol"; import "../../../contracts/EntryPoint.sol"; contract TestIntegrationEntryPoint { BackgroundTest public backgroundTest; EntryPoint public entryPoint; // Run before every test function function beforeEach() public { backgroundTest = new BackgroundTest(); entryPoint = new EntryPoint(address(backgroundTest)); } // Check that storeTwoValues() works correctly. // EntryPoint contract should call background.storeValue() // so we use our mock extension BackgroundTest contract to // check that the integration workds function testItStoresTwoValues() public { uint value1 = 5; uint value2 = 20; entryPoint.storeTwoValues(value1, value2); uint result1 = backgroundTest.values(0); uint result2 = backgroundTest.values(1); Assert.equal(result1, value1, "Value 1 should be correct"); Assert.equal(result2, value2, "Value 2 should be correct"); } // Check that entry point calls our mock extension correctly // indicating that the integration between contracts is working function testItCallsGetNumberOfValuesFromBackground() public { uint result = entryPoint.getNumberOfValues(); Assert.equal(result, 999, "It should call getNumberOfValues"); } } // Extended from Background because values is private in actual Background // but we're not testing background in this unit test contract BackgroundTest is Background { uint[] public values; function storeValue(uint value) public { values.push(value); } function getNumberOfValues() public view returns(uint) { return 999; } }
我们可以看到TestIntegrationEntryPoint使用了一个Background的扩展,即定义在第43行的BackgroundTest,以其作为我们的模拟合约,这可以让我们的测试用例检查EntryPoint是否调用了部署在backgroundAddress地址处的合约的正确的函数。
4、以太坊智能合约测试的JavaScript用例
我们用JavaScript编写集成测试来确保合约的外部行为满足预期要求,这样我们就有信息基于这些智能合约开发DApp了。
下面是我们的JavaScript测试文件entryPoint.test.js:
const EntryPoint = artifacts.require("./EntryPoint.sol"); require('chai') .use(require('chai-as-promised')) .should(); contract("EntryPoint", accounts => { describe("Storing Values", () => { it("Stores correctly", async () => { const entryPoint = await EntryPoint.deployed(); let numberOfValues = await entryPoint.getNumberOfValues(); numberOfValues.toString().should.equal("0"); await entryPoint.storeTwoValues(2,4); numberOfValues = await entryPoint.getNumberOfValues(); numberOfValues.toString().should.equal("2"); }); }); });
使用EntryPoint合约中的函数,JavaScript测试可以确保我们可以将区块链外部的值利用交易传入智能合约,这是通过调用合约的storeTwoValues(uint,uint)函数(第15行)实现的。
5、以太坊智能合约测试教程小节
当谈到智能合约的测试时,可以说越多越好,应当尽可能覆盖所有可能的执行路径都返回预期的结果。Truffle提供了两种办法:区块链层的Solidity单元测试和集成测试,以及DApp级别的JavaScript集成测试,我们在实际的工作中需要根据智能合约的代码实现综合运用这两种测试方法来保证智能合约的运行符合预期。
原文链接:以太坊智能合约测试的两种方法 — 汇智网
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
从linux源码看socket(tcp)的timeout
从linux源码看socket(tcp)的timeout 前言 网络编程中超时时间是一个重要但又容易被忽略的问题,对其的设置需要仔细斟酌。在经历了数次物理机宕机之后,笔者详细的考察了在网络编程(tcp)中的各种超时设置,于是就有了本篇博文。本文大部分讨论的是socket设置为block的情况,即setNonblock(false),仅在最后提及了nonblock socket(本文基于linux 2.6.32-431内核)。 connectTimeout 在讨论connectTimeout之前,让我们先看下java和C语言对于socket connect调用的函数签名: java: // 函数调用中携带有超时时间 public void connect(SocketAddress endpoint, int timeout) ; C语言: // 函数调用中并不携带超时时间 int connect(int sockfd, const struct sockaddr * sockaddr, socklen_t socklent) 操作系统提供的connect系统调用并没有提供timeout...
- 下一篇
面试必问的时间复杂度到底怎么算
高级工程师title的我,最近琢磨着好好刷刷算法题更高级一些,然鹅,当我准备回忆大学和面试时候学的数据结构之时,我发现自己对这个算法复杂度的记忆只有OOOOOooo 文章收录在 GitHub JavaKeeper ,N线互联网开发必备技能兵器谱 算法(Algorithm)是指用来操作数据、解决程序问题的一组方法。对于同一个问题,使用不同的算法,也许最终得到的结果是一样的,但在过程中消耗的资源和时间却会有很大的区别。 那么我们应该如何去衡量不同算法之间的优劣呢? 主要还是从算法所占用的「时间」和「空间」两个维度去考量。 时间维度:是指执行当前算法所消耗的时间,我们通常用「时间复杂度」来描述。 空间维度:是指执行当前算法需要占用多少内存空间,我们通常用「空间复杂度」来描述。 因此,评价一个算法的效率主要是看它的时间复杂度和空间复杂度情况。然而,有的时候时间和空间却又是「鱼和熊掌」,不可兼得的,那么我们就需要从中去取一个平衡点。 时间复杂度 一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但我们不可能也没有必要对每个算法都上机测试,只需知道哪个算法花费的时间多,哪...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- CentOS8编译安装MySQL8.0.19
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Docker安装Oracle12C,快速搭建Oracle学习环境