Ответ
В backend-разработке используется несколько ключевых видов тестирования, которые образуют так называемую "пирамиду тестирования":
-
Unit-тесты (Модульные тесты)
- Цель: Проверка наименьших изолированных частей кода (функций, методов) без внешних зависимостей, таких как базы данных или сетевые сервисы. Они быстрые и составляют основу пирамиды.
- Пример на Go:
// функция для теста func Add(a, b int) int { return a + b } // тест func TestAdd(t *testing.T) { result := Add(2, 3) if result != 5 { t.Errorf("Ожидали 5, получили %d", result) } } -
Интеграционные тесты
- Цель: Проверка взаимодействия нескольких компонентов системы между собой. Например, как ваш сервис работает с базой данных, кэшем или внешней очередью сообщений.
- Пример на Go (тест репозитория с БД):
func TestUserRepository_CreateUser(t *testing.T) { // Инициализация тестовой БД db := setupTestDB() defer db.Close() repo := NewUserRepository(db) user := &User{Name: "Test User"} err := repo.Create(user) if err != nil { t.Fatalf("Не удалось создать пользователя: %v", err) } // Здесь можно добавить проверку, что пользователь действительно появился в БД } -
End-to-End (E2E) тесты
- Цель: Проверка полного пользовательского сценария через внешние интерфейсы системы (обычно API). Эти тесты имитируют поведение реального пользователя.
- Пример на Go (тест API эндпоинта):
func TestAPI_CreateUser(t *testing.T) { router := setupRouter() // Настройка роутера // Создаем HTTP запрос body := strings.NewReader(`{"name":"Test"}`) req := httptest.NewRequest("POST", "/users", body) req.Header.Set("Content-Type", "application/json") // Записываем ответ w := httptest.NewRecorder() router.ServeHTTP(w, req) // Проверяем результат if w.Code != http.StatusCreated { t.Errorf("Ожидали статус 201, получили %d", w.Code) } }
Другие важные виды тестов:
- Нагрузочное тестирование: Проверка производительности и стабильности системы под высокой нагрузкой (инструменты:
k6,JMeter,vegeta). - Smoke-тесты (Дымовые тесты): Быстрая проверка работоспособности основных функций после развертывания.
- Регрессионное тестирование: Проверка того, что новые изменения не сломали существующий функционал.
- Тестирование безопасности: Поиск уязвимостей в приложении (например, с помощью
OWASP ZAP).
Ответ 18+ 🔞
Да ты посмотри, какая хуйня развелась — эта ваша «пирамида тестирования». Ну, типа, слоёный пирог из проверок, чтобы код не разъехался по швам, как старые штаны. Сейчас разжую, но без соплей, сразу по делу.
1. Юнит-тесты, они же модульные, блядь.
Суть проще пареной репы: берёшь одну мелкую функцию — одну! — и долбишь её со всех сторон, как будто она тебе должна деньги. Никаких баз данных, никаких внешних сервисов — чистая изоляция, сука. Быстро, дёшево, и если сломалось — сразу ясно, где искать.
Вот, смотри, как это выглядит на Go, если не усложнять:
// Функция, которая складывает два числа. Гениально, да?
func Add(a, b int) int {
return a + b
}
// А вот тест, который её проверяет. Если 2+3 вдруг станет 6 — мы обосрёмся.
func TestAdd(t *testing.T) {
result := Add(2, 3)
if result != 5 {
t.Errorf("Ожидали 5, получили %d", result)
}
}
Всё, ёпта. Никакой магии. Написал — запустил — либо зелёная галочка, либо в жопу.
2. Интеграционные тесты, ёперный театр.
Тут уже начинается веселье. Нужно проверить, как твои кусочки кода дружат друг с другом и с внешним миром. Например, твой сервис общается с базой данных. Поднимаешь тестовую БД (или мокаешь, но это уже высший пилотаж), настраиваешь соединение и смотришь — а не обосрётся ли всё, когда попытаешься что-то записать или прочитать.
Примерно так:
func TestUserRepository_CreateUser(t *testing.T) {
// Поднимаем тестовую базу — будь то in-memory SQLite или контейнер с Postgres
db := setupTestDB()
defer db.Close() // Не забудь закрыть, а то ресурсы кончатся, и будет пиздец.
repo := NewUserRepository(db)
user := &User{Name: "Test User"}
err := repo.Create(user)
if err != nil {
t.Fatalf("Не удалось создать пользователя: %v", err) // Вот тут уже серьёзно — если упало, дальше можно не проверять.
}
// Дальше можно, например, достать этого пользователя из БД и убедиться, что он там есть.
}
3. End-to-End (E2E) тесты, или «полный пиздец».
Это когда ты проверяешь весь сценарий от начала до конца, как будто реальный пользователь тыкает в интерфейс. Чаще всего это тесты API: отправил запрос — получил ответ — проверил, что всё ок. Медленные, ресурсоёмкие, но без них — никуда, потому что unit-тесты не поймают, если ты, например, забыл добавить эндпоинт в роутер.
Вот так это выглядит:
func TestAPI_CreateUser(t *testing.T) {
router := setupRouter() // Поднимаем всё приложение, как будто для прода.
// Лепим HTTP-запрос, как будто фронтенд пришёл
body := strings.NewReader(`{"name":"Test"}`)
req := httptest.NewRequest("POST", "/users", body)
req.Header.Set("Content-Type", "application/json")
// Ловим ответ
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// И вот тут магия: если статус не 201 (Created), значит, где-то косяк
if w.Code != http.StatusCreated {
t.Errorf("Ожидали статус 201, получили %d", w.Code)
}
}
А ещё есть куча других приколов, на которые тратят время:
- Нагрузочное тестирование — чтобы понять, выдержит ли твой сервис, когда на него набегут десять тысяч ботов или один очень злой проджект-менеджер. Инструменты:
k6,JMeter. Суть — нагружаешь до упора и смотришь, в какой момент всё падает. - Smoke-тесты (дымовые) — быстрая проверка после деплоя: «а живое ли оно вообще?». Запустил пару ключевых сценариев — если не дымится, можно идти пить кофе.
- Регрессионное тестирование — чтобы новая фича не разъебала старую. Типа «работало же вчера, блядь!».
- Тестирование безопасности — чтобы какие-нибудь умники не проникли в систему через дыру размером с амбарную дверь. Тут уже спец-инструменты вроде
OWASP ZAP.
Вот и вся пирамида, если без академического бреда. Главное — не перебарщивать с E2E, а то будешь ждать результатов тестов дольше, чем писать код. А это, простите, пиздец как неэффективно.