Ответ
Модульное (unit) и интеграционное тестирование — это два разных уровня в пирамиде тестирования, каждый из которых преследует свои цели.
Модульные тесты (Unit Tests)
- Цель: Проверить корректность работы наименьшей изолированной части кода — "юнита" (обычно это одна функция или метод).
- Область: Очень маленькая и сфокусированная. Тестируется только логика внутри юнита.
- Изоляция: Все внешние зависимости (базы данных, файловая система, сетевые вызовы, другие сервисы) должны быть заменены моками (mocks) или стабами (stubs). Это гарантирует, что тест проверяет только тестируемый код, а не его зависимости.
- Скорость: Очень быстрые, так как не требуют запуска внешних систем. Их должно быть много.
Пример (проверка функции Add):
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)
}
}
Интеграционные тесты (Integration Tests)
- Цель: Проверить, что несколько компонентов системы корректно взаимодействуют друг с другом. Они проверяют "швы" и контракты между модулями.
- Область: Шире, чем у unit-тестов. Например, взаимодействие вашего сервиса с базой данных, с очередью сообщений или с другим API.
- Изоляция: Частичная. Внешние системы (БД, API) не мокаются, а используются их реальные или тестовые экземпляры (например, тестовая база данных, запущенная в Docker).
- Скорость: Значительно медленнее, чем unit-тесты, так как требуют настройки окружения и реальных I/O операций.
Пример (проверка создания пользователя в БД):
// db - это реальное подключение к тестовой базе данных
func TestUserCreation(t *testing.T) {
// 1. Настройка: подключение к тестовой БД и очистка таблиц
db, cleanup := setupTestDB(t)
defer cleanup() // 3. Очистка после теста
repo := NewUserRepository(db)
// 2. Действие: вызов метода, который работает с реальной БД
err := repo.CreateUser("test@example.com", "password123")
if err != nil {
t.Fatalf("Не удалось создать пользователя: %v", err)
}
// Можно добавить проверку, что пользователь действительно появился в БД
}
Сравнительная таблица
| Характеристика | Модульные тесты (Unit) | Интеграционные тесты (Integration) |
|---|---|---|
| Цель | Корректность отдельного модуля в изоляции | Корректность взаимодействия между модулями |
| Зависимости | Заменяются моками/стабами | Используются реальные (или тестовые экземпляры) |
| Скорость | Очень высокая | Средняя / Низкая |
| Количество | Много (основа пирамиды тестирования) | Меньше, чем модульных |
| Обнаруживаемые ошибки | Ошибки в алгоритмах и логике внутри функции | Ошибки в контрактах API, конфигурации, схеме БД, сетевом взаимодействии |
Заключение: Оба типа тестов критически важны. Unit-тесты обеспечивают надежность отдельных кирпичиков, а интеграционные тесты — что эти кирпичики правильно складываются в стену.
Ответ 18+ 🔞
А, слушай, вот эта вся тема с тестами — она же, блядь, как слоёный пирог, понимаешь? Один слой — это одно, другой — вообще другое. И если их перепутать, то потом сидишь и ебёшь мозг, почему всё падает, а ты нихуя не понимаешь.
Ну вот смотри, есть модульные тесты, их ещё юнитами зовут. Их задача — проверить одну мелкую, ёбушки-воробушки, функцию, чтобы она сама по себе работала как часы. Представь, у тебя есть функция, которая складывает два числа. И ты её тестируешь в полной изоляции, блядь, как будто она в вакууме. Все эти её дружки-приятели — базы данных, файлы, другие сервисы — их нахуй заменяешь заглушками, моками там всякими. Чтобы если тест сломался, ты точно знал — это косяк в этой конкретной функции, а не потому что база данных легла.
Вот, смотри, пример, простой как три копейки:
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)
}
}
Видишь? Никаких телодвижений. Запустил — получил ответ. Быстро, дёшево, сердито. Таких тестов должно быть, блядь, овердохуища, они фундамент.
А теперь интеграционные тесты — это уже следующий уровень, ёпта. Тут уже не вакуум, а почти реальная жизнь. Цель — проверить, как твои отдельные, протестированные по одиночке, модули начинают друг с другом общаться. Не с заглушками, а по-настоящему. Например, твой код пошёл что-то писать в базу данных. И ты проверяешь — а дошло ли? А правильно ли записалось? А не послал ли он туда какую-нибудь дичь?
Тут уже всё серьёзнее, медленнее и геморройнее. Надо поднимать тестовую базу, чистить её перед тестом, потом после теста убирать за собой. Вот, типа так:
func TestUserCreation(t *testing.T) {
// 1. Настройка: подключение к тестовой БД и очистка таблиц
db, cleanup := setupTestDB(t)
defer cleanup() // 3. Очистка после теста
repo := NewUserRepository(db)
// 2. Действие: вызов метода, который работает с реальной БД
err := repo.CreateUser("test@example.com", "password123")
if err != nil {
t.Fatalf("Не удалось создать пользователя: %v", err)
}
// Можно добавить проверку, что пользователь действительно появился в БД
}
Видишь разницу? Тут уже не просто логика, тут уже интеграция, блядь. Скорость, конечно, уже не та. Если модульные тесты — это спринт, то интеграционные — это уже кросс с препятствиями.
Короче, чтобы было совсем понятно, вот тебе табличка, в рот меня чих-пых:
| Признак | Модульные тесты (Юниты) | Интеграционные тесты |
|---|---|---|
| Зачем? | Проверить один кирпичик в изоляции | Проверить, как кирпичики склеиваются в стену |
| С кем общается? | Ни с кем, все зависимости — муляжи | С реальными или тестовыми базами, API и прочей хуйней |
| Скорость | Быстрее света, их можно тыкать постоянно | Медленные, как черепаха в сиропе |
| Сколько нужно? | Много, это основа всего | Меньше, но они тоже очень важны |
| Что ловят? | Косяки в твоей собственной логике | Косяки в том, как всё связано: схемы БД, контракты, сеть |
Вывод, блядь, простой: без первых — ты нихуя не уверен в своих кирпичах. Без вторых — ты нихуя не уверен, что из этих кирпичей получится дом, а не куча говна. Нужны и те, и другие, иначе — пиши пропало.