Java開發的智慧合約單元測試教程

買賣虛擬貨幣

在本教程中,我們將實現一個簡單的合約,為其編寫單元測試,並執行除錯過程以查詢Bug。執行該示例所需的全部是Java和IDE。 只需建立一個依賴AVM最新工具庫的Java專案作為庫。

1.編寫智慧合約首先建立智慧合約。以下是可用於簡單投票DApp的智慧合約的示例。智慧合約的功能:

1. 只有合約所有者(部署合約的帳戶)可以新增或刪除成員。

2. 只有會員可以介紹新的提案。

3. 只有會員可以對提案進行投票。

4. 如果超過50%的成員贊成該提案並對其投票,該提案將透過。

packageorg.aion.avm.embed.temp;importavm.Blockchain;importorg.aion.avm.tooling.abi.Callable;importorg.aion.avm.userlib.AionMap;importorg.aion.avm.userlib.AionSet;importavm.Address;importorg.aion.avm.userlib.abi.ABIDecoder;publicclassVoting{privatestaticAionSet<Address>members=newAionSet<>();privatestaticAionMap<Integer,

Proposal>proposals=newAionMap<>();privatestaticAddressowner;privatestaticintminimumQuorum;static{ABIDecoderdecoder=newABIDecoder(Blockchain.getData());Address[]arg=decoder.decodeOneAddressArray();for(Addressaddr:arg){members.add(addr);}updateQuorum();owner=Blockchain.getCaller();}@CallablepublicstaticvoidaddProposal(Stringdescription){Addresscaller=Blockchain.getCaller();Blockchain.require(isMember(caller));Proposalproposal=newProposal(description,caller);intproposalId=proposals.size();proposals.put(proposalId,proposal);Blockchain.log("NewProposalAdded".getBytes(),

Integer.toString(proposalId).getBytes(),caller.toByteArray(),

description.getBytes());}@Callablepublicstaticvoidvote(intproposalId){Addresscaller=Blockchain.getCaller();Blockchain.require(isMember(caller)&&proposals.containsKey(

proposalId));ProposalvotedProposal=proposals.get(proposalId);votedProposal.voters.add(caller);Blockchain.log("Voted".getBytes(),

Integer.toString(proposalId).getBytes(),caller.toByteArray());if(!votedProposal.isPassed&&votedProposal.voters.size()==

minimumQuorum){votedProposal.isPassed=true;Blockchain.log("ProposalPassed".getBytes(),

Integer.toString(proposalId).getBytes());}}@CallablepublicstaticvoidaddMember(AddressnewMember){onlyOwner();members.add(newMember);updateQuorum();Blockchain.log("MemberAdded".getBytes(),

newMember.toByteArray());}@CallablepublicstaticvoidremoveMember(Addressmember){onlyOwner();members.remove(member);updateQuorum();Blockchain.log("MemberRemoved".getBytes(),

member.toByteArray());}@CallablepublicstaticStringgetProposalDescription(intproposalId){returnproposals.containsKey(proposalId)?

proposals.get(proposalId).description:null;}@CallablepublicstaticAddressgetProposalOwner(intproposalId){returnproposals.containsKey(proposalId)?

proposals.get(proposalId).owner:null;}@CallablepublicstaticbooleanhasProposalPassed(intproposalId){returnproposals.containsKey(proposalId)&&

proposals.get(proposalId).isPassed;}@CallablepublicstaticintgetMinimumQuorum(){returnminimumQuorum;}@CallablepublicstaticbooleanisMember(Addressaddress){returnmembers.contains(address);}privatestaticvoidonlyOwner(){Blockchain.require(owner.equals(Blockchain.getCaller()));}privatestaticvoidupdateQuorum(){minimumQuorum=members.size()/2+1;}privatestaticclassProposal{Stringdescription;Addressowner;booleanisPassed;AionSet<Address>voters=newAionSet<>();Proposal(Stringdescription,Addressowner){this.description=description;this.owner=owner;}}}合約中的static塊在部署時僅執行一次。 我們在此塊中設定初始成員,minimumQuorum和所有者。儘管我們與一組成員啟動了合約,但所有者隨後也可以新增和刪除成員。我們使用AionSet和AionMap跟蹤成員及其提案。可以使用其唯一識別符號從地圖訪問每個建議。智慧合約的主要職能是:

addProposal,允許成員新增提案說明以進行投票。

vote,允許成員透過傳遞其ID對可用提案進行投票。贏得多數成員投票的提案將透過。請注意,將生成ProposalPassed事件以記錄已透過投標的ID。

2.編寫單元測試我們將使用AvmRule編寫測試。 AvmRule是用於在嵌入式AVM上測試合約的Junit規則。 它建立Aion核心和AVM的記憶體中表示形式。每次我們執行測試時,都會重新整理內建的核心和AVM例項。在測試我們的合約之前,我們需要將其部署到記憶體中的Aion區塊鏈中,並且我們將使用AvmRule完成此任務。A.例項化AvmRule您可以看到該規則包含一個布林引數,該布林引數啟用/禁用除錯模式。 最好在啟用偵錯程式的情況下編寫測試。您可以在下一部分中檢視如何除錯合約。

@RulepublicAvmRuleavmRule=newAvmRule(true);注意:此行將為每種測試方法例項化嵌入式AVM。如果將規則定義為@classRule,則將僅為測試類例項化AVM和核心的一個例項。B.獲取合約位元組現在,我們必須獲取與合約jar的記憶體表示相對應的位元組。為了獲取位元組,我們將使用AvmRule中的getDappBytes方法。getDappBytes採用以下引數:1. 合約的主要類別。2. 合約建構函式引數,可以在靜態塊中對其進行訪問和解碼。3. DApp jar中需要包含其他類。

publicbyte[]getDappBytes(Class<?>mainClass,byte[]arguments,Class<?>...otherClasses)C.部署您的智慧合約使用部署功能可以輕鬆完成智慧合約的部署。
publicResultWrapperdeploy(Addressfrom,BigIntegervalue,byte[]dappBytes)AvmRule還提供了在Aion核心中建立具有初始餘額的帳戶的功能。以下是由3名成員組成的小組部署投票智慧合約的方法。

publicclassVotingContractTest{@RulepublicAvmRuleavmRule=newAvmRule(true);publicAddressdappAddress;publicAddressowner=avmRule.getPreminedAccount();publicAddress[]members=newAddress[3];@Beforepublicvoidsetup(){for(inti=0;i<members.length;i++){//createaccountswithinitialbalancemembers[i]=avmRule.getRandomAddress(

BigInteger.valueOf(10_000_000L));}//encodemembersarrayasanargumentbyte[]deployArgument=ABIUtil.encodeOneObject(members);//getthebytesrepresentingtheinmemoryjarbyte[]dappBytes=avmRule.getDappBytes(

Voting.class,deployArgument);//deployandgetthecontractaddressdappAddress=avmRule.deploy(

owner,BigInteger.ZERO,dappBytes).getDappAddress();}}D.呼叫方法我們可以透過以下方式呼叫合同中的方法:1. 編碼方法名稱及其引數。2. 將編碼後的位元組傳遞給AvmRule的call方法。

publicResultWrappercall(Addressfrom,AddressdappAddress,BigIntegervalue,byte[]transactionData)例如我們建立一個新的提案。我們將透過檢查是否生成了NewProposalAdded事件以及事件主題和資料正確來驗證提案。

@TestpublicvoidaddProposalTest(){Stringdescription="newproposaldescription";byte[]txData=ABIUtil.encodeMethodArguments(

"addProposal",description);AvmRule.ResultWrapperresult=avmRule.call(

members[0],dappAddress,BigInteger.ZERO,txData);//assertthetransactionwassuccessfulAssert.assertTrue(result.getReceiptStatus().isSuccess());//asserttheeventisgeneratedAssert.assertEquals(1,result.getLogs().size());Loglog=result.getLogs().get(0);//validatethetopicsanddataAssert.assertArrayEquals(LogSizeUtils.truncatePadTopic(

"NewProposalAdded".getBytes()),log.copyOfTopics().get(0));Assert.assertArrayEquals(LogSizeUtils.truncatePadTopic(

Integer.toString(0).getBytes()),log.copyOfTopics().get(1));Assert.assertArrayEquals(LogSizeUtils.truncatePadTopic(

members[0].toByteArray()),log.copyOfTopics().get(2));Assert.assertArrayEquals(description.getBytes(),log.copyOfData());}

現在我們將提交一個提案以及兩個投票。由於有兩個不同的成員以ID 0對提案進行了投票,提案應透過。因此我們希望為最後一個事務生成兩個不同的事件-Voted和ProposalPassed。您還可以透過提案ID查詢提案的狀態。您會看到返回的解碼資料為true,表示提案已透過。

@TestpublicvoidvoteTest(){Stringdescription="newproposaldescription";byte[]txData=ABIUtil.encodeMethodArguments(

"addProposal",description);AvmRule.ResultWrapperresult=avmRule.call(

members[0],dappAddress,BigInteger.ZERO,txData);Assert.assertTrue(result.getReceiptStatus().isSuccess());Assert.assertEquals(1,result.getLogs().size());//vote#1txData=ABIUtil.encodeMethodArguments("vote",0);result=avmRule.call(

members[1],dappAddress,BigInteger.ZERO,txData);Assert.assertTrue(result.getReceiptStatus().isSuccess());Assert.assertEquals(1,result.getLogs().size());//vote#2txData=ABIUtil.encodeMethodArguments("vote",0);result=avmRule.call(

members[2],dappAddress,BigInteger.ZERO,txData);Assert.assertTrue(result.getReceiptStatus().isSuccess());Assert.assertEquals(2,result.getLogs().size());//validatethattheproposalisstoredaspassedtxData=ABIUtil.encodeMethodArguments("hasProposalPassed",0);result=avmRule.call(

members[2],dappAddress,BigInteger.ZERO,txData);//decodethereturndataasbooleanAssert.assertTrue((boolean)result.getDecodedReturnData());}

3.除錯智慧合約除錯我們的智慧合約非常容易,只需在原始碼中設定斷點即可!由於我們在啟用除錯的情況下建立了AvmRule,因此在達到斷點時將停止執行。讓我們來看一個例子。這是部署後智慧合約的狀態。

您可以看到該智慧合約具有:1. 3名成員。2. 0個提案。3. minimumQuorum = 2。您也可以檢查每個集合的內容。例如透過呼叫addProposal,您將能夠看到更新的AionMap。

讓我們實際對偵錯程式進行測試。我們將故意在評估提案透過方式時造成一個簡單的錯誤。我將修改提案透過條件,如下所示。 請注意,等於條件已更改為小於或等於。

if(!votedProposal.isPassed&&votedProposal.voters.size()<=minimumQuorum)現在當第一個所有者提交投票時,提案將透過。讓我們除錯方法呼叫並逐步執行該函式。

您可以看到,儘管minimumQuorum等於2,但是該提案的投票者計數僅為1。我們修改了if語句(來自上面),並且第51行的isPassed標誌設定為true。從那裡,您可以輕鬆地確定錯誤在程式碼中的位置。

結論如果您曾經為以太坊開發過智慧合約,就會知道用一種陌生的專用語言編寫合約並對其進行除錯的痛苦。任何熟悉Java的人都會感覺像在家中一樣使用AVM編寫智慧合約。另外市場上任何IDE中的所有除錯功能都可用於測試和除錯Java智慧合約。

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;