Ответ
Юнит-тесты (модульные тесты) — это тип автоматизированных тестов, которые проверяют корректность работы наименьших изолированных частей программы, таких как функции или методы.
Основные цели юнит-тестов:
- Обнаружение ошибок на ранней стадии: Позволяют быстро найти и исправить баги в новой или измененной логике.
- Безопасный рефакторинг: Если после изменения кода тесты по-прежнему проходят, вы можете быть уверены, что не сломали существующую функциональность.
- Живая документация: Тесты наглядно демонстрируют, как должен работать код и какие крайние случаи (edge cases) он обрабатывает.
- Улучшение дизайна кода: Необходимость писать тестируемый код подталкивает к созданию более слабосвязанных и модульных компонентов.
Пример в Go:
Для написания тестов в Go используется встроенный пакет testing. Тестовые функции должны начинаться с Test, находиться в файлах _test.go и принимать *testing.T в качестве аргумента.
Код для тестирования:
// file: sum.go
func Sum(a, b int) int {
return a + b
}
Простой юнит-тест:
// file: sum_test.go
package main
import "testing"
func TestSum(t *testing.T) {
result := Sum(2, 3)
expected := 5
if result != expected {
t.Errorf("Sum(2, 3) = %d; ожидалось %d", result, expected)
}
}
Лучшая практика в Go: Table-Driven Tests
Для проверки нескольких сценариев принято использовать табличные тесты. Это делает тест более читаемым и легко расширяемым.
// file: sum_test.go
func TestSumTable(t *testing.T) {
cases := []struct {
name string
a, b int
expected int
}{
{"Положительные числа", 2, 3, 5},
{"Отрицательные числа", -2, -3, -5},
{"Смешанные числа", 2, -3, -1},
{"С нулем", 5, 0, 5},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
result := Sum(tc.a, tc.b)
if result != tc.expected {
t.Errorf("Sum(%d, %d) = %d; ожидалось %d", tc.a, tc.b, result, tc.expected)
}
})
}
}
Запуск тестов выполняется командой: go test.
Ответ 18+ 🔞
А, юнит-тесты! Ну это ж, блядь, как та самая Муму, только наоборот — не топить, а спасать от утопления в говнокоде, понимаешь?
Смотри, вот представь: пишешь ты функцию, вроде всё работает, а потом через месяц приходит какой-нибудь полупидор, типа меня, и начинает её менять. И тут, сука, начинается — он что-то там подкрутил, и всё, пиздец, вся логика рассыпалась как карточный домик от чиха. А юнит-тесты — это как раз те самые охранники, которые кричат: «Э, бошка думай! Ты тут нахуярил, а тут сломалось!».
Зачем они вообще нужны, эти тесты, ёпта?
- Ловить косяки сразу. Написал функцию — тут же её и протестировал. Не жди, пока пользователь начнёт материться, что у него всё ебнулось.
- Рефакторить без страха. Хочешь переписать код красиво? Впердоливай! Если тесты зелёные — значит, не сломал старую логику. Если красные — ну, сам понимаешь, иди чини.
- Они как документация, только живая. Открыл тесты и сразу видишь, как эту функцию кормить и что от неё ждать. Лучше любой писанины в комментариях.
- Заставляют писать нормальный код. Потому что протестировать спагетти, где всё намешано в одну кучу — это пиздец, волнение ебать. Приходится делать модули изолированными.
Ну и пример на Go, чтоб не быть пиздаболом.
Вот у нас есть функция, проще некуда. Складывает два числа, в рот меня чих-пых.
Сам код, святое:
// sum.go
func Sum(a, b int) int {
return a + b
}
А вот её тест. В Go для этого есть свой встроенный инструмент, не надо никаких овердохуищных фреймворков.
Тестик, мать его:
// sum_test.go
package main
import "testing"
func TestSum(t *testing.T) {
result := Sum(2, 3)
expected := 5
if result != expected {
t.Errorf("Sum(2, 3) = %d; ожидалось %d", result, expected)
}
}
Запускаешь go test, и если всё ок — тишина. А если накосячил — он тебе такую оплеуху в виде ошибки выдаст, мало не покажется.
Но настоящие мужики в Go пишут табличные тесты!
Это когда ты не один случай проверяешь, а сразу кучу, как в таблице. Красиво, удобно, расширяемо. Смотри, как это выглядит:
// sum_test.go
func TestSumTable(t *testing.T) {
// Вот наша таблица с кейсами. Каждый — отдельная история.
cases := []struct {
name string // Какой случай проверяем
a, b int // Что даём на вход
expected int // Что ждём на выходе
}{
{"Обычные плюсовые", 2, 3, 5},
{"Оба минусовые", -2, -3, -5},
{"Плюс и минус", 2, -3, -1},
{"С нулём", 5, 0, 5},
// Хочешь добавить ещё случай? Просто пиши сюда новую строчку!
}
// И поехали проверять каждый кейс по очереди
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) { // t.Run запускает подтест с именем
result := Sum(tc.a, tc.b)
if result != tc.expected {
t.Errorf("Sum(%d, %d) = %d; ожидалось %d", tc.a, tc.b, result, tc.expected)
}
})
}
}
Вот так вот. Кажется, ерунда, но когда у тебя таких функций овердохуища, а не одна Sum, эти тесты спасают твою жопу от профессионального выгорания и криков коллег. Запускаешь go test ./... и спишь спокойно. Ну, почти.