Изучая тему написания тестов, я активно ищу примеры, да, и, в целом, различные логические виды тестов. И сегодня хочу рассказать вам о новом виде, который я открыл для себя буквально на днях - Мутационные тесты!
Классно звучит, не так ли? О них я узнал из этой статьи.
https://www.rareskills.io/post/solidity-mutation-testing
Что мне понравилось больше всего, так это то, что об этой теме редко говорят, а вообще идея стоящая.
Мутационные тесты - это такие тесты, когда вы целенаправленно изменяете код своего контракта, и смотрите, как поведет себя тест.
Простой пример:
// original function
function mint() external payable {
require(msg.value >= PRICE, "insufficient msg value");
}
// mutated function
function mint() external public {
require(msg.value < PRICE, "insufficient msg value");
}
Вы написала контракт в котором была представлена функция выше, позже создали для нее тест. А затем специально изменили знак условия, чтобы проверить, пройдет ли тест или нет.
Да, это простой пример, чтобы донести идею такого тестирования, а теперь чуть подробнее.
Смотрите, всем тестировщикам требуют написание тестов, которые бы покрывали 100% кода, функция, веток и условий. При этом все понимаю, что 100% покрытие кода тестами, вовсе не означает отсутствие ошибок. Вспомнить, хотя бы о тех же логических...
Возьмем другой пример.
function mint(address to_, string memory questId_) public onlyMinter {
// business logic
} Допустим мы написали такую функцию в своем контракте и создали тесты дл нее. А что произойдет, если мы уберем модификатор? Тест все равно пройдет!
Или еще пример:
uint256 public LIMIT = 5;
// original
function mint(uint256 amount) external {
require(amount < LIMIT, "exceeds limit");
}
// mutation
function mint(uint256 amount) external {
require(amount <= LIMIT, "exceeds limit");
}
Если мы напишем тесты, где значения для установки будут 3 или 8?
Во втором случае тест пройдет, хотя условие будет нарушено!
Как я понял из статьи, мутационные тесты помогают нам создавать такие тесты, которые будут удовлетворять логику нашего протокола не зависимо от изменений. Более того, это помогает проявлять непредвиденные уязвимости в нашем контракте!
Как можно нам проводить такие тесты:
1. Удалять модификатор из функций;
2. Изменять знаки условий: ==, >=, >= и т.д.;
3. Изменяя значения констант;
4. Заменят строковые значения на пустые строки;
5. Изменяя true/false в результатах;
6. Изменяя && на || или побитовое & на |;
7. Изменяя математические операторы;
8. Удаляя целые линии кода;
9. Меняя местами линии кода;
Для удобства проведения подобных тестов даже были созданы специальные инструменты:
1. vertigo-rs
https://github.com/RareSkills/vertigo-rs
2. Gambit
https://docs.certora.com/en/latest/docs/gambit/index.html
3. Universal Mutator
https://github.com/sambacha/universalmutator/tree/new-solidity-rules
Мутационные тесты сродни обычным unit тестам, а значит они никак не работают с состояниями контракта. Поэтому разработчикам не следует рассматривать какой-либо вариант тестирование, как единственный правильный и преследовать цель заполнить coverage на 100%. Только комбинация нескольких видов тестирования поможет сократить количество потенциальных проблем в вашем коде.
#foundry #lesson26