其他
深聊 Solidity 的测试场景、方法和实践,太详细了,必须收藏!
The following article is from FISCO BCOS开源社区 Author 毛嘉宇
控制台:提供命令行交互界面,通过在控制台创建合约和输入调用、查询指令,来进行简单调试。适用于非常简单的合约。 WeBASE-Front:提供可视化交互界面以及简易的IDE环境。适用于业务逻辑并不复杂的合约,更推荐开发人员进行一些调试。 SDK:例如集成Java Web3sdk,创建一个Java项目,并编写应用和测试代码。适用于对智能合约质量要求较高、要求测试案例可复用、业务逻辑复杂或要求持续集成的场景。
pragma solidity ^0.4.25;
contract HelloWorld{
string public name;
constructor() public{
name = "Hello, World!";
}
function set(string n) public{
name = n;
}
}
=============================================================================================
Welcome to FISCO BCOS console(1.0.8)!
Type 'help' or 'h' for help. Type 'quit' or 'q' to quit console.
________ ______ ______ ______ ______ _______ ______ ______ ______
| | \/ \ / \ / \ | \ / \ / \ / \
| $$$$$$$$\$$$$$| $$$$$$| $$$$$$| $$$$$$\ | $$$$$$$| $$$$$$| $$$$$$| $$$$$$\
| $$__ | $$ | $$___\$| $$ \$| $$ | $$ | $$__/ $| $$ \$| $$ | $| $$___\$$
| $$ \ | $$ \$$ \| $$ | $$ | $$ | $$ $| $$ | $$ | $$\$$ \
| $$$$$ | $$ _\$$$$$$| $$ __| $$ | $$ | $$$$$$$| $$ __| $$ | $$_\$$$$$$\
| $$ _| $$_| \__| $| $$__/ | $$__/ $$ | $$__/ $| $$__/ | $$__/ $| \__| $$
| $$ | $$ \\$$ $$\$$ $$\$$ $$ | $$ $$\$$ $$\$$ $$\$$ $$
\$$ \$$$$$$ \$$$$$$ \$$$$$$ \$$$$$$ \$$$$$$$ \$$$$$$ \$$$$$$ \$$$$$$
=============================================================================================
[group:1]> deploy HelloWorld
contract address: 0x34e95689e05255d160fb96437a11ba97bb31809f
[group:1]>
[group:1]> call HelloWorld 0x34e95689e05255d160fb96437a11ba97bb31809f name
Hello, World!
[group:1]> call HelloWorld 0x34e95689e05255d160fb96437a11ba97bb31809f set "Hello, test!"
transaction hash: 0x72c8a95b651fb5d12c44b69624d5213d58af1509f00920757fce94d019b5eae8
[group:1]> call HelloWorld 0x34e95689e05255d160fb96437a11ba97bb31809f name
Hello, test!
[group:1]>
pragma solidity ^0.4.25;
contract BasicAuth {
address public _owner;
constructor() public {
_owner = msg.sender;
}
function setOwner(address owner)
public
onlyOwner
{
_owner = owner;
}
modifier onlyOwner() {
require(msg.sender == _owner, "BasicAuth: only owner is authorized.");
_;
}
}
Automatic:测试应该被全自动执行,这也是实现持续集成的前提。 Independent:测试案例彼此保持独立,不存在相互依赖和调用。 Repeatable:测试案例必须可被复用。可以跨越软硬件环境,被重复执行。
编写合约:HelloWorld合约。 编译智能合约,将其转为Java文件,从上述WeBASE-Front中,也能编译并导出Java文件。 将编译生成的Java文件导入工程中,如HelloWorld.java。 基于上述文件,调用合约功能和接口,编写相关测试案例,例如:ContractTest。 基于Spring提供的gradle插件,我们可以通过"./gradlew test"命令来运行所有测试案例。 如果需要持续集成,可以在配置和初始化FISCO BCOS后,将步骤5命令加入自动化脚本中。
@Test
public void deployAndCallHelloWorld() throws Exception {
// deploy contract
HelloWorld helloWorld = HelloWorld
.deploy(web3j, credentials, new StaticGasProvider(GasConstants.GAS_PRICE, GasConstants.GAS_LIMIT))
.send();
Assert.assertNotNull(helloWorld);
// call set function
helloWorld.set("Hello, World!").send();
// call get function
String result = helloWorld.get().send();
Assert.assertTrue("Hello, World!".equals(result));
}
第4行中,HelloWorld合约被部署。为符合独立性原则,建议在每个测试案例中部署独立的合约,以避免测试案例执行顺序对正常测试造成干扰。需要模拟合约依赖性的情况除外。 第9行和第11行,分别调用了set和get。为符合可重复性原则,该测试案例必须设计成幂等,即在任意的软硬件环境下,测试案例预期结果都是一致的。 第7行和第12行,使用了Junit框架提供的断言方法,用来判断智能合约执行结果是否符合预期。
TransactionReceipt tr = helloWorld.set("Hello, World!").send();
Assert.assertEquals(tr.getMessage(), "0x0", tr.getStatus());
pragma solidity 0.4.25;
library LibAssert {
event LogTest(bool indexed result, string message);
function equal(uint8 a, uint8 b, string message) internal returns (bool result) {
result = (a == b);
log(result, message);
}
function notEqual(uint8 a, uint8 b, string message) internal returns (bool result) {
result = (a != b);
log(result, message);
}
function log(bool result, string message) internal {
if(result)
emit LogTest(true, "");
else
emit LogTest(false, message);
}
}
pragma solidity ^0.4.25;
import "./Uint8MathTest.sol";
import "./LibAssert.sol";
contract TestDemo {
using LibAssert for uint8;
function testAdd() external returns (bool result) {
uint8 a = 1;
uint8 b = 1;
Uint8MathTest math = new Uint8MathTest();
uint8 c = math.add(a,b);
uint8 expectedValue = 2;
result = expectedValue.equal(c, "Not equal");
}
}
[group:1]> deploy TestDemo
contract address: 0xd931b41c70d2ff7b54eb9b2453072f9b1a4dc05c
[group:1]> call TestDemo 0xd931b41c70d2ff7b54eb9b2453072f9b1a4dc05c testAdd
transaction hash: 0xe569e5d8eae1b949a0ffe27a60f0b4c8bd839f108648f9b18879833c11e94ee4
---------------------------------------------------------------------------------------------
Output
function: testAdd()
return type: (bool)
return value: (true)
---------------------------------------------------------------------------------------------
Event logs
---------------------------------------------------------------------------------------------
针对具体合约场景测试,存在大量且重复的测试代码,费时费力; 性能指标缺乏统一度量,无法横向对比; 展示结果不够直观。
start compile!
compile over!
start deploy!
deploy over!
start execute function name: set(string)
execute function name : set(string) end!
No problem was found in the contract
---end---
####### Compile Problem #######
file: /Users/maojiayu/Downloads/Uint8MathTest.sol
title: Compile Problem
description: Function state mutability can be restricted to pure
code: function add(uint8 a, uint8 b) public returns (uint8 c) {
warningType: Info
line: 5
注意边界值的测试,例如数字溢出、特殊取值、循环边界值等。 注意检查智能合约的实现是否符合预期,其运行逻辑和流程是否与设计一致。 除了正常流程外,还需要模拟和测试在各种异常环境,甚至是极端环境下,智能合约运作是否正常,能否达到预期的处理结果。 围绕智能合约不变的业务逻辑,忽略变化值,进行对应测试。
pragma solidity 0.4.25;
contract Uint8MathTest {
function add(uint8 a, uint8 b) public returns (uint8 c) {
c = a + b;
}
}
[group:1]> deploy Uint8MathTest
contract address: 0xd8a765995c58eb8da103bdcc2c033c0acb81e373
[group:1]> call Uint8MathTest 0xd8a765995c58eb8da103bdcc2c033c0acb81e373 add 255 1
transaction hash: 0x0583185839bc52870ac57cdfd00c0c18840c2674d718b4cc3cb7bc1ef4c173e0
---------------------------------------------------------------------------------------------
Output
function: add(uint8,uint8)
return type: (uint8)
return value: (0)
---------------------------------------------------------------------------------------------
pragma solidity ^0.4.25;
contract HelloWorld{
string public name;
event LogSet(string oldValue, string newValue, address sender);
constructor() public{
name = "Hello, World!";
}
function set(string n) public{
emit LogSet(n, name, msg.sender);
name = n;
}
}
account_info: 记录了所有在链上部署的合约信息,包括了合约名、合约地址、块高、上链时间等。 blcok_detail_info: 记录了每个区块的信息,包括区块hash、交易数量、块高、上链时间等。 block_task_pool:数据导出的任务表,记录了任务详情和状态信息。 block_tx_detail_info:记录了每笔交易的信息,包括了交易的合约名称、函数名称、交易发送者地址、交易接收者地址、交易hash等。 hello_world_log_set_event:数据导出会自动生成每个event的数据库表,命名规则为合约名+事件名。上面例子中定义的event所自动生成的表,包括了event中定义的所有变量和交易信息等。 hello_world_set_method:数据导出会自动生成不同transaction的数据库表,命名规则为合约名+函数名。上面例子中定义的set函数表,包含了函数的入参、返回码和交易hash等。
系列专题 1:智能合约初探:概念与演变系列专题 2:智能合约编写之Solidity的基础特性系列专题 3:智能合约编写之Solidity的高级特性系列专题 4:智能合约编写之 Solidity 的设计模式系列专题 5:万字好文:智能合约编写之Solidity的编程攻略,建议收藏!系列专题 6:硬核干货!智能合约编写之Solidity运行原理,内核原理理解+实战解析看到过瘾!
同时,欢迎所有开发者扫描下方二维码填写《开发者与AI大调研》,只需2分钟,便可收获价值299元的「AI开发者万人大会」在线直播门票!
推荐阅读
老铁们求在看!👇