Что такое фаззинг
Нечеткое тестирование или нечеткое тестирование — это метод предоставления случайных неожиданных входных данных вашим программам для проверки возможных сбоев или пограничных случаев. Фаззинг может пролить свет на некоторые логические ошибки или проблемы с производительностью, поэтому его всегда стоит добавлять в код, где важны стабильность и производительность.
Go проекты для фаззинга
В настоящее время существует несколько хорошо поддерживаемых проектов для фаззинга:
Но мы не будем рассматривать их в этой статье, так как у нас есть отличные новости, так как команда Go приняла предложение добавить в язык поддержку нечеткого тестирования. Он будет доступен в стандартном тулчейне Go 1.18 — docs.
Установите Go 1.18
На момент написания этого поста Go 1.18 находится только в бета-версии, поэтому давайте сначала установим его с помощью gotip
.
go install golang.org/dl/gotip@latest
gotip download
Примечание: вероятно, когда вы читаете это, Go 1.18 уже выпущен, поэтому вы можете обновить свою команду go обычным способом.
Наша «домашняя» функция
Давайте напишем простую функцию, для которой позже мы добавим фаззинг. И мы намеренно добавим некоторые ошибки.
Функция Equal
будет сравнивать два фрагмента байтов поэлементно.
package fuzztestingingo func Equal(a []byte, b []byte) bool { for i := range a { if a[i] != b[i] { return false } } return true }
Написание фазз-теста
- Создайте файл
equal_test.go
- Включим простой регулярный тест
package fuzztestingingo
import "testing"
func TestEqual(t *testing.T) {
if !Equal([]byte{'f', 'u', 'z', 'z'}, []byte{'f', 'u', 'z', 'z'}) {
t.Error("expected true, got false")
}
}
gotip test .
ok github.com/plutov/packagemain/23-fuzz-testing-in-go 0.922s
Тест работает, но он проверяет только простой вариант использования, и, как вы, наверное, уже заметили, у нашей функции есть несколько пограничных случаев. Попробуем раскрыть их, написав фазз-тест.
Нечеткие тесты могут быть включены в ваши обычные файлы _test.go
с помощью функций, начинающихся с Fuzz
, которые принимают новый тип *testing.F
.
func FuzzEqual(f *testing.F) { // target, can be only one per test // values of a and b will be auto-generated f.Fuzz(func(t *testing.T, a []byte, b []byte) { Equal(a, b) }) }
Примечание. В тесте может быть только одна цель.
Чтобы включить фаззинг, мы должны запустить go test
с флагом -fuzz
:
gotip test -fuzz .
fuzz: elapsed: 0s, gathering baseline coverage: 0/2 completed
failure while testing seed corpus entry: FuzzEqual/84ed65595ad05a58e293dbf423c1a816b697e2763a29d7c37aa476d6eef6fd60
fuzz: elapsed: 0s, gathering baseline coverage: 1/2 completed
--- FAIL: FuzzEqual (0.02s)
--- FAIL: FuzzEqual (0.00s)
testing.go:1349: panic: runtime error: index out of range [0] with length 0
Исправление функции «домашнее животное»
Мы нашли свою ошибку, так как наш код не проверяет размер среза. Давайте исправим это и снова запустим фазз.
package fuzztestingingo
func Equal(a []byte, b []byte) bool {
// if length is not the same - slices are not equal
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
gotip test -fuzz .
fuzz: elapsed: 0s, gathering baseline coverage: 0/11 completed
fuzz: elapsed: 0s, gathering baseline coverage: 11/11 completed, now fuzzing with 8 workers
fuzz: elapsed: 3s, execs: 542957 (180982/sec), new interesting: 0 (total: 11)
fuzz: elapsed: 6s, execs: 1035678 (164216/sec), new interesting: 0 (total: 11)
...
Вам решать, как долго будет выполняться фаззинг. Вполне возможно, что выполнение фаззинга может продолжаться бесконечно, если оно не найдет ошибок, как в нашем случае. Мы можем добавить аргумент -fuzztime
, который сообщает, сколько итераций нужно выполнить.
gotip test -fuzz=. -fuzztime=5s .
fuzz: elapsed: 0s, gathering baseline coverage: 0/10 completed
fuzz: elapsed: 0s, gathering baseline coverage: 10/10 completed, now fuzzing with 8 workers
fuzz: elapsed: 3s, execs: 474778 (158251/sec), new interesting: 0 (total: 10)
fuzz: elapsed: 5s, execs: 729255 (121223/sec), new interesting: 0 (total: 10)
PASS
ok github.com/plutov/packagemain/23-fuzz-testing-in-go 5.557s
Теперь давайте рассмотрим результат фаззинга, есть несколько метрик:
- истекло: количество времени, прошедшее с момента начала процесса
- execs: общее количество входных данных, которые были выполнены для цели fuzz.
- новое интересное: общее количество «интересных» входных данных, которые были добавлены к сгенерированному корпусу во время выполнения этого фаззинга.
Имейте в виду, что фаззинг может потреблять много памяти и может повлиять на производительность вашей машины во время ее работы, поэтому вам следует быть осторожным при использовании фаззинга в конвейере CI.