Метод testing.F.Fuzz
Метод testing.F.Fuzz запускает фаззинг-тест,
который многократно вызывает фаззинг-функцию с случайно
сгенерированными значениями. Фаззинг-тестирование помогает
обнаруживать ошибки и паники, которые возникают при обработке
некорректных или неожиданных входных данных. Первым параметром
метод принимает фаззинг-функцию (func(f *testing.F)),
которая содержит логику тестирования и вызывается с различными
значениями.
Синтаксис
func Fuzz(f *testing.F)
Метод Fuzz вызывается на объекте типа
*testing.F. Внутри фаззинг-функции необходимо
использовать метод f.Add для добавления "seed"
(начальных) корпусов и f.Fuzz для определения
основной логики проверки.
Пример
Давайте напишем фаззинг-тест для функции, которая принимает строку и проверяет, является ли она палиндромом. Мы добавим несколько начальных значений и определим логику проверки:
package main
import (
"testing"
)
func IsPalindrome(s string) bool {
for i := 0; i < len(s)/2; i++ {
if s[i] != s[len(s)-1-i] {
return false
}
}
return true
}
func FuzzIsPalindrome(f *testing.F) {
// Добавляем seed корпусы
f.Add("abcba")
f.Add("abcd")
f.Add("a")
f.Add("")
f.Fuzz(func(t *testing.T, s string) {
result := IsPalindrome(s)
if result && len(s) > 1 {
// Проверяем, что палиндром читается одинаково с обеих сторон
for i := 0; i < len(s)/2; i++ {
if s[i] != s[len(s)-1-i] {
t.Errorf("IsPalindrome(%q) = true, but it's not a palindrome", s)
}
}
}
})
}
Для запуска фаззинг-теста используйте команду:
go test -fuzz=FuzzIsPalindrome
Результат выполнения команды (пример):
"fuzz: elapsed: 0s, execs: 0 (0/sec), new interesting: 0"
"fuzz: elapsed: 1s, execs: 100 (100/sec), new interesting: 2"
"fuzz: elapsed: 2s, execs: 200 (100/sec), new interesting: 3"
"PASS"
Пример
Фаззинг-тест может принимать несколько типов данных одновременно. Например, функция, которая конкатенирует две строки и проверяет результат:
package main
import (
"testing"
)
func Concat(a, b string) string {
return a + b
}
func FuzzConcat(f *testing.F) {
// Добавляем seed корпусы с несколькими параметрами
f.Add("hello", "world")
f.Add("foo", "bar")
f.Add("", "test")
f.Fuzz(func(t *testing.T, a, b string) {
result := Concat(a, b)
expected := a + b
if result != expected {
t.Errorf("Concat(%q, %q) = %q, expected %q", a, b, result, expected)
}
})
}
Результат выполнения фаззинг-теста:
"fuzz: elapsed: 0s, execs: 0 (0/sec), new interesting: 0"
"fuzz: elapsed: 1s, execs: 50 (50/sec), new interesting: 1"
"fuzz: elapsed: 2s, execs: 100 (50/sec), new interesting: 1"
"PASS"
Фаззинг-тест автоматически генерирует новые комбинации входных данных, чтобы найти крайние случаи, которые могут вызвать панику или неверное поведение.
Пример
Давайте рассмотрим, как фаззинг-тест может найти ошибку
в функции, которая делит два числа. Мы используем
тип данных int:
package main
import (
"testing"
)
func Divide(a, b int) int {
return a / b // Может вызвать панику при b == 0
}
func FuzzDivide(f *testing.F) {
// Добавляем seed корпусы
f.Add(10, 2)
f.Add(5, 1)
f.Add(0, 1)
f.Fuzz(func(t *testing.T, a, b int) {
if b == 0 {
t.Skip("Skipping division by zero")
}
result := Divide(a, b)
expected := a / b
if result != expected {
t.Errorf("Divide(%d, %d) = %d, expected %d", a, b, result, expected)
}
})
}
Результат выполнения фаззинг-теста:
"fuzz: elapsed: 0s, execs: 0 (0/sec), new interesting: 0"
"fuzz: elapsed: 1s, execs: 100 (100/sec), new interesting: 2"
"--- PASS: FuzzDivide (0.00s)"
"PASS"
Фаззинг-тест автоматически пропускает случаи с делением на ноль, но если бы мы не обрабатывали это, он бы нашел панику и сообщил о ней.
Смотрите также
-
метод
testing.F.Add,
который добавляет seed корпусы для фаззинг-теста -
метод
testing.F.Skip,
который пропускает текущий фаззинг-тест -
тип
testing.T,
который используется для обычных модульных тестов -
тип
testing.B,
который используется для бенчмарков