Перейти к содержанию

Покрытие кода Solidity в Foundry: что показывает отчёт

В предыдущем уроке мы записали независимую модель текущего выпуска. Теперь оценим набор сценариев с другой стороны: какие части исходника они способны задействовать и какие требования действительно выражены утверждениями.

Покрытие кода помогает искать непосещённые участки, но не измеряет правильность приложения напрямую. Составим карту требований нашего CourseCatalog и план чтения будущего отчёта. Команда покрытия не запускалась, поэтому у этой главы нет полученного процента, зелёного отчёта или утверждения о полном исследовании реестра.

Выполнение строки не равно проверке результата

Представим сценарий, который вызывает publish разрешённым издателем, а затем завершается без чтения. Строки записи mapping и испускания события могут выполниться, однако функция не утверждает, что сохранён правильный хеш. Реализация, записывающая постоянное значение, может остаться незамеченной.

Проверка assertCourse(id, digest, true) добавляет смысл: она связывает исходный аргумент с наблюдаемой записью. Покрытие показывает, что путь был посещён; утверждение определяет, какой результат требуется на этом пути. Эти сведения дополняют друг друга и не должны смешиваться в одном выводе.

Подобное различие возникает у отрицательного вызова. Сценарий может дойти до revert, но без точного ожидания ошибки не отличить запрет доступа от неправильных данных. Поэтому ранее мы требовали Unauthorized для чужого отправителя и InvalidCourse для запрещённого входа издателя.

Готовим карту прикладного договора

Вернёмся к требованиям второго урока и сопоставим их с уже написанными функциями. Таблица не является отчётом инструмента: это редакционная карта, по которой можно увидеть отсутствующий смысловой сценарий ещё до исполнения.

Требование Существующий пример Дополнительный вопрос
Издатель задан аргументом Создание экземпляра Не спутан ли он с создателем?
Чужой адрес не пишет Fuzz роли Сохранён ли прежний выпуск?
Нулевые входы запрещены Граничные сценарии Проверены ли оба аргумента отдельно?
Выпуск заменяется Две публикации Допустимо ли повторить тот же хеш?
Отключение хранит хеш Жизненный цикл Не выполняется ли удаление struct?
Журнал сообщает выпуск Ожидание события Проверен ли источник и номер?

Один пункт может требовать нескольких функций. Например, запрет чужому адресату охватывает и publish, и deactivate. Обе функции способны использовать похожее условие, но будущая правка одной из них не обязана изменить другую. Карта помогает не потерять отдельное требование из-за похожих строк реализации.

При добавлении нового метода сначала расширяем договор и карту, затем выбираем сценарии. Автоматическое копирование старого теста может посетить новую функцию, но не выразить её новые ограничения. Покрытие особенно плохо заменяет анализ требований, которые вообще отсутствуют в исходнике.

Читаем разные виды покрытия

Отчёт может описывать строки, выражения или ветви. Эти величины отвечают на разные вопросы. Посещённая строка с условием не обязательно означает, что исследованы оба его исхода. Сложное логическое выражение также может иметь несколько сочетаний входов при похожем видимом пути.

Для courseId == 0 || contentHash == 0 нужны отдельные случаи запрещённого номера и запрещённого отпечатка. Один вызов с обоими нулями может завершиться в той же ветви, но не обнаружит исчезновение одной части проверки. Именно поэтому таблица граничных входов полезна даже при красивом общем проценте.

При чтении будущего отчёта сначала смотрим на исходник и выбранный вид метрики. Затем связываем непосещённый участок с прикладным требованием. Если участок намеренно недостижим при нашем профиле, причину фиксируем; если сценарий отсутствует, добавляем осмысленную проверку, а не случайный вызов ради строки.

План получения отчёта в будущем

В зафиксированном профиле Foundry для покрытия предусмотрена команда forge coverage. Здесь она приведена как будущий шаг читателя и не выполнялась. Прежде чем запускать её в собственной среде, читатель должен подготовить зависимости и убедиться, что обычная сборка использует согласованные версии.

Отчёт необходимо связывать с конкретными исходниками, настройками и набором сценариев. Результат от вчерашнего контракта не описывает сегодняшнюю правку. Полезно сохранить идентификатор редакции материалов, версию инструментов, выбранные параметры и сам отчёт вместе, чтобы вывод можно было оценить позднее.

При инструментировании покрытия могут отличаться условия компиляции и измерения. Не стоит брать расход газа из такого прогона и сравнивать его с обычной сборкой как будто окружения идентичны. В двадцать восьмом уроке мы отдельно зададим условия для сравнения газа и дерева вызовов.

Изменённый пример: забытое событие

Предположим, автор удаляет emit Published, сохраняя запись mapping. Сценарии чтения продолжают выражать правильные ожидания состояния, но индексатор теряет сообщение. Общий процент покрытия нового, более короткого исходника может даже выглядеть лучше, хотя прикладное требование исчезло из программы.

Ожидание события из седьмого урока предназначено для этого нарушения. Оно связано с внешним договором, поэтому остаётся нужным независимо от числа строк. Такой пример показывает, почему метрика относительно текущего кода не выявляет все случаи удаления необходимого поведения.

Другой пример — случайное удаление проверки роли. Если сценарии вызывают только издателя, обе версии выполняются нормально. Отрицательные свойства с чужими адресами выражают то требование, которое исчезло. Для смыслового анализа сначала спрашиваем, что программа должна запрещать, а не только куда она дошла.

Учитываем границы локальной лаборатории

Даже полный отчёт по CourseCatalog.sol не охватывает браузерную форму, RPC, индексатор, хранение манифеста и работу кошелька. Эти компоненты имеют собственные сценарии и источники отказов. Следует называть конкретный измеренный файл и профиль, избегая широкого заявления о безопасности всего сайта.

Также покрытие не является формальным доказательством всех входов. Параметризованные проверки исследуют конечную выборку, последовательности имеют конечную длину, а подготовка задаёт ограниченное окружение. Метрика показывает посещение в этом окружении; её полезность зависит от качества сформулированных свойств.

Для нашей текущей подготовки корректный итог звучит так: карта требований составлена, соответствующие исходники сценариев написаны, фактическое покрытие пока не получено. Это даёт рецензенту проверяемый план и не создаёт ложного впечатления законченного аудита.

В следующем уроке перейдём к свойствам после последовательностей. Возможности инструмента описаны в руководстве Foundry, зафиксированная версия доступна в репозитории Foundry 1.3.1. Оглавление курса.

Оглавление серии