Przejdź do głównej zawartości

Jak mockować inteligentne kontrakty Solidity na potrzeby testowania

solidity
smart kontrakty
testowanie
tworzenie atrap
Średnio zaawansowany
Markus Waas
2 maja 2020
3 minuta czytania

Obiekty typu mockopens in a new tab to popularny wzorzec projektowy w programowaniu zorientowanym obiektowo. Pochodzi od starofrancuskiego słowa „mocquer”, oznaczającego „naśmiewać się”, które ewoluowało w „imitowanie czegoś prawdziwego”, co jest dokładnie tym, co robimy w programowaniu. Proszę, naśmiewajcie się ze swoich inteligentnych kontraktów tylko, jeśli chcecie, ale twórzcie ich atrapy (mocki), kiedy tylko możecie. To ułatwia życie.

Testowanie jednostkowe kontraktów z użyciem atrap (mocków)

Mockowanie kontraktu w istocie oznacza stworzenie jego drugiej wersji, która zachowuje się bardzo podobnie do oryginału, ale w sposób, który deweloper może łatwo kontrolować. Często masz do czynienia ze złożonymi kontraktami, w których chcesz tylko przetestować jednostkowo małe części kontraktu. Problem pojawia się, gdy testowanie tej małej części wymaga bardzo specyficznego stanu kontraktu, który jest trudny do osiągnięcia.

Możesz za każdym razem pisać złożoną logikę konfiguracji testu, która doprowadza kontrakt do wymaganego stanu, albo napisać atrapę (mock). Mockowanie kontraktu jest łatwe dzięki dziedziczeniu. Po prostu utwórz drugi kontrakt-atrapę, który dziedziczy po oryginalnym. Teraz możesz nadpisywać funkcje do swojej atrapy (mocka). Zobaczmy to na przykładzie.

Przykład: Prywatny ERC20

Używamy przykładowego kontraktu ERC-20, który ma początkowy okres prywatny. Właściciel może zarządzać prywatnymi użytkownikami i tylko oni będą mogli na początku otrzymywać tokeny. Gdy upłynie określony czas, wszyscy będą mogli używać tokenów. Jeśli jesteś ciekaw, używamy hooka _beforeTokenTransferopens in a new tab z nowych kontraktów 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: nieprawidłowy odbiorca");
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}
Pokaż wszystko

A teraz stwórzmy jego atrapę (mocka).

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}
Pokaż wszystko

Otrzymasz jeden z następujących komunikatów o błędach:

  • PrivateERC20Mock.sol: TypeError: Overriding function is missing "override" specifier.
  • PrivateERC20.sol: TypeError: Trying to override non-virtual function. Czy zapomniałeś dodać „virtual”?

Ponieważ używamy nowej wersji Solidity 0.6, musimy dodać słowo kluczowe virtual dla funkcji, które można nadpisać, i override dla funkcji nadpisującej. Dodajmy je więc do obu funkcji isPublic.

Teraz w swoich testach jednostkowych możesz zamiast tego użyć PrivateERC20Mock. Gdy chcesz przetestować zachowanie w czasie użytkowania prywatnego, użyj setIsPublic(false), a do testowania w czasie użytkowania publicznego setIsPublic(true). Oczywiście w naszym przykładzie moglibyśmy również użyć funkcji pomocniczych do obsługi czasuopens in a new tab, aby odpowiednio zmienić czas. Ale idea mockowania powinna być już jasna i możesz sobie wyobrazić scenariusze, w których nie jest to tak proste, jak samo przesunięcie czasu.

Mockowanie wielu kontraktów

Tworzenie kolejnego kontraktu dla każdej pojedynczej atrapy (mocka) może stać się kłopotliwe. Jeśli to ci przeszkadza, możesz zapoznać się z biblioteką MockContractopens in a new tab. Pozwala ona na nadpisywanie i zmienianie zachowań kontraktów w locie. Działa ona jednak tylko w przypadku mockowania wywołań do innego kontraktu, więc nie zadziała w naszym przykładzie.

Mockowanie może być jeszcze potężniejsze

Na tym nie kończą się możliwości mockowania.

  • Dodawanie funkcji: przydatne jest nie tylko nadpisywanie określonej funkcji, ale również dodawanie nowych. Dobrym przykładem dla tokenów jest posiadanie dodatkowej funkcji mint, aby umożliwić każdemu użytkownikowi otrzymywanie nowych tokenów za darmo.
  • Użycie w sieciach testowych: wdrażając i testując swoje kontrakty w sieciach testowych razem ze swoją dappką, rozważ użycie wersji mockowanej. Unikaj nadpisywania funkcji, chyba że jest to absolutnie konieczne. W końcu chcesz przetestować prawdziwą logikę. Ale przydatne może być dodanie na przykład funkcji resetowania, która po prostu przywraca stan kontraktu do stanu początkowego, bez konieczności ponownego wdrożenia. Oczywiście nie chcesz mieć tego w kontrakcie w sieci głównej (Mainnet).

Strona ostatnio zaktualizowana: 25 sierpnia 2025

Czy ten samouczek był pomocny?