Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Юнит тестирование в Java
Что такое юнит тест
Юнит тест (Unit Test) — это автоматизированный тест, который проверяет одну "единицу" кода в изоляции. Единица обычно — это один метод или одна функция. Цель: убедиться, что код работает корректно на входах и ожидает корректные выходы.
Зачем нужны юнит тесты
- Рано ловят баги — перед production
- Документация — тесты показывают, как использовать код
- Рефакторинг с уверенностью — переписываешь код, не боясь сломать
- Design validation — если метод сложно тестировать, его дизайн плохой
- Regression prevention — старые баги не вернутся
Популярные фреймворки
- JUnit — стандарт (JUnit 4, JUnit 5)
- Mockito — мокирование зависимостей
- AssertJ — удобные assertions
- Hamcrest — матчеры
Установка
<!-- JUnit 5 (современный) -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
<!-- Mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.2.1</version>
<scope>test</scope>
</dependency>
<!-- AssertJ -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.24.1</version>
<scope>test</scope>
</dependency>
Пример: код для тестирования
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public int divide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("Cannot divide by zero");
}
return a / b;
}
public boolean isEven(int number) {
return number % 2 == 0;
}
}
Основная структура теста
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class CalculatorTest {
// Arrange — подготовка
// Act — действие
// Assert — проверка
@Test
public void testAddTwoPositiveNumbers() {
// Arrange
Calculator calculator = new Calculator();
int a = 5;
int b = 3;
// Act
int result = calculator.add(a, b);
// Assert
assertEquals(8, result);
}
@Test
public void testAddNegativeNumbers() {
Calculator calculator = new Calculator();
assertEquals(-5, calculator.add(-3, -2));
}
@Test
public void testAddZero() {
Calculator calculator = new Calculator();
assertEquals(5, calculator.add(5, 0));
}
}
Жизненный цикл теста
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.AfterAll;
public class TestLifecycle {
// Статический — один раз для ВСЕХ тестов класса
@BeforeAll
static void setupAll() {
System.out.println("Один раз в начале");
}
// Перед КАЖДЫМ тестом
@BeforeEach
void setup() {
System.out.println("Перед тестом");
}
@Test
void test1() {
System.out.println("Тест 1");
}
@Test
void test2() {
System.out.println("Тест 2");
}
// После КАЖДОГО теста
@AfterEach
void teardown() {
System.out.println("После теста");
}
// Статический — один раз для ВСЕХ тестов класса
@AfterAll
static void teardownAll() {
System.out.println("Один раз в конце");
}
}
/* Вывод:
Один раз в начале
Перед тестом
Тест 1
После теста
Перед тестом
Тест 2
После теста
Один раз в конце
*/
Assertions
@Test
public void testAssertions() {
// Равенство
assertEquals(4, 2 + 2);
assertNotEquals(4, 2 + 1);
// True/False
assertTrue(5 > 3);
assertFalse(5 < 3);
// null
String str = null;
assertNull(str);
assertNotNull("hello");
// То же самое
Object obj1 = new Object();
Object obj2 = obj1;
assertSame(obj1, obj2) // Одно и то же объект
// Исключения
assertThrows(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("Error");
});
// Содержит
String message = "Hello World";
assertTrue(message.contains("Hello"));
}
Более удобные assertions с AssertJ
import static org.assertj.core.api.Assertions.*;
@Test
public void testWithAssertJ() {
String text = "Hello World";
// Цепочка assertions (более читаемо)
assertThat(text)
.isNotEmpty()
.hasLength(11)
.contains("Hello")
.startsWith("Hello")
.endsWith("World");
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
assertThat(numbers)
.isNotEmpty()
.hasSize(5)
.contains(1, 3, 5)
.doesNotContain(0);
User user = new User("John", 30);
assertThat(user)
.hasFieldOrPropertyWithValue("name", "John")
.hasFieldOrPropertyWithValue("age", 30);
}
Мокирование с Mockito
import org.mockito.Mock;
import org.mockito.InjectMocks;
import static org.mockito.Mockito.*;
public class UserServiceTest {
@Mock
private UserRepository userRepository; // Mock зависимость
@InjectMocks
private UserService userService; // Инжектить mock'и
@BeforeEach
public void setup() {
MockitoAnnotations.openMocks(this);
}
@Test
public void testGetUserById() {
// Arrange: настроить мок
User mockUser = new User(1, "John", "john@example.com");
when(userRepository.findById(1))
.thenReturn(Optional.of(mockUser));
// Act
User result = userService.getUserById(1);
// Assert
assertNotNull(result);
assertEquals("John", result.getName());
// Проверить, что метод был вызван
verify(userRepository).findById(1);
}
@Test
public void testGetUserNotFound() {
// Arrange
when(userRepository.findById(999))
.thenReturn(Optional.empty());
// Act & Assert
assertThrows(UserNotFoundException.class, () -> {
userService.getUserById(999);
});
}
}
Параметризованные тесты
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.jupiter.params.provider.CsvSource;
public class ParameterizedTests {
// Один параметр
@ParameterizedTest
@ValueSource(ints = {1, 3, 5, 7, 9})
public void testOddNumbers(int number) {
assertTrue(number % 2 != 0);
}
// Несколько параметров (CSV)
@ParameterizedTest
@CsvSource({
"2, 2, 4",
"5, 3, 8",
"-1, 1, 0"
})
public void testAdd(int a, int b, int expected) {
Calculator calc = new Calculator();
assertEquals(expected, calc.add(a, b));
}
}
Сценарий: Service с зависимостями
// Класс для тестирования
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
private final EmailService emailService;
public OrderService(OrderRepository orderRepository,
PaymentService paymentService,
EmailService emailService) {
this.orderRepository = orderRepository;
this.paymentService = paymentService;
this.emailService = emailService;
}
public Order createOrder(Order order) {
// Валидация
if (order.getItems().isEmpty()) {
throw new IllegalArgumentException("Order must have items");
}
// Сохранение
Order savedOrder = orderRepository.save(order);
// Обработка платежа
paymentService.process(savedOrder);
// Отправка email
emailService.sendConfirmation(savedOrder);
return savedOrder;
}
}
// Юнит тест
@ExtendWith(MockitoExtension.class) // JUnit 5 + Mockito
public class OrderServiceTest {
@Mock
private OrderRepository orderRepository;
@Mock
private PaymentService paymentService;
@Mock
private EmailService emailService;
@InjectMocks
private OrderService orderService;
@Test
public void testCreateOrderSuccess() {
// Arrange
Order order = new Order();
order.addItem(new Item("Book", 100));
order.addItem(new Item("Pen", 50));
Order savedOrder = new Order(1, order.getItems(), OrderStatus.PENDING);
when(orderRepository.save(order)).thenReturn(savedOrder);
// Act
Order result = orderService.createOrder(order);
// Assert
assertEquals(1, result.getId());
assertEquals(OrderStatus.PENDING, result.getStatus());
// Проверить вызовы
verify(orderRepository).save(order);
verify(paymentService).process(savedOrder);
verify(emailService).sendConfirmation(savedOrder);
}
@Test
public void testCreateOrderWithEmptyItems() {
// Arrange
Order order = new Order(); // Нет items
// Act & Assert
assertThrows(IllegalArgumentException.class, () -> {
orderService.createOrder(order);
});
// Verify что никто не был вызван
verify(orderRepository, never()).save(any());
verify(paymentService, never()).process(any());
}
}
Best Practices
- Одна assertion за тест (или несколько связанных):
// ❌ Плохо: множество независимых проверок
@Test
public void testEverything() {
assertEquals(4, 2 + 2);
assertTrue(5 > 3);
assertNull(null);
}
// ✅ Хорошо: одна ответственность
@Test
public void testAddition() { assertEquals(4, 2 + 2); }
@Test
public void testComparison() { assertTrue(5 > 3); }
- Избегай зависимости между тестами:
// ❌ Плохо: test2 зависит от test1
int sharedValue;
@Test
void test1() { sharedValue = 5; }
@Test
void test2() { assertEquals(5, sharedValue); }
// ✅ Хорошо: каждый тест независим
@Test
void test1() { assertEquals(5, getValue()); }
@Test
void test2() { assertEquals(5, getValue()); }
- Понятные имена тестов:
// ❌ Плохо
@Test
void test1() { }
// ✅ Хорошо
@Test
void shouldThrowExceptionWhenDividingByZero() { }
Запуск тестов
# Maven
mvn test
mvn test -Dtest=CalculatorTest # Конкретный класс
mvn test -Dtest=CalculatorTest#testAdd # Конкретный метод
# Gradle
gradle test
gradle test --tests CalculatorTest
Покрытие кода (Coverage)
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.8</version>
</plugin>
mvn clean test jacoco:report
# Отчёт: target/site/jacoco/index.html
Вывод
Юнит тестирование — это критичный навык для Java разработчика. Хорошо протестированный код — это уверенность в production, быстрая разработка и меньше ночных вызовов.