Сегодня мы начинаем практиковаться с тестами и пойдем также с самого начала. Мы разберем все "по кирпичикам", постепенно усложняя и материал и тесты.
Что нужно знать перед написанием тестов?
Во-первых, все тесты пишутся в папке test. Можно, конечно, создавать новые папки, но придется переписывать пути для Foundry и производить другие не нужные манипуляции. Поэтому проще все делать уже в одной папке, в которой строить нужную нам архитектуру.
Во-вторых, по правилам Foundry название файлов для тестов должно значится как fileName.t.sol. Другими словами окончание t.sol обязательно для каждого файла теста.
В-третьих, практически в каждом файле тестов нам нужно будет импортировать библиотеку Test от Forge и контракт, для которого будут писаться тесты:
import {Test, console2} from "forge-std/Test.sol";
В четвертых, уже наш тестовый контракт обязательно должен иметь наследование от Test:contract myContract is Test {}
Возьмем, к примеру, файл контракта и файл теста, которые создаются при инициализации проекта: Counter.sol:Наш контракт:
contract Counter {
uint256 public number;
function setNumber(uint256 newNumber) public {
number = newNumber;
}
function increment() public {
number++;
}
}
И напишем к нему простые тесты. Для начала импортируем библиотеку Test и наш контракт, а также создаем наследование:// SPDX-License-Identifier: UNLICENSEDИз Test, как вы видите, мы получаем два объекта: сам Test и еще Console. Второй объект поможет нам логировать данные из тестов и выводить их в консоль, очень похоже на console.log из javascript.
pragma solidity ^0.8.13;
import {Test, console2} from "forge-std/Test.sol";
import {Counter} from "../src/Counter.sol";
contract CounterTest is Test {}
Далее нам нужно будет создать объект нашего контракта и поместить его в переменную.
Counter public counter;Но как поместить объект контракта в переменную при том, что в Foundry нет конструктора?
Для этого есть специальная служебная функция setUp(). Если вы переходите на Foundry с Hardhat, то можете представить себе setUp() как нечто подобное loadFixture или beforeEach. Другими словами setUp() будет исполняться перед каждым отдельным тестом. Это нужно для того, чтобы наш локальный блокчейн, на котором и будут проходить все тесты, не хранил изменения в стейте, а очищал его. Так сами тесты будут более достоверными.
Итак, в setUp() мы будем разворачивать наш контракт Counter:
setUp() public {
counter = new Counter();
}
Теперь можно написать наш первый тест. Самое главное, что нужно запомнить сейчас так это то, что название тестов:
1. Должны начинаться со слова test;
2. Должны кратко описывать то, что они тестируют;
В нашем случае мы можем создать тест с названием:
function test_Increment() public {}
Также следует запомнить, что все функции для тестов должны иметь область видимости public или external, чтобы программа могла работать с ними.Итак, теперь нам нужно понять логику проведения тестов. По мере постов, вы более подробно поймете, о чем идет речь, поэтому сейчас напишу по простому.
Смотрите, у нас есть функция, которую мы хотим протестировать. Результатом работы этой функции будет какое-то значение или действие: изменение переменной, баланса, голосов, доступа или чего-то еще. Т.е. мы ожидаем определенные результаты.
В тестах нам и нужно сравнить наши ожидания с тем, что явилось результатом исполнения функции. Например, у нас в контракте есть функция increment(). Что она делает? Она прибавляет 1 к значению в number.
Как написать для этого тест?
function test_Increment() public {
counter.increment();
assertEq(counter.number(), 1);
//console2.log(counter.number());
}
В начале мы вызываем функцию в нашем контракте для изменения переменной number, а затем проверяем, что наше ожидаемое значение будет равно значению, которое сохранилось в переменной. С помощью console2.log мы смотрим значение number.Теперь нам нужно обратиться к Foundry в терминале и попросить сделать тест. Это делается через команду: