Хочешь уверенно проходить IT-интервью?

Мы понимаем, как сложно подготовиться: стресс, алгоритмы, вопросы, от которых голова идёт кругом. Но с AI тренажёром всё гораздо проще.
💡 Почему Т1 тренажёр — это мастхэв?
- Получишь настоящую обратную связь: где затык, что подтянуть и как стать лучше
- Научишься не только решать задачи, но и объяснять своё решение так, чтобы интервьюер сказал: "Вау!".
- Освоишь все этапы собеседования, от вопросов по алгоритмам до диалога о твоих целях.
Зачем листать миллион туториалов? Просто зайди в Т1 тренажёр, потренируйся и уверенно удиви интервьюеров. Мы не обещаем лёгкой прогулки, но обещаем, что будешь готов!
Реклама. ООО «Смарт Гико», ИНН 7743264341. Erid 2VtzqwP8vqy
Модульное тестирование
Модульное тестирование, также юнит-тестирование – это проверка корректности отдельных модулей (юнитов) программы. Модулями принято считать блоки кода, содержащие определенный функционал. Чаще всего ими выступают функции и методы.
Модульное тестирование проводится сразу после написания юнита, чтобы проверить, не привело ли изменение кода к появлению ошибок в уже протестированных частях программы. Такой подход позволяет своевременно отлавливать и устранять возможные ошибки.
Познакомимся с юнит-тестами на практическом примере. Создадим примитивную функцию IsEven
, которая принимает на вход число типа int
и возвращает строку “Even”, если оно четное, иначе – строку “Odd”.
package main
func IsEven(input int) string {
if input%2 == 0 {
return "Even"
}
return "Odd"
}
Теперь напишем тест для функции IsEven. В Go приняты определенные правила по расположению и наименованию тестирующих файлов и функций:
- Файлы с тестами должны находиться в одном пакете с тестируемыми функциями или в соответствующем пакете с суффиксом
_test
- Название файла с тестами должно оканчиваться на
_test.go
- Название тестирующей функции должно начинаться префиксом Test и далее содержать название тестируемой функции.
Например, тест для нашей функции IsEven
, которая располагается в папке folder
и файле iseven.go
, следует расположить в той же папке folder
, файле iseven_test.go
и назвать TestIsEven
:
package main
import "testing"
func TestIsEven(t *testing.T) {
result1 := IsEven(-1)
if result1 != "Odd" {
t.Errorf("Result was incorrect, got: %s, want: %s.", result1, "Odd")
}
t.Log("Tested result1")
result2 := IsEven(0)
if result2 != "Even" {
t.Errorf("Result was incorrect, got: %s, want: %s.", result2, "Even")
}
t.Log("Tested result2")
}
Тестирующая функция всегда принимает единственный параметр – *testing.T
, где T
– это тип для управления состоянием теста и поддержки тестовых логов.
Тест заканчивается в тот момент, когда тестирующая функция возвращает или вызывает один из методов: FailNow, Fatal, Fatalf, SkipNow, Skip, Skipf. Стоит учитывать, что они могут вызываться только из горутины, выполняющей тестирующую функцию.
В тестах часто используются методы Log(f)
и Error(f)
. Первый из них форматирует переданный текст и записывает его в специальный лог ошибок. Для обычных тестов этот текст будет выведен при неудачном завершении теста или при указании флага -v
, в то время как для бенчмарков (тестов производительности) он выводится всегда.
Метод Error
под капотом вызывает методы Log
и Fail
, последний из которых при вызове отмечает, что в тестируемой функции есть ошибка, и продолжает выполнение теста.
Запуск тестов
Для запуска тестов достаточно написать в терминале команду go test
. В зависимости от результата тестирования в консоли будет надпись PASS (все тесты пройдены) или FAIL (определенные тесты не пройдены), а также дополнительная информация: путь до go-модуля и время выполнения тестов:
$ go test
PASS
ok github.com/username/gomodule 0.002
Теперь поменяем местами строки “Even” и “Odd” в функции IsEven и после очередного тестирования получим сообщение об ошибке:
$ go test
--- FAIL: TestIsEven (0.00s)
iseven_test.go:8: Result was incorrect, got: Even, want: Odd.
iseven_test.go:13: Result was incorrect, got: Odd, want: Even.
FAIL
exit status 1
FAIL github.com/username/gomodule 0.002s
Иногда требуется запустить тесты в определенном пакете. Сделать это можно следующей командой: go test ./<название_пакета>
. Для тестирования всех пакетов используется команда go test ./...
Стоит отметить, что утилита go test
имеет множество флагов, позволяющих детально настроить вывод результатов тестирования.
Например, ранее упомянутый флаг -v
выведет названия всех выполненных тестовых функций, время их выполнения, а также содержимое лога ошибок:
$ go test -v
=== RUN TestIsEven
iseven_test.go:11: Tested result1
iseven_test.go:17: Tested result2
--- PASS: TestIsEven (0.00s)
PASS
ok github.com/username/gomodule 0.003s
В рамках одной статьи не представляется возможным рассмотреть все флаги, поэтому для детального ознакомления рекомендуем обратиться к соответствующему разделу из официальной документации.
Визуализация покрытия кода тестами
Часто возникает необходимость проверить покрытие кода тестами. В этом поможет команда go test
с флагом -cover
, который выведет процент протестированного кода:
$ go test -cover
PASS
coverage: 100.0% of statements
ok github.com/username/gomodule 0.003s
По этой информации трудно понять, какие именно участки кода покрыты тестами, а какие – нет. Более наглядное представление этой информации можно получить с помощью двух последовательных команд:
go test -coverprofile=coverage.out
****- генерация файла с информацией о покрытииgo tool cover -html=coverage.out
– генерация HTML-страницы, наглядно иллюстрирующей покрытие кода тестами

Table-driven тесты
Представим, что необходимо протестировать работу функции IsEven на 5 разных числах. При модульном тестировании придется прописывать много повторяющегося кода с проверкой каждого случая. Такой подход нарушает принцип разработки DRY (Don't repeat yourself), отнимает много времени и подвержен ошибкам.
В подобных случаях рациональнее применить table-driven подход, при котором тест-кейсы организуются в виде таблицы, содержащей заданные входные и ожидаемые выходные значения.
Для создания таблицы используется слайс структур ([]struct
) с несколькими полями, содержащими входные и выходные данные и дополнительную информацию, например, текст.
Далее с помощью цикла for производится итерация по значениям слайса структур с запуском метода t.Run
, который в качестве второго параметра принимает функцию, проверяющую соответствие входных и выходных данных.
Применим разобранную схему table-driven подхода на тесте для функции IsEven:
func TestIsEvenTableDriven(t *testing.T) {
// задание столбцов таблицы
var testcases = []struct {
text string
input int
want string
}{
// строки таблицы
{"-1 - нечетное число", -1, "Odd"},
{"0 - четное число", 0, "Even"},
{"1 - нечетное число", 1, "Odd"},
{"2 - четное число", 2, "Even"},
}
// итерация по слайсу структур
for _, tt := range testcases {
t.Run(tt.text, func(t *testing.T) {
result := IsEven(tt.input)
// проверка на соответствие входных и выходных данных
if result != tt.want {
t.Errorf("got %s, want %s", result, tt.want)
}
})
}
}
Запустите приведенный выше тест с флагом -v
и посмотрите на получившийся вывод.
Как вы можете заметить, table-driven тесты упрощают проверку и добавление тест-кейсов и довольно просты в реализации, поэтому являются оптимальным и часто используемым способом тестирования программ.
Параллельный запуск тестов
По умолчанию тесты в Go исполняются последовательно. Чтобы запустить конкретный тест параллельно с другими, следует добавить в него метод t.Parallel()
.
Утилита go test
приостанавливает тесты, содержащие метод t.Parallel()
, и возобновляет их после завершения непараллельных тестов. Стоит отметить, что количество тестов, способных исполнятся параллельно в единицу времени, определяет рассмотренный нами в предыдущей статье параметр GOMAXPROCS
.
Тест можно сделать параллельным, добавив всего три строки, две из которых – это вызов t.Parallel()
в начале теста и внутри метода t.Run()
, а третья – инициализация в цикле for дополнительной переменной для избежания распространенной ошибки использования горутин с итераторами и замыканиями:
func TestIsEvenParallel(t *testing.T) {
t.Parallel() // помечает тест как параллельный
var testcases = []struct {
text string
input int
want string
}{
{"-1 - нечетное число", -1, "Odd"},
{"0 - четное число", 0, "Even"},
{"1 - нечетное число", 1, "Odd"},
{"2 - четное число", 2, "Even"},
}
for _, tc := range testcases {
tc := tc // для избежания ошибки замыкания внутри горутины
t.Run(tc.text, func(t *testing.T) {
t.Parallel() // помечает тест-кейс как параллельный
result := IsEven(tc.input)
if result != tc.want {
t.Errorf("got %s, want %s", result, tc.want)
}
})
}
}
Стоит отметить, что упомянутая ошибка была решена после изменения поведения цикла for в версии Go 1.22, поэтому строка tc := tc
имеет смысл только для версий Go < 1.22.
Для простых функций разница в скорости выполнения параллельных и обычных тестов несущественная. Она становится заметна при увеличении количества и сложности тест-кейсов.
Тесты производительности
Тесты производительности (бенчмарк-тесты, бенчмарки) позволяют оценить эффективность кода. Путем многократного запуска одной и той же функции с разными параметрами бенчмарки рассчитывают среднее время её выполнения и объем задействованной памяти.
Стоит учитывать, что результаты бенчмарков напрямую зависят от характеристик и физического состояния компьютера, на котором они выполняются. Архитектура и кэш процессора, скорость RAM, система охлаждения, конфигурация ОС – эти и другие параметры имеют прямое влияние на качество и результативность тестов производительности.
В Go правила для бенчмарк-тестов такие же, как и для обычных, но с некоторыми отличиями: бенчмарки должны начинаться с префикса Benchmark, принимать параметр *testing.B
и содержать тестируемую функцию в цикле for c верхней границей b.N
. Таким образом, тестируемый код будет запущен N раз, причем N автоматически корректируется до тех пор, пока время выполнения каждой итерации не станет статистически стабильным.
Чтобы применить бенчмарки на практике, для начала напишем функцию нахождения простых чисел от 1 до заданного целого числа:
func FindPrimes(limit int) []int {
var primeNums []int
for i := 2; i < limit; i++ {
isPrime := true
for j := 2; j <= int(math.Sqrt(float64(i))); j++ {
if i%j == 0 {
isPrime = false
break
}
}
if isPrime {
primeNums = append(primeNums, i)
}
}
return primeNums
}
Простейший бенчмарк-тест для функции FindPrimes выглядит следующим образом:
func BenchmarkFindPrimes(b *testing.B) {
for i := 0; i < b.N; i++ {
FindPrimes(100)
}
}
Такой тест дает мало информации об эффективности функции, так как оперирует только одним числом. Более объективным и показательным будет table-driven бенчмарк-тест с несколькими значениями разного порядка, что позволит сделать выводы о работе функции при возрастающих параметрах:
func BenchmarkFindPrimes(b *testing.B) {
var testcases = []struct {
input int
}{
{input: 50},
{input: 100},
{input: 500},
{input: 1000},
}
for _, tc := range testcases {
b.Run(fmt.Sprintf("input=%d", tc.input), func(b *testing.B) {
for i := 0; i < b.N; i++ {
FindPrimes(tc.input)
}
})
}
}
Запуск бенчмарков производится при помощи команды go test
с флагом -bench
и регулярным выражением. Например, для запуска всех бенчмарков используется команда go test -bench=.
, что эквивалентно go test -bench .
$ go test -bench=.
goos: linux
goarch: amd64
pkg: github.com/username/gomodule
cpu: AMD Ryzen 3 5300U with Radeon Graphics
BenchmarkFindPrimes/input=50-8 2237236 541.4 ns/op
BenchmarkFindPrimes/input=100-8 874279 1208 ns/op
BenchmarkFindPrimes/input=500-8 122217 9658 ns/op
BenchmarkFindPrimes/input=1000-8 48544 24804 ns/op
PASS
ok github.com/username/gomodule 5.569s
Первые 4 параметра (goos, goarch, pkg и cpu) описывают соответственно ОС, архитектуру процессора, go-модуль и маркировку процессора. Далее написаны названия бенчмарк-тестов с указанием входных данных и количества ядер (параметр GOMAXPROCS
), задействованных при бенчмарке, а правее – итерации цикла и среднее время выполнения каждой из них.
Стоит учитывать, что при наличии обычных тестов они также выполняются во время бенчмарков. Для избежания этого поведения следует запустить бенчмарк с флагом -run
и регулярным выражением, не подходящим под названия обычных тестов. Например, -run=^#
или -run=ZZZ
Fuzzing-тестирование
При классическом юнит-тестировании зачастую сложно учесть все возможные варианты вводимых данных. Для разнообразия юнит-тестов случайными, граничными и заведомо неправильными значениями применяют технику Fuzzing-тестирования. Она позволяет покрыть специфические варианты ввода и выявить часто встречающиеся ошибки: выход за пределы массива, деление на 0, утечка памяти, состояние гонки и множество других.
Разработчики языка Go добавили поддержку fuzz-тестов в версии 1.18 и в официальном руководстве сформулировали основные правила их написания. Они схожи с правилами для бенчмарков и юнит-тестов, но содержат дополнительную информацию о допустимых типах и наименовании компонентов.

Задача
В качестве практического задания предлагаем оптимизировать функцию поиска простых чисел с помощью алгоритма “Решето Эратосфена” и написать для нее модульные и бенчмарк-тесты. Некоторые подзадачи потребуют обращения к дополнительным источникам информации, что поспособствует развитию навыка самостоятельного обучения и углублению знаний. Ответы на большинство вопросов содержатся в официальной документации языка Go, поэтому не стоит ею пренебрегать.
Решето Эратосфена
Напишите функцию sieveOfEratosthenes для поиска простых чисел с помощью алгоритма «Решето Эратосфена».
Юнит-тестирование
Напишите юнит-тест по table-driven подходу для функции sieveOfEratosthenes с проверкой пяти и более значений.
Запустите написанный тест одной командой с выполнением следующих условий:
- Выводится процент покрытия кода тестами
- Включено перемешивание порядка выполнения тестов
- Количество запусков теста равно трем
- Количество используемых процессоров равно четырем
Бенчмарки
Напишите бенчмарк-тест по table-driven подходу для функции sieveOfEratosthenes
с входными числами 50, 100, 500, 1000.
Запустите написанный бенчмарк одной командой с выполнением следующих условий:
- Продолжительность каждого теста 3 секунды
- Выводится статистика распределения памяти
В качестве полезного упражнения предлагаем провести бенчмарки с одинаковыми параметрами для функций FindPrimes
и sieveOfEratosthenes
и на основании полученных результатов определить наиболее эффективный алгоритм.
Заключение
В 16 части самоучителя мы затронули важные аспекты тестирования кода, подробно изучили основные виды тестов и на практических примерах рассмотрели их реализацию в языке Go.
В следующей статье изучим основы сетевого программирования и клиент-серверного взаимодействия, рассмотрим инструменты пакета net и напишем первый HTTP-запрос.
Содержание самоучителя
- Особенности и сфера применения Go, установка, настройка
- Ресурсы для изучения Go с нуля
- Организация кода. Пакеты, импорты, модули. Ввод-вывод текста.
- Переменные. Типы данных и их преобразования. Основные операторы
- Условные конструкции if-else и switch-case. Цикл for. Вложенные и бесконечные циклы
- Функции и аргументы. Области видимости. Рекурсия. Defer
- Массивы и слайсы. Append и сopy. Пакет slices
- Строки, руны, байты. Пакет strings. Хеш-таблица (map)
- Структуры и методы. Интерфейсы. Указатели. Основы ООП
- Наследование, абстракция, полиморфизм, инкапсуляция
- Обработка ошибок. Паника. Восстановление. Логирование
- Обобщенное программирование. Дженерики
- Работа с датой и временем. Пакет time
- Интерфейсы ввода-вывода. Буферизация. Работа с файлами. Пакеты io, bufio, os
- Конкурентность. Горутины. Каналы
- Тестирование кода и его виды. Table-driven подход. Параллельные тесты
- Основы сетевого программирования. Стек TCP/IP. Сокеты. Пакет net
- Протокол HTTP. Создание HTTP-сервера и клиента. Пакет net/http
Комментарии