内容简介:Truffle 框架支持 Solidity和 JavaScript 编写测试用例,本文介绍了他们的区别与应用场景。本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。
Truffle 框架支持 Solidity和 JavaScript 编写测试用例,本文介绍了他们的区别与应用场景。
阅读本文需要对区块链,以太坊,JavaScript 有所了解。
所有的代码可以在 Github
软件测试的重要性
如果您希望代码按照预期的方式工作,那么软件测试至关重要。
软件测试有两种常规类型: 单元测试 和 集成测试 。
- 单元测试 地关注每个独立的功能。
- 集成测试 重点在于确保代码的多个部分按预期在一起工作。
区块链软件也不例外。 而且由于 不可变性 ,区块链应用程序需要更多地强调测试。
区块链测试
Truffle 开发框架为我们提供了两种测试Solidity智能合约的途径:Solidity测试和JavaScript测试。 问题是,我们应该使用哪个?
答案是都需要。
Solidity 测试
用Solidity编写智能合约的测试用例让我们可以在区块链层级进行测试。这种测试用例可以调用合约方法,就像用例部署在区块链里一样。为了测试智能合约的内部行为,我们可以:
- 编写Solidity 单元测试 来检查智能合约函数的返回值以及状态变量的值。
- 编写Solidity 集成测试 来检查智能合约之间的交互。这些集成测试可以确保像继承或者依赖注入这样的机制的运行符合预期
JavaScript 测试
我们也需要确保智能合约能够表现出正确的外部行为。为了从区块链外部测试智能合约,我们在JavaScript测试用例中使用web3.js,就像在 开发DApp时一样。我们需要有信心对DApp前端可以正确调用智能合约。 这方面的测试属于集成测试。
示例项目
我们有两个合约: Background
and EntryPoint
需要测试.
Background
是一个内部合约,DApp前端不会直接和它交互。 EntryPoint
则是设计作为供DApp交互的智能合约,在 EntryPoint
合约会引用 Background
合约。
合约
Background合约代码如下:
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.sol 合约代码如下:
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() storeTwoValues(uint, uint) getNumberOfValues()
storeTwoValues(uint, uint)
函数调用两次 Background
合约中的函数,因此对这个函数进行独立单元测试比较困难。 getNumberOfValues()
也有同样的问题,因此这两个函数更适合进行集成测试。
Solidity 测试用例
在 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; // 在每个测试函数之前运行 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; // 在测试用例之前运行 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
地址处的合约。
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行)实现的。
通过在测试的第12行和第16行调用 getNumberOfValues()
来检索存储在区块链中的值的数量,以确保和存储的值一致。
结论
在测试智能合约时,越多越好。 应该不遗余力确保所有可能的执行路径返回预期结果。 将链级Solidity测试用于单元测试和集成测试,并将Javascript测试用于DApp级别的集成测试。
该项目中有些地方可能要编写更多的单元或集成测试,因此,如果您认为可以添加到该项目中,请向 代码库 提交 Pull request。
进一步阅读
如果你觉得本文对你用户,下面的文章也可能对你有用:
-
了解如何使用Truffle将智能合约部署到公共测试网络(英文) .
这里有一篇中文文章介绍 使用Truffle将智能合约部署到公共测试网络 .
-
确保智能合约免受黑客攻击,了解 可重入攻击和所有者逻辑盗窃攻击以及如何防止它们 .
原文链接:https://medium.com/better-programming/how-to-test-ethereum-smart-contracts-35abc8fa199d
阅读本文需要对区块链,以太坊,JavaScript 有所了解。
所有的代码可以在 Github
软件测试的重要性
如果您希望代码按照预期的方式工作,那么软件测试至关重要。
软件测试有两种常规类型: 单元测试 和 集成测试 。
- 单元测试 地关注每个独立的功能。
- 集成测试 重点在于确保代码的多个部分按预期在一起工作。
区块链软件也不例外。 而且由于 不可变性 ,区块链应用程序需要更多地强调测试。
区块链测试
Truffle 开发框架为我们提供了两种测试Solidity智能合约的途径:Solidity测试和JavaScript测试。 问题是,我们应该使用哪个?
答案是都需要。
Solidity 测试
用Solidity编写智能合约的测试用例让我们可以在区块链层级进行测试。这种测试用例可以调用合约方法,就像用例部署在区块链里一样。为了测试智能合约的内部行为,我们可以:
- 编写Solidity 单元测试 来检查智能合约函数的返回值以及状态变量的值。
- 编写Solidity 集成测试 来检查智能合约之间的交互。这些集成测试可以确保像继承或者依赖注入这样的机制的运行符合预期
JavaScript 测试
我们也需要确保智能合约能够表现出正确的外部行为。为了从区块链外部测试智能合约,我们在JavaScript测试用例中使用web3.js,就像在 开发DApp时一样。我们需要有信心对DApp前端可以正确调用智能合约。 这方面的测试属于集成测试。
示例项目
我们有两个合约: Background
and EntryPoint
需要测试.
Background
是一个内部合约,DApp前端不会直接和它交互。 EntryPoint
则是设计作为供DApp交互的智能合约,在 EntryPoint
合约会引用 Background
合约。
合约
Background合约代码如下:
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.sol 合约代码如下:
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() storeTwoValues(uint, uint) getNumberOfValues()
storeTwoValues(uint, uint)
函数调用两次 Background
合约中的函数,因此对这个函数进行独立单元测试比较困难。 getNumberOfValues()
也有同样的问题,因此这两个函数更适合进行集成测试。
Solidity 测试用例
在 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; // 在每个测试函数之前运行 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; // 在测试用例之前运行 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
地址处的合约。
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行)实现的。
通过在测试的第12行和第16行调用 getNumberOfValues()
来检索存储在区块链中的值的数量,以确保和存储的值一致。
结论
在测试智能合约时,越多越好。 应该不遗余力确保所有可能的执行路径返回预期结果。 将链级Solidity测试用于单元测试和集成测试,并将Javascript测试用于DApp级别的集成测试。
该项目中有些地方可能要编写更多的单元或集成测试,因此,如果您认为可以添加到该项目中,请向 代码库 提交 Pull request。
进一步阅读
如果你觉得本文对你用户,下面的文章也可能对你有用:
-
了解如何使用Truffle将智能合约部署到公共测试网络(英文) .
这里有一篇中文文章介绍 使用Truffle将智能合约部署到公共测试网络 .
-
确保智能合约免受黑客攻击,了解 可重入攻击和所有者逻辑盗窃攻击以及如何防止它们 .
原文链接: https://medium.com/better-programming/how-to-test-ethereum-smart-contracts-35abc8fa199d
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。
- 发表于 22分钟前
- 阅读 ( 9 )
- 学分 ( 0 )
- 分类:以太坊
以上所述就是小编给大家介绍的《如何使用 Solidity 和 JavaScript 测试智能合约【译】》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。