測試以太坊智能合約

了解一下本文所講內(nèi)容:

  1. 如何用truffle建立測試環(huán)境
  2. 如何在javascript中編寫測試并在測試網(wǎng)絡中執(zhí)行它們
  3. 你必須在任何合約中測試的5件事

如何用truffle建立測試環(huán)境

truffle初始化之后,生成的test文件夾,你在javascript或solidity中編寫所有測試的主文件夾。
建議堅持使用javascript編寫測試,因為它是測試合約的最快速、最簡單的方法。

如何編寫測試并在測試網(wǎng)絡中執(zhí)行它們

下面是示例合約:

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/文件夾并創(chuàng)建一個名為todoList.js的文件,第一個字母是小寫,它是一個javascript文件。名稱必須與要測試的合約名稱相同。

在這個測試文件中,首先導入合約合和庫以檢查測試條件。在測試文件中寫下:

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

TodoList它只是將智能合約中的代碼轉換為在此使用它的變量。assert是nodejs庫,允許檢查每個測試的條件。

現(xiàn)在,創(chuàng)建一個名為contractInstance的變量。

let contractInstance

在assert初始化下面。合約實例變量將包含稍后將使用的合約實例。

現(xiàn)在創(chuàng)建您將要測試的合約容器:

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

名稱Todolist只是合約的名稱,可以使用想要的任何文本,因為這只是為了讓你知道當時正在執(zhí)行的內(nèi)容。

添加beforeEach:

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

該beforeEach函數(shù)將在每次測試之前執(zhí)行,在其中我們只是使用deployed()方法部署新的TodoList合約。

現(xiàn)在可以添加測試了。

每個測試都應驗證某個功能在某些條件下是否正常工作。在這個合約中,有一個叫做addTodo的函數(shù),它只是在notes數(shù)組中添加一個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. 每個測試都以函數(shù)開始,該函數(shù)it()包含測試的簡短精確描述和回調(diào)函數(shù)。在這種情況下,回調(diào)函數(shù)時async,因為我想使用await修飾符,這將允許我使用更干凈的代碼更輕松地進行測試。如果你不熟悉callbacks、promises、async和await關鍵字,請查看:Callbacks, Promises and Async/Await。web3.toHex()函數(shù)的作用是將文本轉換成十六進制,以便在合約中存儲。
  2. 然后開始addTodo()的測試。我們希望添加帶有短文本的待辦事項,然后查看它是否實際存儲在智能合約上。await關鍵字允許我等待直到函數(shù)完成添加待辦事項,否則它會在后臺處理時繼續(xù)執(zhí)行代碼。
  3. 添加note后,檢查智能合約的todos變量,看看note是否在那里。因為todos變量是public的,所以我可以在不使用任何附加函數(shù)的情況下執(zhí)行它。它接收到owner的note和該note的索引(在本例中為0),這是第一個。
  4. 因為我將note存儲在bytes32類型的變量中,所以可以存儲的文本數(shù)量限制為32字符,并且必須是十六進制文本。因此,當我試圖取回內(nèi)容時,我收到一個十六進制字符串,它由隨機數(shù)和字母組成,如下所示:0x74686924852857424513218979854654530000000000。實際上用web3.toUtf8()函數(shù)將十六進制轉換為人類可讀文本,并將其存儲在一個名為todoContent的變量中。
  5. 最后,檢查存儲在智能合約的note內(nèi)容是否正確,因為默認情況下,所有bytes32都有一個null十六進制。當值不相等時,assert.equal()函數(shù)拋出異常,破壞測試。如果它們相等,則測試正確。

接下來,為合約的剩余功能添加測試:

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,
        }
    }
};

打開終端,轉到項目文件夾并執(zhí)行以下操作啟動測試:

truffle test --localnode

如果測試正確,那么將全部passing

測試單個文件:

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

你必須在任何合約測試的5件事

  • 始終檢查溢出和下溢。如果要進行任何類型的數(shù)學計算,則必須確保代碼不會溢出或下溢。這些只是意味著你超過了一種unit變量的容量,因此該變量的值重置并嘗試存儲一個巨大的數(shù)字后再次變?yōu)?。
function sumNumbers(uint256 numberA, uint256 numberB) public view returns(uint256) {
   return numberA + numberB;
}

// 這個函數(shù)就存在溢出的風險。在這種情況下,可以編寫一個這樣的測試,通過檢查最終值來測試總和是否溢出:

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')
   }
})

  • 檢查功能的返回值是否始終在預期值的范圍內(nèi)。例如,如果你有一個預期會返回大于0的數(shù)字的函數(shù),請強制返回0的位置進行測試以查看它是否拒絕該情況。
  • 始終測試功能的限制。如果一個函數(shù)返回一個數(shù)字,那么寫一個測試,用最大可能的數(shù)字執(zhí)行它,另一個測試用盡可能小的數(shù)字,還有一個測試用中間的隨機值。你永遠不會知道你的功能在意外情況下會如何反應。
  • 確保返回值正確格式化。如果有一個應該返回數(shù)字數(shù)組的函數(shù),請檢查該數(shù)組是否返回空的任何情況。這很重要,因為它可能會破壞dapp的功能。
  • 確保參數(shù)拒絕無效值。必須確保合約已準備好用于功能參數(shù)的所有可能值,以避免安全風險。
// 比如這樣參數(shù)的函數(shù):

function doSomething (string randomText,uint256 randomNumber) public {}

// 寫一個字符串為空的測試,寫另一個字符串時一萬字的大量文本,寫一個測試,其中unit為零,一個是負數(shù),一個是一個巨大的數(shù)字。

參考

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容