Test de contrats intelligents –
Dans notre introduction à Truffle nous avons discuté de ce qu'est Truffle et comment il peut vous aider à automatiser le travail de compilation, de test et de déploiement de contrats intelligents.
Dans cet article, nous explorerons comment tester des contrats intelligents. Testing est l'aspect le plus important du développement de contrats intelligents de qualité.
Pourquoi tester intensivement? Donc vous pouvez éviter des choses comme ceci ou ceci . Les contrats intelligents gèrent la valeur, parfois une valeur énorme – ce qui en fait des proies très intéressantes pour les personnes ayant le temps et les compétences pour les attaquer.
Vous ne voudriez pas que votre projet se termine sur le cimetière de la Blockchain voulez-vous?
Mise en route
Nous allons créer HashMarket un marché d'articles d'occasion simple, basé sur des contrats intelligents.
Ouvrez votre terminal et positionnez-vous dans le dossier où vous voulez construire le projet. Dans ce dossier, exécutez ce qui suit:
mkdir HashMarket
cd HashMarket
truffle init
Vous devriez obtenir un résultat qui ressemble à ceci:
Téléchargement en cours ...
Déballage...
Mise en place ...
Unbox réussi. Doux!
Commandes
Compiler: compilation de truffes
Migrer: la truffe migre
Contrats d'essai: test à la truffe
Vous obtiendrez également une structure de fichier qui ressemble à ceci:
.
├── contrats
Mig └── Migrations.sol
Mig── migrations
│ └── 1_initial_migration.js
├── test
├── truffle-config.js
└── truffle.js
Pour un rappel sur les fichiers, jetez un coup d'œil à l'article précédent . En un mot, nous avons la base truffle.js
et les deux fichiers utilisés pour effectuer les migrations initiales sur la blockchain
Préparation de l'environnement de test
Le moyen le plus simple de tester est le local réseau. Je recommande fortement d'utiliser l'outil ganache-cli
(précédemment connu sous le nom de TestRPC
) pour le test de contrat
Install ganache-cli
(qui nécessite le paquet Node Manager):
npm installer -g ganache-cli
Après cela, ouvrez une fenêtre ou un onglet de terminal séparé et lancez ceci:
ganache-cli
Vous devriez voir une sortie similaire à celle-ci:
Ganache CLI v6.1.0 (ganache-core: 2.1.0)
Comptes disponibles
=================
(0) 0xd14c83349da45a12b217988135fdbcbb026ac160
(1) 0xc1df9b406d5d26f86364ef7d449cc5a6a5f2e8b8
(2) 0x945c42c7445af7b3337834bdb1abfa31e291bc40
(3) 0x56156ea86cd46ec57df55d6e386d46d1bbc47e3e
(4) 0x0a5ded586d122958153a3b3b1d906ee9ff8b2783
(5) 0x39f43d6daf389643efdd2d4ff115e5255225022f
(6) 0xd793b706471e257cc62fe9c862e7a127839bbd2f
(7) 0xaa87d81fb5a087364fe3ebd33712a5522f6e5ac6
(8) 0x177d57b2ab5d3329fad4f538221c16cb3b8bf7a7
(9) 0x6a146794eaea4299551657c0045bbbe7f0a6db0c
Clés privées
=================
(0) 66a6a84ee080961beebd38816b723c0f790eff78f0a1f81b73f3a4c54c98467b
(1) fa134d4d14fdbac69bbf76d2cb27c0df1236d0677ec416dfbad1cc3cc058145e
(2) 047fef2c5c95d5cf29c4883b924c24419b12df01f3c6a0097f1180fa020e6bd2
(3) 6ca68e37ada9b1b88811015bcc884a992be8f6bc481f0f9c6c583ef0d4d8f1c9
(4) 84bb2d44d64478d1a8b9d339ad1e1b29b8dde757e01f8ee21b1dcbce50e2b746
(5) 517e8be95253157707f34d08c066766c5602e519e93bace177b6377c68cba34e
(6) d2f393f1fc833743eb93f108fcb6feecc384f16691250974f8d9186c68a994ef
(7) 8b8be7bec3aca543fb45edc42e7b5915aaddb4138310b0d19c56d836630e5321
(8) e73a1d7d659b185e56e5346b432f58c30d21ab68fe550e7544bfb88765235ae3
(9) 8bb5fb642c58b7301744ef908fae85e2d048eea0c7e0e5378594fc7d0030f100
Portefeuille HD
=================
Mnémonique: écologie douce animal jure exclure citation léopard éruption garde noyau belle série
Base HD Path: m / 44 '/ 60' / 0 '/ 0 / {account_index}
Écoute sur localhost: 8545
Ceci est une liste de tous les comptes ganache-cli
créé pour vous. Vous pouvez utiliser n'importe quel compte que vous souhaitez, mais ces comptes seront préchargés avec de l'éther, ce qui les rend très utiles (puisque le test nécessite de l'éther pour coûts du gaz ).
] truffle.js ou fichier truffle-config.js
et ajoute un réseau de développement à votre config:
module.exports = {
réseaux: {
développement: {
hôte: "127.0.0.1",
port: 8545,
network_id: "*"
}
}
}
Rédaction du contrat intelligent
La première chose à faire est d'écrire le contrat intelligent HashMarket. Nous allons essayer de le garder aussi simple que possible, tout en conservant la fonctionnalité requise.
HashMarket est un type d'eBay sur la blockchain. Il permet aux vendeurs d'afficher des produits et aux acheteurs de les acheter pour l'éther. Cela permet également aux vendeurs de retirer des produits s'ils ne sont pas vendus.
Dans votre projet, dans le dossier contracts
créez un nouveau fichier et appelez-le HashMarket.sol
. Dans ce fichier, ajoutez le code suivant:
pragma solidity 0.4.21;
contrat HashMarket {
// Suivre l'état des éléments, tout en préservant l'historique
enum ItemStatus {
actif,
vendu,
supprimé
}
struct Item {
nom bytes32;
prix d'uint;
adresse vendeur;
État de l'état de l'article;
}
event ItemAdded (nom bytes32, prix uint, vendeur d'adresse);
événement ItemPurchased (uint itemID, adresse acheteur, adresse vendeur);
événement ItemRemoved (uint itemID);
event FundsPulled (propriétaire de l'adresse, montant d'uint);
Item [] _items privés;
mapping (address => uint) public _pendingWithdrawals;
modificateur onlyIfItemExists (uint itemID) {
require (_items [itemID] .seller! = adresse (0));
_;
}
function addNewItem (nom bytes32, prix uint) public returns (uint) {
_items.push (Item ({
nom nom,
prix: prix,
vendeur: msg.sender,
status: ItemStatus.active
}));
émettre ItemAdded (nom, prix, msg.sender);
// L'élément est poussé à la fin, de sorte que la longueur est utilisée pour
// l'ID de l'article
return _items.length - 1;
}
function getItem (uint itemID) affichage public onlyIfItemExists (itemID)
retourne (bytes32, uint, adresse, uint) {
Élément de stockage d'élément = _items [itemID];
return (item.name, item.price, item.seller, uint (item.status));
}
function buyItem (uint itemID) public payable uniquementIfItemExists (itemID) {
Stockage d'élément currentItem = _items [itemID];
require (currentItem.status == ItemStatus.active);
require (currentItem.price == msg.value);
_pendingWithdrawings [currentItem.seller] = msg.value;
currentItem.status = ItemStatus.sold;
émet ItemPurchased (itemID, msg.sender, currentItem.seller);
}
function removeItem (uint itemID) public onlyIfItemExists (itemID) {
Stockage d'élément currentItem = _items [itemID];
require (currentItem.seller == msg.sender);
require (currentItem.status == ItemStatus.active);
currentItem.status = ItemStatus.removed;
Emit ItemRemoved (itemID);
}
function pullFunds () public returns (booléen) {
require (_pendingWithdrawals [msg.sender]> 0);
uint outstandingFundsAmount = _pendingWithdrawals [msg.sender];
if (msg.sender.send (outstandingFundsAmount)) {
émettent FundsPulled (msg.sender, outstandingFundsAmount);
retourner vrai;
} autre {
return false;
}
}
}
Après avoir fait cela, essayez d'exécuter la compilation truffle
pour voir si le code va compiler. Comme Solidity a tendance à changer de convention, si votre code ne compile pas, la solution probable est d'utiliser une version plus ancienne du compilateur (0.4.21 est la version avec laquelle il a été écrit et se passera bien).
Vous devez écrire une migration pour que Truffle sache comment déployer votre contrat dans la blockchain. Allez dans le dossier migrations
et créez un nouveau fichier appelé 2_deploy_contracts.js
. Dans ce fichier, ajoutez le code suivant:
var HashMarket = artefacts.require ("./ HashMarket.sol");
module.exports = function (déployer) {
deployer.deploy (HashMarket);
}
Comme nous n'avons qu'un seul contrat, le fichier de migration est très simple.
Maintenant, lancez truffle migrate
et vous obtiendrez quelque chose comme ça:
Utilisation du développement de réseau.
Migration en cours: 1_initial_migration.js
Déploiement de migrations ...
... 0xad501b7c4e183459c4ee3fee58ea9309a01aa345f053d053b7a9d168e6efaeff
Migrations: 0x9d69f4390c8bb260eadb7992d5a3efc8d03c157e
Enregistrement d'une migration réussie vers le réseau ...
... 0x7deb2c3d9dacd6d7c3dc45dc5b1c6a534a2104bfd17a1e5a93ce9aade147b86e
Enregistrement d'artefacts ...
Migration en cours: 2_deploy_contracts.js
Déploiement de HashMarket ...
... 0xcbc967b5292f03af2130fc0f5aaced7080c4851867abd917d6f0d52f1072d91e
HashMarket: 0x7918eaef5e6a21a26dc95fc95ce9550e98e789d4
Enregistrement d'une migration réussie vers le réseau ...
... 0x5b6a332306f739b27ccbdfd10d11c60200b70a55ec775e7165358b711082cf55
Enregistrement d'artefacts ...
Tester les contrats intelligents
Vous pouvez utiliser Solidity ou JavaScript pour tester les contrats intelligents. La solidité peut être un peu plus intuitive lors du test des contrats intelligents, mais JavaScript vous offre bien plus de possibilités
Solidity testing
Pour commencer le test, dans le dossier test
de votre projet, créez un fichier appelé TestHashMarket.sol
. La suite Truffle nous fournit des fonctions d'assistance pour les tests, nous devons donc les importer. Au début du fichier, ajoutez:
pragma solidity ^ 0.4.20;
importer "truffle / Assert.sol";
import "truffle / DeployedAddresses.sol";
importer "../contracts/HashMarket.sol";
Les deux premières importations sont les plus importantes.
L'import Assert
nous donne accès à diverses fonctions de test, comme Assert.equals ()
Assert. greaterThan ()
etc. De cette manière, Assert travaille avec Truffle pour automatiser la plupart des écritures de code "ennuyeuses".
L'importation DeployedAddresses
gère les adresses de contrat pour nous. Puisque chaque fois que vous changez votre contrat, vous devez le redéployer à une nouvelle adresse et même si vous ne le changez pas, chaque fois que vous testez le contrat devrait être redéployé pour commencer à l'état vierge. La bibliothèque DeployedAddresses
gère cela pour nous.
Écrivons maintenant un test. Sous les directives import
ajoutez ce qui suit:
contract TestHashMarket {
function testAddingNewProduct () public {
// DeployedAddresses.HashMarket () gère l'adresse du contrat
// gestion pour nous
Marché HashMarket = HashMarket (DeployedAddresses.HashMarket ());
bytes32 expectedName = "T";
uint attenduPrix = 1000;
uint itemID = market.addNewItem (expectedName, expectedPrice);
nom bytes32;
prix d'uint;
adresse vendeur;
statut de l'uint;
(nom, prix, vendeur, statut) = market.getItem (itemID);
Assert.equal (nom, expectedName, "Le nom de l'élément doit correspondre");
Assert.equal (price, expectedPrice, "Le prix de l'article doit correspondre");
Assert.equal (status, uint (HashMarket.ItemStatus.active), "L'état de l'élément à la création doit être .active");
Assert.equal (vendeur, ceci, "L'appelant de fonction devrait être le vendeur");
}
}
Regardons quelques-unes des parties importantes d'un test. Premièrement:
HashMarket market = HashMarket (DeployedAddresses.HashMarket ());
Ce code utilise la bibliothèque DeployedAddresses
pour créer une nouvelle instance de
le contrat HashMarket
à tester.
Assert.equal ( , )
Cette partie du code se charge de vérifier si deux valeurs sont égales. Si oui, il communique un message de réussite à la suite de tests. Sinon, il communique un échec. Il ajoute également le message afin que vous puissiez savoir où votre code a échoué.
Maintenant, nous allons lancer ceci:
test de la truffe
Vous devriez obtenir un résultat comme celui-ci:
TestHashMarket
1) testAddingNewProduct
> Aucun événement n'a été émis
0 passant (879ms)
1 échec
1) TestHashMarket testAddingNewProduct:
Erreur: Exception de machine virtuelle lors du traitement de la transaction: rétablir
à Object.InvalidResponse (/usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/errors.js:38:1)
à /usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/requestmanager.js:86:1
à /usr/local/lib/node_modules/truffle/build/webpack:/~/truffle-provider/wrapper.js:134:1
à XMLHttpRequest.request.onreadystatechange (/usr/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/httpprovider.js:128:1)
à XMLHttpRequestEventTarget.dispatchEvent (/usr/local/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:64:1)
à XMLHttpRequest._setReadyState (/usr/local/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:354:1)
à XMLHttpRequest._onHttpResponseEnd (/usr/local/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:509:1)
à IncomingMessage. (/usr/local/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:469:1)
à endReadableNT (_stream_readable.js: 1106: 12)
à l'adresse process._tickCallback (internal / process / next_tick.js: 178: 19)
Notre test a échoué: – (
Passons au contrat pour inspecter les erreurs possibles.
Après une inspection minutieuse, nous verrons que le problème avec notre contrat se trouve dans la déclaration
instruction de notre méthode addNewItem
:
function addNewItem (nom bytes32, prix uint) public returns (uint) {
_items.push (Item ({
nom nom,
prix: prix,
vendeur: msg.sender,
status: ItemStatus.active
}));
// L'élément est poussé à la fin, de sorte que la longueur est utilisée pour
// l'ID de l'article
return _items.length;
}
Puisque les tableaux sont indexés à zéro, et que nous utilisons la position du tableau comme ID, nous devrions retourner _items.length - 1
. Corrigez cette erreur et relancez cette opération:
test à la truffe
Vous devriez recevoir un message beaucoup plus heureux:
TestHashMarket
✓ testAddingNewProduct (130ms)
1 passant (729ms)
Nous avons utilisé le test Truffle pour corriger une erreur très probable dans notre code!
JavaScript Testing
Truffle nous permet d'utiliser JavaScript pour tester, en tirant parti du framework de test Mocha. Cela vous permet d'écrire des tests plus complexes et d'obtenir plus de fonctionnalités de votre framework de test.
Ok, écrivons le test. Tout d'abord, dans le dossier test
créez un fichier et appelez-le hashmarket.js
.
La première chose à faire est d'obtenir la référence à notre contrat en JavaScript. Pour cela, nous allons utiliser la fonction de Truffle. (...)
function:
var HashMarket = artefacts.require ("./ HashMarket.sol");
Maintenant que nous avons la référence au contrat, commençons à écrire des tests. Pour commencer, nous allons utiliser la fonction contrat
qui nous est fournie:
contrat ("HashMarket", fonction (accounts) {
});
Cela crée une suite de tests pour notre contrat. Maintenant, pour tester, nous utilisons Mocha il
syntaxe:
contrat ("HashMarket", fonction (comptes) {
il ("devrait ajouter un nouveau produit", function () {
});
});
Ceci décrit le test que nous allons écrire et présente un message pour que nous connaissions le but du test. Maintenant, écrivons le test lui-même. A la fin, le fichier hashmarket.js
devrait ressembler à ceci. Le raisonnement est expliqué dans les commentaires du code source:
var HashMarket = artefacts.require ("./ HashMarket.sol");
contrat ("HashMarket", fonction (comptes) {
il ("devrait ajouter un nouveau produit", function () {
// Définit les noms des données de test
var itemName = "TestItem";
var itemPrice = 1000;
var itemSeller = comptes [0];
// Comme toutes nos fonctions de test sont asynchrones, nous stockons
// contrat l'instance à un niveau supérieur pour permettre l'accès de
// toutes les fonctions
var hashMarketContract;
// L'ID de l'article sera fourni de manière asynchrone, donc nous l'extrayons
var itemID;
return HashMarket.deployed (). then (fonction (instance) {
// définit une instance de contrat dans une variable
hashMarketContract = instance;
// S'abonner à un événement Solidity
instance.ItemAdded ({}). watch ((erreur, résultat) => {
if (erreur) {
console.log (erreur);
}
// Une fois l'événement déclenché, stockez le résultat dans le
// variable externe
itemID = result.args.itemID;
});
// Appelle la fonction addNewItem et renvoie la promesse
renvoie instance.addNewItem (itemName, itemPrice, {from: itemSeller});
}). then (function () {
// Cette fonction est déclenchée après la transaction d'appel addNewItem
// a été extrait. Appelez maintenant la fonction getItem avec l'ID d'élément
// nous avons reçu de l'événement
return hashMarketContract.getItem.call (itemID);
}). then (fonction (résultat) {
// Le résultat de getItem est un tuple, on peut le déconstruire
// à des variables comme celle-ci
var [name, price, seller, status] = résultat;
// Commencer les tests. Utilisez web3.toAscii () pour convertir le résultat de
// le contrat intelligent de Solidity bytecode à ASCII. Après ça
// utilise le .replace () pour remplacer les octets en excès de bytes32
assert.equal (itemName, web3.toAscii (name) .replace (/ u0000 / g, ''), "Le nom n'a pas été ajouté correctement");
// Utilise assert.equal () pour vérifier toutes les variables
assert.equal (itemPrice, price, "Le prix n'a pas été ajouté correctement");
assert.equal (itemSeller, seller, "Le vendeur n'a pas été ajouté correctement");
assert.equal (status, 0, "Le statut n'a pas été ajouté correctement");
});
});
});
Lancez le test de la truffe
et vous devriez obtenir quelque chose comme ceci:
TestHashMarket
✓ testAddingNewProduct (109ms)
Contrat: HashMarket
✓ devrait ajouter un nouveau produit (64ms)
2 passes (876ms)
Votre test est passé et vous pouvez être sûr de ne pas avoir de bugs de régression.
Pour les devoirs, écrivez des tests pour toutes les autres fonctions du contrat.