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

Ответ

Это разные уровни тестирования, решающие distinct задачи в процессе разработки.

Unit-тесты (Модульные тесты):

  • Цель: Проверить корректность работы отдельного, изолированного модуля (функции, метода, класса) в идеальных условиях.
  • Изоляция: Все внешние зависимости (база данных, файловая система, API) заменяются заглушками (mocks/stubs/fakes).
  • Скорость: Выполняются очень быстро (миллисекунды).
  • Пример (xUnit + Moq):

    public class OrderServiceTests {
    [Fact]
    public void CalculateTotal_WithItems_ReturnsCorrectSum() {
        // Arrange
        var mockItem = new Mock<IOrderItem>();
        mockItem.Setup(i => i.Price).Returns(100);
        var service = new OrderService();
    
        // Act
        var total = service.CalculateTotal(new List<IOrderItem> { mockItem.Object, mockItem.Object });
    
        // Assert
        Assert.Equal(200, total);
    }
    }

Интеграционные тесты:

  • Цель: Проверить взаимодействие нескольких компонентов или системы с внешними ресурсами (БД, файлы, микросервисы).
  • Изоляция: Минимальная или отсутствует. Тесты работают с реальными или приближенными к реальным зависимостями (например, тестовой БД).
  • Скорость: Выполняются медленно (секунды или минуты).
  • Пример (интеграция с БД):

    public class UserRepositoryIntegrationTests : IClassFixture<DatabaseFixture> {
    private readonly AppDbContext _dbContext;
    
    public UserRepositoryIntegrationTests(DatabaseFixture fixture) {
        _dbContext = fixture.CreateContext(); // Поднимается тестовая БД
    }
    
    [Fact]
    public async Task CreateUser_ValidData_UserSavedInDatabase() {
        // Arrange
        var repo = new UserRepository(_dbContext);
        var user = new User { Name = "Test" };
    
        // Act
        await repo.AddAsync(user);
        await _dbContext.SaveChangesAsync();
    
        // Assert
        var userFromDb = await _dbContext.Users.FindAsync(user.Id);
        Assert.NotNull(userFromDb);
        Assert.Equal("Test", userFromDb.Name);
    }
    }

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

Ответ 18+ 🔞

А, ну это же классика, про которую все говорят, но половина нихуя не понимает разницы! Смотри, тут всё просто, как три копейки, если не усложнять.

Юнит-тесты (или модульные, кому как удобнее) Их задача — проверить одну мелкую детальку в идеальных условиях. Представь, что у тебя есть функция, которая складывает два числа. Ты её вырываешь из всего контекста, сажаешь в стерильную комнату и начинаешь тыкать: «А ну-ка, сложи 2 и 2!». Все её друзья — база данных, файловая система, соседний микросервис — остаются за дверью в виде заглушек. Эти тесты быстрые, как угорелые, потому что по сути гоняют голую логику.

Вот, смотри на пример, тут всё понятно:

public class OrderServiceTests {
    [Fact]
    public void CalculateTotal_WithItems_ReturnsCorrectSum() {
        // Arrange (Готовимся)
        var mockItem = new Mock<IOrderItem>(); // Это не реальный товар, а муляж!
        mockItem.Setup(i => i.Price).Returns(100); // Говорим муляжу: «Твоя цена — 100».
        var service = new OrderService();

        // Act (Действуем)
        var total = service.CalculateTotal(new List<IOrderItem> { mockItem.Object, mockItem.Object }); // Скидываем два муляжа

        // Assert (Проверяем)
        Assert.Equal(200, total); // Ждём, что сервис не обосрётся и вернёт 200.
    }
}

Суть в том, что если тест упадёт, ты сразу знаешь — косяк в логике CalculateTotal. Не в БД, не в сети, а прямо вот тут, в этих трёх строчках. Удобно, чё.

Интеграционные тесты А вот это уже поинтереснее. Тут мы проверяем, как наши детальки на самом деле работают вместе. Всё по-взрослому: поднимается тестовая база, наливается тестовое вино, и компоненты начинают общаться как в реальной жизни, а не через муляжи.

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

Глянь, как это выглядит:

public class UserRepositoryIntegrationTests : IClassFixture<DatabaseFixture> {
    private readonly AppDbContext _dbContext;

    public UserRepositoryIntegrationTests(DatabaseFixture fixture) {
        _dbContext = fixture.CreateContext(); // Вот она, наша тестовая база, живая!
    }

    [Fact]
    public async Task CreateUser_ValidData_UserSavedInDatabase() {
        // Arrange
        var repo = new UserRepository(_dbContext);
        var user = new User { Name = "Test" };

        // Act
        await repo.AddAsync(user); // Кидаем в репозиторий
        await _dbContext.SaveChangesAsync(); // Коммитим в БД, вот тут и может прилететь пиздец

        // Assert
        var userFromDb = await _dbContext.Users.FindAsync(user.Id); // Достаём обратно
        Assert.NotNull(userFromDb); // Если null — всё, пиши пропало, запись не сохранилась
        Assert.Equal("Test", userFromDb.Name); // А если сохранилась, то с правильным именем ли?
    }
}

Короче, резюмирую:

  • Юнит-тесты — это когда ты проверяешь мотор на стенде, в идеальной чистоте. Быстро и точно.
  • Интеграционные тесты — это когда ты этот мотор уже в машину поставил и пытаешься завести. Медленнее, но зато видно, как он с коробкой передач и бензобаком дружит.

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