如何使用 Solidity 和 JavaScript 测试智能合约【译】

栏目: IT技术 · 发布时间: 4年前

内容简介:Truffle 框架支持 Solidity和 JavaScript 编写测试用例,本文介绍了他们的区别与应用场景。本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

Truffle 框架支持 Solidity和 JavaScript 编写测试用例,本文介绍了他们的区别与应用场景。

阅读本文需要对区块链,以太坊,JavaScript 有所了解。

所有的代码可以在 Github

软件测试的重要性

如果您希望代码按照预期的方式工作,那么软件测试至关重要。

软件测试有两种常规类型: 单元测试集成测试

  • 单元测试 地关注每个独立的功能。
  • 集成测试 重点在于确保代码的多个部分按预期在一起工作。

区块链软件也不例外。 而且由于 不可变性 ,区块链应用程序需要更多地强调测试。

区块链测试

Truffle 开发框架为我们提供了两种测试Solidity智能合约的途径:Solidity测试和JavaScript测试。 问题是,我们应该使用哪个?

答案是都需要。

如何使用 Solidity 和 JavaScript 测试智能合约【译】

Solidity 测试

用Solidity编写智能合约的测试用例让我们可以在区块链层级进行测试。这种测试用例可以调用合约方法,就像用例部署在区块链里一样。为了测试智能合约的内部行为,我们可以:

  • 编写Solidity 单元测试 来检查智能合约函数的返回值以及状态变量的值。
  • 编写Solidity 集成测试 来检查智能合约之间的交互。这些集成测试可以确保像继承或者依赖注入这样的机制的运行符合预期

JavaScript 测试

我们也需要确保智能合约能够表现出正确的外部行为。为了从区块链外部测试智能合约,我们在JavaScript测试用例中使用web3.js,就像在 开发DApp时一样。我们需要有信心对DApp前端可以正确调用智能合约。 这方面的测试属于集成测试。

示例项目

项目的网站代码 Github

我们有两个合约: 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 合约的部署地址,并将其存入一个状态变量 backgroundAddressEntryPoint 合约暴露出三个函数:

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。

进一步阅读

如果你觉得本文对你用户,下面的文章也可能对你有用:

原文链接:https://medium.com/better-programming/how-to-test-ethereum-smart-contracts-35abc8fa199d

阅读本文需要对区块链,以太坊,JavaScript 有所了解。

所有的代码可以在 Github

软件测试的重要性

如果您希望代码按照预期的方式工作,那么软件测试至关重要。

软件测试有两种常规类型: 单元测试集成测试

  • 单元测试 地关注每个独立的功能。
  • 集成测试 重点在于确保代码的多个部分按预期在一起工作。

区块链软件也不例外。 而且由于 不可变性 ,区块链应用程序需要更多地强调测试。

区块链测试

Truffle 开发框架为我们提供了两种测试Solidity智能合约的途径:Solidity测试和JavaScript测试。 问题是,我们应该使用哪个?

答案是都需要。

如何使用 Solidity 和 JavaScript 测试智能合约【译】

Solidity 测试

用Solidity编写智能合约的测试用例让我们可以在区块链层级进行测试。这种测试用例可以调用合约方法,就像用例部署在区块链里一样。为了测试智能合约的内部行为,我们可以:

  • 编写Solidity 单元测试 来检查智能合约函数的返回值以及状态变量的值。
  • 编写Solidity 集成测试 来检查智能合约之间的交互。这些集成测试可以确保像继承或者依赖注入这样的机制的运行符合预期

JavaScript 测试

我们也需要确保智能合约能够表现出正确的外部行为。为了从区块链外部测试智能合约,我们在JavaScript测试用例中使用web3.js,就像在 开发DApp时一样。我们需要有信心对DApp前端可以正确调用智能合约。 这方面的测试属于集成测试。

示例项目

项目的网站代码 Github

我们有两个合约: 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 合约的部署地址,并将其存入一个状态变量 backgroundAddressEntryPoint 合约暴露出三个函数:

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。

进一步阅读

如果你觉得本文对你用户,下面的文章也可能对你有用:

原文链接: https://medium.com/better-programming/how-to-test-ethereum-smart-contracts-35abc8fa199d

本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

  • 发表于 22分钟前
  • 阅读 ( 9 )
  • 学分 ( 0 )
  • 分类:以太坊

以上所述就是小编给大家介绍的《如何使用 Solidity 和 JavaScript 测试智能合约【译】》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

赛博人

赛博人

[美]约翰·苏勒尔 / 刘淑华、张海会 / 中信出版集团 / 2018-7 / 88.00

随着数字时代的飞速发展,网络空间正在深深影响着我们每个人的思想、感受和网络行为,其对我们的影响甚至比在现实生活中更大。为全面解析人类在网络空间中的感知、感觉、思维以及行为方式,帮助我们应对生活中面临的各种挑战,促进个人成长和改善心理健康,网络心理学专家和学科奠基人约翰·R.苏勒尔,根据20多年在不同网络环境里进行参与-观察式的实地调查所获得的成果,综合运用了行为心理学、认知心理学、人本主义心理学和......一起来看看 《赛博人》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具