Testes com Hardhat

A seguir, é apresentado um exemplo completo de como escrever e executar testes unitários para um contrato inteligente na Ethereum utilizando o Hardhat. Este exemplo incluirá a criação do contrato inteligente, a configuração dos fixtures e a escrita de diversos testes.

Paso 1: Configuração do Projeto

  1. Devemos ter o Hardhat instalado e um projeto básico criado (caso não saibas como fazer isso, consulte o módulo 4).

  2. Certifique-se de ter o plugin hardhat-toolbox instalado.

    npm install --save-dev @nomicfoundation/hardhat-toolbox

    Além disso, é necessário adicionar a seguinte linha no início do arquivo hardhat.config.js

    require("@nomicfoundation/hardhat-toolbox");

Paso 2: Definir o contrato a ser testado

Utilizaremos o contrato Token.sol, que será colocado na pasta contracts com o seguinte conteúdo:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Token {
    string public name = "KIPU";
    string public symbol = "KIP";
    uint8 public decimals = 18;
    uint256 public totalSupply;
    mapping(address => uint256) public balanceOf;

    event Transfer(address indexed from, address indexed to, uint256 value);

    constructor(uint256 _initialSupply) {
        totalSupply = _initialSupply * (10 ** uint256(decimals));
        balanceOf[msg.sender] = totalSupply;
    }

    function transfer(address _to, uint256 _value) public returns (bool success) {
        require(_to != address(0), "Invalid address");
        require(balanceOf[msg.sender] >= _value, "Insufficient balance");
        
        balanceOf[msg.sender] -= _value;
        balanceOf[_to] += _value;
        
        emit Transfer(msg.sender, _to, _value);
        
        return true;
    }
}

Paso 3: Escrever os testes

Crie um arquivo de testes chamado TokenTest.js dentro da pasta test com o seguinte conteúdo:

Explicação do script acima

Importações e configurações necessárias:

  • chai: Biblioteca de asserções que facilita a verificação dos resultados esperados nos testes.

  • ethers: Biblioteca para interação com contratos inteligentes e a blockchain.

  • loadFixture: Função que carrega um estado inicial fixo (fixture) para os testes, garantindo que todos comecem com as mesmas condições.

Depois definimos o fixture

  • deployTokenFixture: Esta função implanta o contrato Token com um fornecimento inicial. Também obtém três contas para serem utilizadas nos testes (owner, addr1, addr2).

Primeiro teste: Verificar o proprietário

  • Propósito: Verificar se o proprietário inicial (a conta que implanta o contrato) recebe o fornecimento total de tokens.

  • Proceso:

    1. Implantar o contrato utilizando o fixture.

    2. Obter o saldo do proprietário.

    3. Verificar se o saldo é igual ao fornecimento inicial (1000 tokens convertidos para a unidade mínima).

Segundo teste: Verificar o fornecimento total

  • Propósito: Garantir que o fornecimento total de tokens seja atribuído corretamente ao proprietário.

  • Processo:

    1. Implantar o contrato utilizando o fixture.

    2. Obter o saldo do proprietário.

    3. Verificar se o saldo do proprietário é igual ao fornecimento total de tokens

Terceiro teste: Transferência de tokens

  • Propósito: Verificar se os tokens são transferidos corretamente entre contas.

  • Processo:

    1. Implantar o contrato utilizando o fixture.

    2. Transferir 50 tokens do owner para addr1.

    3. Verificar que addr1 enha recebido 50 tokens.

    4. Transferir 50 tokens de addr1 para addr2.

    5. Verificar que addr2 tenha recebido 50 tokens e que addr1 tenha um saldo de 0 tokens.

Quarto teste: Verificar que uma conta não pode transferir se não tiver saldo suficiente

  • Propósito: Garantir que a transferência falhe se o remetente não tiver tokens suficientes.

  • Proceso:

    1. Implantar o contrato utilizando fixture.

    2. Tentar transferir 1 token de addr1 (que não possui tokens) para o owner.

    3. Verificar que a transação seja revertida com a mensagem "Insufficient balance".

    4. Verificar que o saldo do owner não tenha sido alterado.

Quinto teste: Atualização dos saldos após uma transferência

  • Propósito: Verificar se os saldos são atualizados corretamente após as transferências.

  • Proceso:

    1. Implantar o contrato utilizando o fixture.

    2. Transferir 100 tokens do owner para addr1.

    3. Transferir 50 tokens do owner para addr2.

    4. Verificar se o saldo final do owner é igual ao saldo inicial menos 150 tokens.

    5. Verificar se addr1 possui 100 tokens e addr2 possui 50 tokens.

Passo 4: Executar os testes

Para executar os testes, utilize o seguinte comando no terminal:

Este comando executará todos os testes definidos no arquivo TokenTest.js e exibirá os resultados no console.

Se os testes forem bem-sucedidos, você verá um resultado semelhante a este:

Parabéns!! Você executou com sucesso seus primeiros testes. Agora é hora de começar a testar outros contratos que você tenha criado.

O Hardhat também permite verificar a cobertura dos seus testes. Para isso, basta executar o seguinte comando:

E você obterá um relatório semelhante a este

Mais uma coisa: Chai

Neste exemplo, utilizamos Chai para realizar nossos testes. Chai é uma biblioteca de assertions (afirmações) para Node.js que pode ser combinada com qualquer framework de testes, como o Hardhat. No contexto de testes para contratos inteligentes, o Chai é amplamente utilizado devido à sua sintaxe amigável e capacidades robustas de asserção.

Para utilizar o Chai, primeiro é necessário importar a biblioteca e, em seguida, usar seus métodos nas suas asserções dentro dos testes. Aqui está um exemplo básico de configuração:

Chai oferece três estilos principais de asserção:

  1. Assert: Estilo clássico baseado em funções.

  2. Expect: Estilo BDD (Behavior-Driven Development), mais legível.

  3. Should: Outro estilo BDD, que adiciona propriedades ao Object.prototype.

No nosso exemplo, utilizamos o estilo expect, pois é um dos mais usados em testes de contratos inteligentes com Hardhat, graças à sua clareza e sintaxe intuitiva.

Principais comandos do Chai

  1. Igualdade (equal, eql)

  • equal verifica igualdade estrita (===).

  • eql verifica igualdade profunda (ideal para objetos e arrays).

  1. Booleanos (true, false)

Verifica se um valor é verdadeiro ou falso.

  1. Existência (exist)

Verifica se um valor não é null nem undefined.

  1. Tipos (a, an)

Verifica o tipo de um valor.

  1. Conteúdo (include, contain)

Verifica se um valor contém outro (em arrays, strings ou objetos).

  1. Comprimento (lengthOf)

Verifica o comprimento de um array, string ou Map.

  1. Maior e menor que (above, below)

Verifica se um valor é maior ou menor que outro.

  1. Aproximação (closeTo)

Verifica se um número está próximo de outro, dentro de uma margem de erro.

  1. Emissão de eventos (emit, withArgs)

No contexto de testes de contratos inteligentes, verificamos se um evento é emitido com os argumentos corretos.

Para mais informações sobre Chai, consulte a documentação do Hardhat.

Last updated