Hardhat進(jìn)行合約測試環(huán)境準(zhǔn)備及方法詳解
引言
Hardhat是一個開源的以太坊開發(fā)框架,簡單好用、可擴(kuò)展、可定制的特點(diǎn)讓它在開發(fā)者中間很受歡迎。Hardhat在支持編輯、編譯、調(diào)試和部署合約方面都非常的方便,也有很多功能可以使合約測試工作更加高效和便捷,本文就是聚焦在合約測試領(lǐng)域,探尋Hardhat的特點(diǎn)和日常測試過程中的一些使用技巧。
一、環(huán)境準(zhǔn)備
可以參考Hardhat官網(wǎng)教程,進(jìn)行環(huán)境的準(zhǔn)備和Hardhat安裝。
Hardhat提供了快速構(gòu)建合約工程的方法:
- 建立空的工程目錄
- 在目錄下執(zhí)行npx hardhat
- 根據(jù)交互提示完成Hardhat工程的創(chuàng)建
二、示例合約與測試方法
快速創(chuàng)建Hardhat工程,可以在contract目錄下看到Lock.sol的合約,此合約是一個簡單的示例,實(shí)現(xiàn)了在指定時間前(unlockTime)鎖定資產(chǎn)的功能。
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.9; // Uncomment this line to use console.log import "hardhat/console.sol"; contract Lock { uint public unlockTime; address payable public owner; event Withdrawal(uint amount, uint when); constructor(uint _unlockTime) payable { require( block.timestamp < _unlockTime, "Unlock time should be in the future" ); unlockTime = _unlockTime; owner = payable(msg.sender); } function withdraw() public { // Uncomment this line, and the import of "hardhat/console.sol", to print a log in your terminal console.log("Unlock time is %o and block timestamp is %o", unlockTime, block.timestamp); require(block.timestamp >= unlockTime, "You can't withdraw yet"); require(msg.sender == owner, "You aren't the owner"); emit Withdrawal(address(this).balance, block.timestamp); owner.transfer(address(this).balance); } }
同時,在test目錄下,有Lock.ts(或Lock.js)的測試代碼,測試代碼里分別展示了對合約部署,合約中涉及的功能的測試。其中值得學(xué)習(xí)的部分:
一是定義了一個具有setup功能的函數(shù),此函數(shù)定義了一些狀態(tài)變量的初始狀態(tài),后面在每次測試代碼運(yùn)行前,可以通過loadFixture方法執(zhí)行此函數(shù),把狀態(tài)變量還原到函數(shù)中定義的初始狀態(tài)。這種給狀態(tài)變量取快照,并用快照還原的方式,解決了很多因?yàn)闋顟B(tài)變量改變而測試用例執(zhí)行異常的問題,是個很有用很便捷的方法。
另一個是用到了很便捷的斷言方式,這就省掉了寫很多麻煩的校驗(yàn)條件,來驗(yàn)證一個執(zhí)行結(jié)果。比如下面這個斷言,直接能驗(yàn)證當(dāng)withdraw函數(shù)被調(diào)用后出現(xiàn)的回滾情況:
await expect(lock.withdraw()).to.be.revertedWith( "You can't withdraw yet" );
三、LoadFixture的使用
使用場景
用于每次執(zhí)行測試前的setup操作,可以定義一個函數(shù),在此函數(shù)中完成諸如合約部署,合約初始化,賬戶初始化等操作,在每次執(zhí)行測試前利用loadFixture的功能,進(jìn)行相同的變量狀態(tài)的設(shè)置,對合約測試提供了很大的幫助。
工作原理
根據(jù)Hardhat源碼,可以看到loadFixture維護(hù)了一個快照數(shù)組snapshots,一個快照元素包含:
不同的函數(shù)f作為loadFixture入?yún)r,會有不同的snapshot存儲在loadFixture維護(hù)的snapshots數(shù)組中。
在loadFixture(f)首次執(zhí)行時,屬于f函數(shù)的snapshot為undefined,此時會記錄f函數(shù)中定義的全部狀態(tài)變量,同時執(zhí)行:
const restorer = await takeSnapshot();
并將此時的snapshot元素加入到snapshots數(shù)組中,后面再次用到同一個入?yún)⒑瘮?shù)f的loadFixture時,在快照數(shù)組snapshots中已存在快照,可直接進(jìn)行區(qū)塊鏈狀態(tài)回滾: await snapshot.restorer.restore();
- fixture: Fixture類型的入?yún)⒑瘮?shù),type Fixture = () => Promise;
- data:fixture函數(shù)中定義的狀態(tài)變量
- restorer:一個有restore方法的結(jié)構(gòu)體,在“./helpers/takeSnapshot”方法中有定義,可以觸發(fā)evm_revert操作,指定區(qū)塊鏈退回到某個快照點(diǎn)。
loadFixture的用法
官方文檔示例如下:
```js async function deployContractsFixture() { const token = await Token.deploy(...); const exchange = await Exchange.deploy(...); return { token, exchange }; } it("test", async function () { const { token, exchange } = await loadFixture(deployContractsFixture); // use token and exchanges contracts }) ``` 注意:loadFixture的入?yún)⒉豢梢允悄涿瘮?shù),即: ```js //錯誤寫法 loadFixture(async () => { ... }) //正確寫法 async function beforeTest(){ //定義函數(shù) } loadFixture(beforeTest); ```
四、Matchers的使用
Machers:在chai斷言庫的基礎(chǔ)上增加了以太坊特色的斷言,便于測試使用
1.Events用法
contract Ademo { event Event(); function callFunction () public { emit Event(); } }
對合約C的call方法進(jìn)行調(diào)用會觸發(fā)一個無參數(shù)事件,為了測試這個事件是否被觸發(fā),可以直接用hardhat-chai-matchers中的Events斷言,用法如下:
const A=await ethers.getContractFactory("Ademo"); const a=await A.deploy(); //采用hardhat-chai-matchers的斷言方式,判斷Events是否觸發(fā) await expect(a.callFunction()).to.emit(a,"Event");
- Reverts用法:
//最簡單的判斷revert的方式 await expect(contract.call()).to.be.reverted; //判斷未發(fā)生revert await expect(contract.call()).not.to.be.reverted; //判斷revert發(fā)生并且?guī)Я酥付ǖ腻e誤信息 await expect(contract.call()).to.be.revertedWith("Some revert message"); //判斷未發(fā)生revert并且攜帶指定信息 await expect(contract.call()).not.to.be.revertedWith("Another revert message");
除了上述常用的判斷場景外,hardhat-chai-matchers還支持了對Panic以及定制化Error的判定:
await expect(…).to.be.revertedWithPanic(PANIC_CODES) await expect(…).not.to.be.revertedWithPanic(PANIC_CODES) await expect(…).to.be.revertedWithCustomError(CONTRACT,"CustomErrorName") await expect(…).to.be.revertedWithoutReason();
- Big Number
在solidity中最大整型數(shù)是2^256,而JavaScript中的最大安全數(shù)是2^53-1,如果用JS寫solidity合約中返回的大數(shù)的斷言,就會出現(xiàn)問題。hardhat-chai-matchers提供了關(guān)于大數(shù)的斷言能力,使用者無需關(guān)心大數(shù)之間比較的關(guān)系,直接以數(shù)字的形式使用即可,比如: expect(await token.balanceOf(someAddress)).to.equal(1);
關(guān)于JavaScript的最大安全數(shù)問題:
Number.MAX_SAFE_INTEGER 常量表示在 JavaScript 中最大的安全整數(shù),其值為2^53-1,即9007199254740991 。因?yàn)镴avascript的數(shù)字存儲使用了IEEE 754中規(guī)定的雙精度浮點(diǎn)數(shù)數(shù)據(jù)類型,而這一數(shù)據(jù)類型能夠安全存儲(-2^53-1 ~ 2^53-1)之間的數(shù)(包括邊界值),超出范圍后將會出現(xiàn)錯誤,比如:
const x = Number.MAX_SAFE_INTEGER + 1; const y = Number.MAX_SAFE_INTEGER + 2; console.log(Number.MAX_SAFE_INTEGER); // Expected output: 9007199254740991 console.log(x); console.log(y); // Expected output: 9007199254740992 console.log(x === y); // Expected output: true
Balance Changes
可以很方便的檢測用戶錢包的資金變化額度,適用于以太幣的金額變化,或者ERC-20代幣的金額變化。
單個錢包地址的金額變化:
await expect(() => sender.sendTransaction({ to: someAddress, value: 200 }) ).to.changeEtherBalance(sender, "-200"); await expect(token.transfer(account, 1)).to.changeTokenBalance( token, account, 1 );
也可以用來檢測多個賬戶的金額變化,在測試轉(zhuǎn)賬交易時,非常適用:
await expect(() => sender.sendTransaction({ to: receiver, value: 200 }) ).to.changeEtherBalances([sender, receiver], [-200, 200]); await expect(token.transferFrom(sender, receiver, 1)).to.changeTokenBalances( token, [sender, receiver], [-1, 1] );
- 字符串比較
可以用hardhat-chai-matchers提供的方法,方便地校驗(yàn)各種復(fù)雜的字符串,比如一個字符串是否是正確的地址格式、私鑰格式等,用法如下:
// 是否符合address格式 expect("0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2").to.be.a.properAddress; //是否符合私鑰格式 expect(SOME_PRI_KEY).to.be.a.properPrivateKey; //判斷十六進(jìn)制字符串的用法 expect("0x00012AB").to.hexEqual("0x12ab"); //判斷十六進(jìn)制字符串的長度 expect("0x123456").to.be.properHex(6);
以上就是Hardhat進(jìn)行合約測試環(huán)境準(zhǔn)備及方法詳解的詳細(xì)內(nèi)容,更多關(guān)于Hardhat合約測試的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
實(shí)現(xiàn)一個簡單得數(shù)據(jù)響應(yīng)系統(tǒng)
這篇文章主要介紹了實(shí)現(xiàn)一個簡單得數(shù)據(jù)響應(yīng)系統(tǒng),文章介紹的數(shù)據(jù)響應(yīng)系統(tǒng)會用到Dep,其實(shí),這就是一個依賴收集的容器, depend 收集依賴, notify 觸發(fā)依賴,下面來看看詳細(xì)的內(nèi)容結(jié)介紹,需要的朋友可以參考一下2021-11-11JavaScript實(shí)際應(yīng)用:innerHTMl和確認(rèn)提示的使用
JavaScript實(shí)際應(yīng)用:innerHTMl和確認(rèn)提示的使用...2006-06-06微信小程序 Canvas增強(qiáng)組件實(shí)例詳解及源碼分享
這篇文章主要介紹了微信小程序 Canvas增強(qiáng)組件實(shí)例詳解及源碼分享的相關(guān)資料,WeZRender是一個微信小程序Canvas增強(qiáng)組件,這里詳細(xì)介紹,需要的朋友可以參考下2017-01-01采用CSS和JS,剛好我最近有個站點(diǎn)要用到下拉菜單!
采用CSS和JS,剛好我最近有個站點(diǎn)要用到下拉菜單!...2006-06-06ECMAScript 6對象的擴(kuò)展實(shí)現(xiàn)示例
這篇文章主要為大家介紹了ECMAScript 6對象的擴(kuò)展實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08bigScreen大屏配置選項(xiàng)無法和畫布中心的展示聯(lián)動解決
這篇文章主要為大家介紹了bigScreen大屏配置選項(xiàng)無法和畫布中心的展示聯(lián)動解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01You-Dont-Know-JS詞法作用域及兩種常見的模型學(xué)習(xí)文檔
這篇文章主要為大家介紹了JS?詞法作用域及兩種常見的模型詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08