Přeskočit na hlavní obsah

Jak vytvořit maketu chytrých kontraktů Solidity pro testování

solidity
smart kontrakt účty
testování
mocking
Středně pokročilý
Markus Waas
2. května 2020
3 minuta čtení

Mock objekty (opens in a new tab) jsou běžným návrhovým vzorem v objektově orientovaném programování. Pochází ze starého francouzského slova „mocquer“ s významem „dělat si legraci“ a vyvinulo se ve význam „napodobování něčeho skutečného“, což je vlastně to, co v programování děláme. Dělejte si prosím legraci ze svých chytrých kontraktů, jen pokud chcete, ale vytvářejte pro ně makety, kdykoli můžete. Usnadní vám to život.

Unit testování kontraktů pomocí maket

Vytvoření makety kontraktu v podstatě znamená vytvoření druhé verze tohoto kontraktu, která se chová velmi podobně jako původní, ale způsobem, který může vývojář snadno ovládat. Často skončíte u složitých kontraktů, kde chcete pouze testovat malé části kontraktu. Problém je, co když testování této malé části vyžaduje velmi specifický stav kontraktu, do kterého je obtížné se dostat?

Pokaždé byste mohli napsat složitou logiku nastavení testu, která kontrakt uvede do požadovaného stavu, nebo napíšete maketu. Vytvoření makety kontraktu je s dědičností snadné. Jednoduše vytvořte druhý mock kontrakt, který dědí z původního. Nyní můžete do své makety přepsat funkce. Ukažme si to na příkladu.

Příklad: Privátní ERC20

Použijeme příklad kontraktu ERC-20, který má počáteční soukromé období. Vlastník může spravovat soukromé uživatele a pouze ti budou moci na začátku přijímat tokeny. Jakmile uplyne určitá doba, bude moci tokeny používat každý. Pokud vás to zajímá, používáme hook _beforeTokenTransfer (opens in a new tab) z nových kontraktů OpenZeppelin v3.

1pragma solidity ^0.6.0;
2
3import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
4import "@openzeppelin/contracts/access/Ownable.sol";
5
6contract PrivateERC20 is ERC20, Ownable {
7 mapping (address => bool) public isPrivateUser;
8 uint256 private publicAfterTime;
9
10 constructor(uint256 privateERC20timeInSec) ERC20("PrivateERC20", "PRIV") public {
11 publicAfterTime = now + privateERC20timeInSec;
12 }
13
14 function addUser(address user) external onlyOwner {
15 isPrivateUser[user] = true;
16 }
17
18 function isPublic() public view returns (bool) {
19 return now >= publicAfterTime;
20 }
21
22 function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override {
23 super._beforeTokenTransfer(from, to, amount);
24
25 require(_validRecipient(to), "PrivateERC20: invalid recipient");
26 }
27
28 function _validRecipient(address to) private view returns (bool) {
29 if (isPublic()) {
30 return true;
31 }
32
33 return isPrivateUser[to];
34 }
35}
Zobrazit vše

A teď si vytvoříme maketu.

1pragma solidity ^0.6.0;
2import "../PrivateERC20.sol";
3
4contract PrivateERC20Mock is PrivateERC20 {
5 bool isPublicConfig;
6
7 constructor() public PrivateERC20(0) {}
8
9 function setIsPublic(bool isPublic) external {
10 isPublicConfig = isPublic;
11 }
12
13 function isPublic() public view returns (bool) {
14 return isPublicConfig;
15 }
16}
Zobrazit vše

Dostanete jednu z následujících chybových zpráv:

  • PrivateERC20Mock.sol: TypeError: Overriding function is missing "override" specifier.
  • PrivateERC20.sol: TypeError: Trying to override non-virtual function. Did you forget to add "virtual"?.

Protože používáme novou verzi Solidity 0.6, musíme přidat klíčové slovo virtual pro funkce, které lze přepsat, a override pro přepisující funkci. Přidejme je tedy k oběma funkcím isPublic.

Nyní ve svých unit testech můžete místo toho použít PrivateERC20Mock. Pokud chcete testovat chování během doby soukromého používání, použijte setIsPublic(false) a podobně setIsPublic(true) pro testování doby veřejného používání. V našem příkladu bychom samozřejmě mohli také použít pomocné funkce pro čas (opens in a new tab), abychom odpovídajícím způsobem změnili časy. Ale myšlenka mockingu by teď měla být jasná a dokážete si představit scénáře, kde to není tak snadné jako pouhé posunutí času.

Vytváření maket mnoha kontraktů

Může to být nepřehledné, pokud musíte pro každou jednu maketu vytvářet další kontrakt. Pokud vám to vadí, můžete se podívat na knihovnu MockContract (opens in a new tab). Umožňuje přepisovat a měnit chování kontraktů za chodu. Funguje to však pouze pro mockování volání jiného kontraktu, takže by to pro náš příklad nefungovalo.

Mocking může být ještě mocnější

Možnosti mockingu tím nekončí.

  • Přidávání funkcí: Užitečné je nejen přepsání konkrétní funkce, ale také pouhé přidání dalších funkcí. Dobrým příkladem pro tokeny je mít pouze dodatečnou funkci mint, která umožní každému uživateli získat zdarma nové tokeny.
  • Použití na testnetech: Když nasazujete a testujete své kontrakty na testnetech společně s vaší dapp, zvažte použití mock verze. Vyhněte se přepisování funkcí, pokud to opravdu nemusíte. Koneckonců chcete testovat skutečnou logiku. Ale přidání například resetovací funkce může být užitečné, která jednoduše resetuje stav kontraktu na začátek, není vyžadováno žádné nové nasazení. To byste samozřejmě nechtěli mít v kontraktu na hlavní síti.

Stránka naposledy aktualizována: 25. srpna 2025

Byl tento tutoriál užitečný?