美文网首页
测试以太坊智能合约

测试以太坊智能合约

作者: Jisen | 来源:发表于2019-01-11 11:49 被阅读10次

    了解一下本文所讲内容:

    1. 如何用truffle建立测试环境
    2. 如何在javascript中编写测试并在测试网络中执行它们
    3. 你必须在任何合约中测试的5件事

    如何用truffle建立测试环境

    truffle初始化之后,生成的test文件夹,你在javascript或solidity中编写所有测试的主文件夹。
    建议坚持使用javascript编写测试,因为它是测试合约的最快速、最简单的方法。

    如何编写测试并在测试网络中执行它们

    下面是示例合约:

    pragma solidity 0.4.20;
    contract TodoList {
       struct Todo {
          uint256 id;
          bytes32 content;
          address owner;
          bool isCompleted;
          uint256 timestamp;
       }
       
       uint256 public constant maxAmountOfTodos = 100;
    
       // Owner => todos
       mapping(address => Todo[maxAmountOfTodos]) public todos;
       // Owner => last todo id
       mapping(address => uint256) public lastIds;
       modifier onlyOwner(address _owner) {
          require(msg.sender == _owner);
          _;
       }
       // Add a todo to the list
       function addTodo(bytes32 _content) public {
          Todo memory myNote = Todo(lastIds[msg.sender], _content, msg.sender, false, now);
          todos[msg.sender][lastIds[msg.sender]] = myNote;
          if(lastIds[msg.sender] >= maxAmountOfTodos) lastIds[msg.sender] = 0;
          else lastIds[msg.sender]++;
       }
       // Mark a todo as completed
       function markTodoAsCompleted(uint256 _todoId) public onlyOwner(todos[msg.sender][_todoId].owner) {
          require(_todoId < maxAmountOfTodos);
          require(!todos[msg.sender][_todoId].isCompleted);
          
          todos[msg.sender][_todoId].isCompleted = true;
       }
    }
    

    转到test/文件夹并创建一个名为todoList.js的文件,第一个字母是小写,它是一个javascript文件。名称必须与要测试的合约名称相同。

    在这个测试文件中,首先导入合约合和库以检查测试条件。在测试文件中写下:

    const TodoList = artifacts.require('./ TodoList.sol')
    const assert = require('assert')
    

    TodoList它只是将智能合约中的代码转换为在此使用它的变量。assert是nodejs库,允许检查每个测试的条件。

    现在,创建一个名为contractInstance的变量。

    let contractInstance
    

    在assert初始化下面。合约实例变量将包含稍后将使用的合约实例。

    现在创建您将要测试的合约容器:

    contract('TodoList', (accounts) => {
    })
    

    名称Todolist只是合约的名称,可以使用想要的任何文本,因为这只是为了让你知道当时正在执行的内容。

    添加beforeEach:

    contract('TodoList', (accounts) => {
       beforeEach (async() => {
           contractInstance = await TodoList.deployed();
       });
    });
    

    该beforeEach函数将在每次测试之前执行,在其中我们只是使用deployed()方法部署新的TodoList合约。

    现在可以添加测试了。

    每个测试都应验证某个功能在某些条件下是否正常工作。在这个合约中,有一个叫做addTodo的函数,它只是在notes数组中添加一个note。所以下面开始测试:

    contract('TodoList', (accounts) => {
       beforeEach(async () => {
          contractInstance = await TodoList.deployed()
       })
       it('should add a to-do note successfully with a short text of 20 letters',async() => {
           await contractInstance.addTodo(web3.toHex('this is a short text'));
    
           const newAddedTodo = await contractInstance.todos(accounts[0],0);
           const todoContent = web3.toUtf8(newAddedTodo[1]);
    
           assert.equal(todoContent, 'this is a short text','The content of the new added todo is not correct');
       });
       })
    })
    

    这个测试的步骤细分如下:

    1. 每个测试都以函数开始,该函数it()包含测试的简短精确描述和回调函数。在这种情况下,回调函数时async,因为我想使用await修饰符,这将允许我使用更干净的代码更轻松地进行测试。如果你不熟悉callbacks、promises、async和await关键字,请查看:Callbacks, Promises and Async/Await。web3.toHex()函数的作用是将文本转换成十六进制,以便在合约中存储。
    2. 然后开始addTodo()的测试。我们希望添加带有短文本的待办事项,然后查看它是否实际存储在智能合约上。await关键字允许我等待直到函数完成添加待办事项,否则它会在后台处理时继续执行代码。
    3. 添加note后,检查智能合约的todos变量,看看note是否在那里。因为todos变量是public的,所以我可以在不使用任何附加函数的情况下执行它。它接收到owner的note和该note的索引(在本例中为0),这是第一个。
    4. 因为我将note存储在bytes32类型的变量中,所以可以存储的文本数量限制为32字符,并且必须是十六进制文本。因此,当我试图取回内容时,我收到一个十六进制字符串,它由随机数和字母组成,如下所示:0x74686924852857424513218979854654530000000000。实际上用web3.toUtf8()函数将十六进制转换为人类可读文本,并将其存储在一个名为todoContent的变量中。
    5. 最后,检查存储在智能合约的note内容是否正确,因为默认情况下,所有bytes32都有一个null十六进制。当值不相等时,assert.equal()函数抛出异常,破坏测试。如果它们相等,则测试正确。

    接下来,为合约的剩余功能添加测试:

    it('should mark one of your to-dos as completed', async () => {
       await contractInstance.addTodo('example')
       await contractInstance.markTodoAsCompleted(0)
       const lastTodoAdded = await contractInstance.todos(accounts[0], 0)
       const isTodoCompleted = lastTodoAdded[3] // 3 is the bool isCompleted value of the todo note
       assert(isTodoCompleted, 'The todo should be true as completed')
    })
    

    使用npm安装ganache-cli,(或者直接下载Mac客户端,并打开ganache):

    npm i -g ganache-cli
    

    然后在truffle-config.js中配置:

    module.exports = {
        networks: {
            localnode: {
                network_id: "5777",
                host: "127.0.0.1",
                port: 7545,
            }
        }
    };
    

    打开终端,转到项目文件夹并执行以下操作启动测试:

    truffle test --localnode
    

    如果测试正确,那么将全部passing

    测试单个文件:

    truffle test ./test/todoList.js --localnode
    

    你必须在任何合约测试的5件事

    • 始终检查溢出和下溢。如果要进行任何类型的数学计算,则必须确保代码不会溢出或下溢。这些只是意味着你超过了一种unit变量的容量,因此该变量的值重置并尝试存储一个巨大的数字后再次变为0。
    function sumNumbers(uint256 numberA, uint256 numberB) public view returns(uint256) {
       return numberA + numberB;
    }
    
    // 这个函数就存在溢出的风险。在这种情况下,可以编写一个这样的测试,通过检查最终值来测试总和是否溢出:
    
    it('the sum should not overflow', async () => {
       try {
          // Trying to sum 2^256 + 5, which should overflow and throw an exception in the best case
          const sumResult = contractInstance.sumNumbers(2e256, 5)
          assert.ok(false, 'The contract should throw an exception to avoid overflowing and thus making bad calculations')
       } catch(error) {
          assert.ok(true, 'The contract is throwing which is the expected behaviour when you try to overflow')
       }
    })
    
    
    • 检查功能的返回值是否始终在预期值的范围内。例如,如果你有一个预期会返回大于0的数字的函数,请强制返回0的位置进行测试以查看它是否拒绝该情况。
    • 始终测试功能的限制。如果一个函数返回一个数字,那么写一个测试,用最大可能的数字执行它,另一个测试用尽可能小的数字,还有一个测试用中间的随机值。你永远不会知道你的功能在意外情况下会如何反应。
    • 确保返回值正确格式化。如果有一个应该返回数字数组的函数,请检查该数组是否返回空的任何情况。这很重要,因为它可能会破坏dapp的功能。
    • 确保参数拒绝无效值。必须确保合约已准备好用于功能参数的所有可能值,以避免安全风险。
    // 比如这样参数的函数:
    
    function doSomething (string randomText,uint256 randomNumber) public {}
    
    // 写一个字符串为空的测试,写另一个字符串时一万字的大量文本,写一个测试,其中unit为零,一个是负数,一个是一个巨大的数字。
    

    参考

    相关文章

      网友评论

          本文标题:测试以太坊智能合约

          本文链接:https://www.haomeiwen.com/subject/zutxdqtx.html