В чем разница между unit-тестами и интеграционными тестами?

Ответ

Unit-тесты проверяют изолированно одну единицу кода (класс, метод). Интеграционные тесты проверяют взаимодействие нескольких модулей или с внешними системами.

Сравнительная таблица:

Характеристика Unit-тест Интеграционный тест
Объект тестирования Один класс/метод (единица). Группа классов, модуль, взаимодействие с БД, API.
Зависимости Изолируются с помощью моков (Mockito). Используются реальные или приближённые к реальным зависимости (testcontainers, in-memory БД).
Скорость Очень быстрые (миллисекунды). Медленные (секунды, минуты).
Цель Проверить корректность логики единицы кода. Проверить корректность взаимодействия и интеграции компонентов.
Частота запуска Запускаются постоянно, при каждом изменении кода. Запускаются реже (ночью, перед релизом).

Пример Unit-теста (JUnit 5 + Mockito):

@Test
void shouldReturnUserWhenFound() {
    // 1. ARRANGE: Создаём мок зависимости и тестируемый сервис
    UserRepository mockRepo = mock(UserRepository.class);
    UserService service = new UserService(mockRepo);
    User expectedUser = new User(1L, "Alice");
    when(mockRepo.findById(1L)).thenReturn(Optional.of(expectedUser));

    // 2. ACT: Вызываем тестируемый метод
    User actualUser = service.getUserById(1L);

    // 3. ASSERT: Проверяем результат
    assertEquals("Alice", actualUser.getName());
    verify(mockRepo).findById(1L); // Проверяем взаимодействие с моком
}

Пример Интеграционного теста (Spring Boot Test):

@SpringBootTest
@AutoConfigureTestDatabase // Заменяет БД на embedded (H2)
class UserServiceIntegrationTest {
    @Autowired
    private UserRepository repository; // Реальный репозиторий
    @Autowired
    private UserService service; // Реальный сервис

    @Test
    void shouldSaveAndRetrieveUser() {
        // Действия с реальной БД
        User savedUser = repository.save(new User(null, "Bob"));
        User foundUser = service.getUserById(savedUser.getId());

        assertThat(foundUser.getName()).isEqualTo("Bob");
    }
}

Пирамида тестирования: Unit-тесты составляют её основу (их больше всего), а интеграционные находятся уровнем выше (их меньше). Оба типа необходимы для качественного проекта.

Ответ 18+ 🔞

Давай я тебе на пальцах объясню, в чём разница между этими двумя типами тестов, а то я вижу, у тебя в глазах уже туман от этих умных словечек.

Представь себе, блядь, двигатель от машины. Unit-тест — это когда ты снял свечу зажигания, положил её на стол и проверяешь, даёт ли она искру. Одна деталь, в полной изоляции, нихуя вокруг. Интеграционный тест — это когда ты эту свечу вкрутил обратно в движок, завёл его и слушаешь, не троит ли он, блядь. Проверяешь, как эта сволочь работает уже в компании с другими железяками.

Короче, смотри таблицу, чтобы не ебать мозг:

Что проверяем Unit-тест Интеграционный тест
Объект Один класс или метод. Одиноко, как хуй в проруби. Целая банда классов, работа с БД, API — полный ансамбль.
Зависимости Всех заместо них подставляем муляжи (моки). Как будто подсадили актёров-дублёров. Используем настоящих уродов: реальную БД (или почти реальную).
Скорость Молниеносно. Тыкнул — получил результат. Тяжело и долго. Как ждать, пока тёща в туалете соберётся.
Цель Убедиться, что вот эта маленькая функция не ебёт мозг в логике. Убедиться, что все эти функции, собравшись вместе, не устроят пиздец.
Когда запускаем Постоянно, после каждой строчки кода. Реже, но основательно — перед тем как выкатить всё нахуй.

Вот тебе пример Unit-теста, где мы все вокруг наёбываем (JUnit 5 + Mockito):

@Test
void shouldReturnUserWhenFound() {
    // 1. ПОДГОТОВКА: Создаём фейковый репозиторий и наш сервис
    UserRepository mockRepo = mock(UserRepository.class);
    UserService service = new UserService(mockRepo);
    User expectedUser = new User(1L, "Alice");
    // Говорим муляжу: "Когда вызовут findById с единичкой, верни эту Алису"
    when(mockRepo.findById(1L)).thenReturn(Optional.of(expectedUser));

    // 2. ДЕЙСТВИЕ: Дёргаем наш метод
    User actualUser = service.getUserById(1L);

    // 3. ПРОВЕРКА: А получили ли мы то, что хотели?
    assertEquals("Alice", actualUser.getName());
    verify(mockRepo).findById(1L); // И заодно тыкаем муляж носом: "А вызывали тебя, сука?"
}

А вот интеграционный тест, где уже по-взрослому, с настоящей БД (Spring Boot):

@SpringBootTest
@AutoConfigureTestDatabase // Подменяет продакшн-БД на лёгкую, in-memory (типа H2)
class UserServiceIntegrationTest {
    @Autowired
    private UserRepository repository; // Внедряем ПРЯМО НАСТОЯЩИЙ репозиторий, ёпта!
    @Autowired
    private UserService service; // И настоящий сервис

    @Test
    void shouldSaveAndRetrieveUser() {
        // Сохраняем юзера в БД — это уже не муляж!
        User savedUser = repository.save(new User(null, "Bob"));
        // А теперь достаём его через сервис, который пойдёт в эту же БД
        User foundUser = service.getUserById(savedUser.getId());

        assertThat(foundUser.getName()).isEqualTo("Bob"); // И охуеваем, если не сработало
    }
}

Пирамида тестирования — это вообще святое. Внизу, основанием, лежат unit-тесты, их дохуя. Над ними — интеграционные, их поменьше. А на самом верху — редкие и толстые, как начальник, end-to-end тесты. Без этой пирамиды твой проект — это как дом без фундамента, который развалится от первого же чиха в его сторону.