Какие существуют основные виды тестирования в Backend-разработке?

Ответ

В backend-разработке используется несколько ключевых видов тестирования, которые образуют так называемую "пирамиду тестирования":

  1. 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)
        }
    }
  2. Интеграционные тесты

    • Цель: Проверка взаимодействия нескольких компонентов системы между собой. Например, как ваш сервис работает с базой данных, кэшем или внешней очередью сообщений.
    • Пример на 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)
        }
        // Здесь можно добавить проверку, что пользователь действительно появился в БД
    }
  3. 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, а то будешь ждать результатов тестов дольше, чем писать код. А это, простите, пиздец как неэффективно.