
Привет, Хабр!
Golang как ЯП очень хорош для разработки высокопроизводительных приложений. В любом проекте наступает момент, когда нужно проверить, насколько хорошо все работает на самом деле, это можно сделать с помощью тестирования.
Тестирование в Go можно выполнять с помощью mock-объектов, fuzzing и property-based testing. В этой статье мы рассмотрим эти механизмы.
Mock-объекты
Mock-объекты — это такие подставные объекты, используемые в тестировании для имитации поведения реальных компонентов системы. Они дают возможность проверить, как тестируемый компонент взаимодействует с внешними зависимостями, не ввязываясь в сложные отношения с реальным миром. Проще говоря, это своего рода type Doer interface { DoSomething(int) string }
И мы хотим мокировать этот интерфейс для тестирования. Выглядеть это будет простым образом: Или вместо того, чтобы отправлять реальные email при тестировании, можно юзать mock-объект: Предположим, есть внешний сервис для погоды Мокирование этого интерфейса для тестов: Использование Если есть HTTP-контроллер, который зависит от Мокирование ситуаций, когда внешняя система возвращает ошибку: Fuzzing, или фаззинг, – это процесс автоматического тестирования путем подачи непредсказуемых или случайных данных на вход программы. Если сказать еще проще, то это словно бросать всё подряд, надеясь вызвать сбой. Fuzzing помогает выявить уязвимости, на которые стандартные тесты могут не указывать, включая переполнения буфера, утечки памяти, обработку исключений и многое другое. Во многих отраслях fuzzing является частью требований к ПО. С версии Go 1.18 можно просто написать fuzz-тест, указав функцию, которую нужно протестировать, и Go сделает остальное, генерируя случайные данные для поиска потенциальных ошибок. Допустим, есть функция, которая парсит URL: Fuzzing функция будет выглядеть таким образом: После того как fuzzing функция написана, нужно собрать fuzzing корпус и запустить fuzzing: Эти команды запустят процесс fuzzing. Создадим более конкретный сценарий использования fuzzing в Go, чтобы лучше понять, как можно вывести информацию об ошибках или уязвимостях из fuzz-теста. Предположим, что мы тестируем функцию разбора URL, которая может быть уязвима к некоторым специфическим входным данным. Представим, чтоесть следующая функция Напишем fuzz-тест для этой функции: После запуска fuzz-теста go-fuzz может обнаружить входные данные, которые вызывают неожиданное поведение или сбои. Предположим, fuzz-тест нашел входные данные, вызывающие ошибку, которая не была должным образом обработана в нашем код: Такой вывод указывает на то, что функция С property-based testing можно, проверять удовлетворяет ли функция определенным свойствам для широкого диапазона входных данных. Property-based testing генерирует входные данные автоматически, чтобы проверить общие свойства функции, такие как идемпотентность, коммутативность или инвариантность, среди множества других возможных входных данных. Предположим, есть функция сложения Автоматически генерим случайные значения для Идемпотентность – это свойство объекта или операции, которое гарантирует, что повторное применение не изменит результат после первого применения. Например, функция, которая удаляет все вхождения заданного элемента из списка, должна быть идемпотентной. Обратимость – это свойство, при котором для каждой операции существует обратная операция, возвращающая систему в исходное состояние. Допустим, есть функция шифрования и соответствующая функция дешифрования: Предположим, что есть функция, которая фильтрует список чисел, удаляя все, что меньше заданного порога и нам нужно убедиться в том, что длина результата не превышает длину исходного списка: Напоследок приглашаю присоединиться к открытому уроку и понаблюдать за процессом собеседования на позицию Golang Developer Middle. Интервьюером выступит руководитель курса Golang Developer. Professional Олег Венгер, tech-lead в Авито. После вебинара вам будет намного проще подготовиться к реальному собеседованию на аналогичные позиции. type MockDoer struct { mock.Mock } func (m *MockDoer) DoSomething(number int) string { args := m.Called(number) return args.String(0) }import ( "testing" "github.com/stretchr/testify/mock" ) type MockMailer struct { mock.Mock } func (m *MockMailer) Send(to, subject, body string) error { args := m.Called(to, subject, body) return args.Error(0) } func TestSendEmail(t *testing.T) { mockMailer := new(MockMailer) mockMailer.On("Send", "example@example.com", "Subject", "Body").Return(nil) mockMailer.AssertExpectations(t) } WeatherService:type WeatherService interface { GetWeather(city string) (float64, error) }type MockWeatherService struct { mock.Mock } func (m *MockWeatherService) GetWeather(city string) (float64, error) { args := m.Called(city) return args.Get(0).(float64), args.Error(1) }MockWeatherService в тестах:mockService := new(MockWeatherService) mockService.On("GetWeather", "Moscow").Return(20.0, nil) // mockServiceMailer, можно мокировать эту зависимость в тестах:type Controller struct { Mailer Mailer } func TestController_SendEmail(t *testing.T) { mockMailer := new(MockMailer) mockMailer.On("Send", mock.Anything, mock.Anything, mock.Anything).Return(nil) controller := Controller{Mailer: mockMailer} // тест методов контроллера }mockMailer := new(MockMailer) mockMailer.On("Send", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("failed to send")) // обработка ошибокFuzzing
func ParseURL(url string) (*URL, error) { // логика }//+build gofuzz package mypackage import "testing" func FuzzParseURL(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { _ = ParseURL(string(data)) }) }go get -u github.com/dvyukov/go-fuzz/go-fuzz go get -u github.com/dvyukov/go-fuzz/go-fuzz-buildParseURL, которая разбирает строку URL и возвращает структуру URL или ошибку, если URL не может быть разобран: package urlparser import ( "net/url" "errors" ) func ParseURL(input string) (*url.URL, error) { parsedURL, err := url.Parse(input) if err != nil { return nil, err } if parsedURL.Scheme == "" || parsedURL.Host == "" { return nil, errors.New("url lacks scheme or host") } return parsedURL, nil }//+build gofuzz package urlparser import "testing" func FuzzParseURL(f *testing.F) { testcases := []string{"http://example.com", "https://example.com", "ftp://example.com"} for _, tc := range testcases { f.Add(tc) // добавляем начальные тестовые случаи } f.Fuzz(func(t *testing.T, url string) { _, err := ParseURL(url) if err != nil { t.Fatalf("ParseURL failed for %s: %v", url, err) } }) }fuzz: elapsed: 15s, execs: 1153423 (76894/sec), crashes: 1, restarts: 1/10000, coverage: 1023/2000 edges fuzz: minimizing crash input... fuzz: crash: ParseURL("http://%00/") fuzz: minimizing crash input... fuzz: crash reproduced; minimizing... fuzz: minimized input to 10 bytes (from 28) fuzz: minimizing duration... fuzz: duration minimized, 0.1s (from 0.3s)ParseURL не справилась с обработкой входных данных "http://%00/", что привело к сбою. Property-based testing
add(a, b). Одно из свойств, которое мы хотим проверить, – это package mypackage import ( "testing" "github.com/leanovate/gopter" "github.com/leanovate/gopter/prop" "github.com/leanovate/gopter/gen" ) func TestAddCommutativeProperty(t *testing.T) { parameters := gopter.DefaultTestParameters() properties := gopter.NewProperties(parameters) properties.Property("add is commutative", prop.ForAll( func(a int, b int) bool { return add(a, b) == add(b, a) }, gen.Int(), gen.Int(), )) properties.TestingRun(t) }
a и b и проверяем, удовлетворяет ли функция add свойству коммутативности. func removeElement(slice []int, element int) []int { var result []int for _, v := range slice { if v != element { result = append(result, v) } } return result } func TestRemoveElementIdempotentProperty(t *testing.T) { parameters := gopter.DefaultTestParameters() properties := gopter.NewProperties(parameters) properties.Property("removeElement is idempotent", prop.ForAll( func(slice []int, element int) bool { firstApplication := removeElement(slice, element) secondApplication := removeElement(firstApplication, element) return reflect.DeepEqual(firstApplication, secondApplication) }, gen.SliceOf(gen.Int()), gen.Int(), )) properties.TestingRun(t) }func encrypt(plaintext string, key int) string { // простое шифрование путем сдвига каждого символа на key позиций result := "" for _, char := range plaintext { shiftedChar := rune(char + key) result += string(shiftedChar) } return result } func decrypt(ciphertext string, key int) string { // обратное шифрование return encrypt(ciphertext, -key) } func TestEncryptionReversibilityProperty(t *testing.T) { parameters := gopter.DefaultTestParameters() properties := gopter.NewProperties(parameters) properties.Property("encrypt and decrypt are reversible", prop.ForAll( func(plaintext string, key int) bool { ciphertext := encrypt(plaintext, key) decryptedText := decrypt(ciphertext, key) return plaintext == decryptedText }, gen.AlphaString(), // генерируем строку из алфавитных символов gen.IntRange(1, 26), // генерируем ключ шифрования как целое число от 1 до 26 )) properties.TestingRun(t) }func filterSlice(slice []int, threshold int) []int { var result []int for _, v := range slice { if v >= threshold { result = append(result, v) } } return result } func TestFilterSliceLengthProperty(t *testing.T) { parameters := gopter.DefaultTestParameters() properties := gopter.NewProperties(parameters) properties.Property("filterSlice does not increase slice length", prop.ForAll( func(slice []int, threshold int) bool { result := filterSlice(slice, threshold) return len(result) <= len(slice) }, gen.SliceOf(gen.Int()), gen.Int(), )) properties.TestingRun(t) }
ссылка на оригинал статьи https://habr.com/ru/articles/800717/
Добавить комментарий