← Назад к вопросам
В чем разница между unit тестами и функциональными тестами?
1.0 Junior🔥 71 комментариев
#Основы Java
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
В чем разница между unit тестами и функциональными тестами?
Unit тесты и функциональные тесты проверяют разные уровни приложения. Unit тесты проверяют маленькие части кода изолированно, функциональные тесты проверяют целые сценарии использования конца-в-конец. Обе необходимы для полного покрытия качества.
Unit тесты: тестирование компонентов
Unit тест проверяет один метод/класс изолированно
// Класс для тестирования
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public int multiply(int a, int b) {
return a * b;
}
}
// Unit тесты
@ExtendWith(MockitoExtension.class)
class CalculatorTest {
private Calculator calculator;
@BeforeEach
void setUp() {
calculator = new Calculator();
}
@Test
void testAdd() {
// Arrange
int a = 2, b = 3;
// Act
int result = calculator.add(a, b);
// Assert
assertEquals(5, result);
}
@Test
void testMultiply() {
int result = calculator.multiply(3, 4);
assertEquals(12, result);
}
@Test
void testAddWithNegatives() {
int result = calculator.add(-2, -3);
assertEquals(-5, result);
}
}
Unit тест с mocks
// Класс с зависимостями
public class UserService {
private UserRepository repository;
public UserService(UserRepository repository) {
this.repository = repository;
}
public User findById(int id) {
return repository.findById(id);
}
}
// Unit тест (изолированный)
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository mockRepository;
private UserService service;
@BeforeEach
void setUp() {
service = new UserService(mockRepository);
}
@Test
void testFindByIdReturnUser() {
// Arrange
User expectedUser = new User(1, "John");
when(mockRepository.findById(1)).thenReturn(expectedUser);
// Act
User result = service.findById(1);
// Assert
assertEquals("John", result.getName());
verify(mockRepository).findById(1);
}
@Test
void testFindByIdNotFound() {
// Arrange
when(mockRepository.findById(999)).thenReturn(null);
// Act
User result = service.findById(999);
// Assert
assertNull(result);
}
}
Функциональные тесты: тестирование поведения
Функциональный тест проверяет полный сценарий использования
// Интеграционный функциональный тест
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerFunctionalTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private UserRepository repository;
@Test
void testGetUserFlow() throws Exception {
// Arrange: создаем пользователя в БД
User user = new User(1, "John", "john@example.com");
repository.save(user);
// Act & Assert: проверяем весь flow
mockMvc.perform(get("/users/1")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("John"))
.andExpect(jsonPath("$.email").value("john@example.com"));
}
@Test
void testCreateUserFlow() throws Exception {
// Arrange
String newUserJson = "{\"name\":\"Jane\",\"email\":\"jane@example.com\"}";
// Act & Assert: полный flow создания
mockMvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(newUserJson))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").exists())
.andExpect(jsonPath("$.name").value("Jane"));
// Verify: проверяем что сохранилось в БД
User saved = repository.findByEmail("jane@example.com");
assertNotNull(saved);
}
@Test
void testUserWorkflow() throws Exception {
// 1. Создание пользователя
String createJson = "{\"name\":\"Bob\",\"email\":\"bob@example.com\"}";
MvcResult createResult = mockMvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(createJson))
.andExpect(status().isCreated())
.andReturn();
// 2. Получение созданного пользователя
mockMvc.perform(get("/users/bob@example.com")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("Bob"));
// 3. Обновление пользователя
String updateJson = "{\"name\":\"Robert\"}";
mockMvc.perform(put("/users/bob@example.com")
.contentType(MediaType.APPLICATION_JSON)
.content(updateJson))
.andExpect(status().isOk());
// 4. Проверка обновления
mockMvc.perform(get("/users/bob@example.com"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("Robert"));
}
}
E2E (End-to-End) тест
// E2E тест с реальным браузером
class UserApplicationE2ETest {
private WebDriver driver;
@BeforeEach
void setUp() {
driver = new ChromeDriver();
driver.navigate().to("http://localhost:8080");
}
@Test
void testCompleteUserRegistrationFlow() {
// 1. Переходим на страницу регистрации
driver.findElement(By.linkText("Register")).click();
// 2. Заполняем форму
driver.findElement(By.id("name")).sendKeys("John Doe");
driver.findElement(By.id("email")).sendKeys("john@example.com");
driver.findElement(By.id("password")).sendKeys("password123");
// 3. Отправляем форму
driver.findElement(By.id("submit")).click();
// 4. Проверяем успешность
String message = driver.findElement(By.className("success")).getText();
assertEquals("Registration successful", message);
// 5. Проверяем редирект на профиль
assertEquals("http://localhost:8080/profile", driver.getCurrentUrl());
}
@AfterEach
void tearDown() {
driver.quit();
}
}
Сравнение
| Аспект | Unit тесты | Функциональные тесты |
|---|---|---|
| Область | Один метод/класс | Полный сценарий |
| Скорость | Быстро (ms) | Медленно (сек/мин) |
| Mocks | Много (изолируем) | Минимум (реальное) |
| Простота | Простые | Сложные |
| Кол-во | Много (сотни) | Мало (десятки) |
| Поддержка | Легко рефакторить | Хрупкие (brittle) |
| Покрытие | Детальное | Высокоуровневое |
| Примеры | JUnit, Mockito | Integration, Selenium |
Пирамида тестирования
/\
/ \
/ E2E \ (10%)
/________\
/ \
/Functional \ (30%)
/____________\
/ \
/ Unit Tests \ (60%)
/________________\
Unit тесты
Плюсы:
- ✅ Быстрые (ms)
- ✅ Легко писать
- ✅ Высокое покрытие
- ✅ Легко найти проблему
Минусы:
- ❌ Не ловят интеграционные баги
- ❌ Mocks могут скрывать проблемы
- ❌ Не проверяют реальное поведение
Функциональные тесты
Плюсы:
- ✅ Проверяют реальное поведение
- ✅ Ловят интеграционные баги
- ✅ Проверяют API contract
Минусы:
- ❌ Медленные
- ❌ Сложные для написания
- ❌ Хрупкие (brittle)
- ❌ Сложно найти причину проблемы
Пример комплексного подхода
// 1. Unit тесты: бизнес логика
@Test
void testCalculateDiscount() {
// Проверяем только логику расчета
Order order = new Order(100);
int discount = order.calculateDiscount(10); // 10% discount
assertEquals(10, discount);
}
// 2. Интеграционные тесты: слои работают вместе
@SpringBootTest
class OrderServiceIntegrationTest {
@Test
void testOrderSaveAndRetrieve() {
Order order = new Order(100);
service.save(order); // Реальная БД
Order retrieved = service.findById(order.getId());
assertEquals(100, retrieved.getAmount());
}
}
// 3. Функциональные тесты: весь flow
@Test
void testOrderCreationFlow() {
// 1. Создание заказа
mockMvc.perform(post("/orders")
.content("{\"amount\":100}"))
.andExpect(status().isCreated());
// 2. Получение заказа
mockMvc.perform(get("/orders/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.amount").value(100));
}
Лучшие практики
// ✅ Хорошо: разделение на уровни
public class OrderTest {
// Unit: только бизнес логика
@Test
void testApplyDiscount() {
Order order = new Order(100);
order.applyDiscount(10);
assertEquals(90, order.getTotal());
}
}
@SpringBootTest
class OrderControllerTest {
// Функциональный: API level
@Test
void testCreateOrderEndpoint() {
mockMvc.perform(post("/api/orders")
.content("{\"amount\":100}"))
.andExpect(status().isCreated());
}
}
// ❌ Плохо: смешивание уровней
@Test
void testEverything() {
Order order = new Order(100);
service.save(order);
repository.findById(order.getId());
String json = objectMapper.writeValueAsString(order);
// Что мы здесь тестируем?
}
Инструменты
Unit тестирование:
- JUnit (основной фреймворк)
- Mockito (mocking)
- AssertJ (assertions)
- Parameterized tests
Функциональное тестирование:
- Spring MockMvc (API тесты)
- RestAssured (REST API)
- Selenium (UI)
- Testcontainers (интеграция)
Вывод
Unit тесты:
- Пишите МНОГО
- Проверяйте бизнес логику
- Используйте mocks для изоляции
- Они должны быть БЫСТРЫМИ
Функциональные тесты:
- Пишите МЕНЬШЕ
- Проверяйте полный сценарий
- Тестируйте реальные интеграции
- Они медленнее, но проверяют реальность
Правило 70/20/10:
- 70% unit тесты
- 20% интеграционные
- 10% E2E
Оптимальный проект имеет БОЛЬШОе количество быстрых unit тестов, среднее количество интеграционных, и минимум медленных E2E тестов.