Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Тестирование собственного кода
Да, абсолютно. Тестирование — это не отдельная стадия, а неотъемлемая часть разработки. За 10+ лет я понял, что код без тестов — это код, который сломается в самый неожиданный момент в продакшене.
Почему я тестирую
1. Уверенность в коде
Тесты — это страховка. Они дают уверенность, что:
- Код работает так, как я его спроектировал
- Рефакторинг не сломает существующий функционал
- Новые разработчики не испортят мою логику
2. Экономия времени
На первый взгляд, тесты требуют времени. Но на практике:
- Без тестов: ловишь баги в продакшене, срочно ищешь причину, переделываешь
- С тестами: баги выявляются сразу, исправляются на месте
3. Документация кода
Хорошие тесты — это живая документация. Из теста видно:
- Что должен делать метод
- Какие граничные случаи он обрабатывает
- Какой результат ожидается
Стратегия тестирования
Я использую пирамиду тестирования:
- Unit тесты (много, быстро)
- Integration тесты (среднее количество)
- E2E тесты (редко, долго)
Unit тесты (80% времени)
Тесты отдельных методов, изолированные от зависимостей:
public class OrderCalculatorTest {
private OrderCalculator calculator;
@BeforeEach
void setUp() {
calculator = new OrderCalculator();
}
@Test
void testCalculateSubtotal_ValidItems() {
OrderItem item1 = new OrderItem("SKU1", 100.0, 2);
OrderItem item2 = new OrderItem("SKU2", 50.0, 1);
Order order = new Order(Arrays.asList(item1, item2));
double subtotal = calculator.calculateSubtotal(order);
assertEquals(250.0, subtotal);
}
@Test
void testCalculateSubtotal_WithDiscount() {
OrderItem item = new OrderItem("SKU1", 100.0, 1, true);
Order order = new Order(Arrays.asList(item));
double subtotal = calculator.calculateSubtotal(order);
assertEquals(90.0, subtotal);
}
@Test
void testCalculateSubtotal_EmptyOrder() {
Order order = new Order(Collections.emptyList());
double subtotal = calculator.calculateSubtotal(order);
assertEquals(0.0, subtotal);
}
}
Integration тесты (15% времени)
Тесты взаимодействия компонентов:
@SpringBootTest
class OrderProcessorIntegrationTest {
@Autowired
private OrderProcessor orderProcessor;
@Autowired
private OrderRepository orderRepository;
@MockBean
private EmailService emailService;
@Test
void testProcessOrder_SavesAndSendsEmail() {
Order order = new Order(Arrays.asList(
new OrderItem("SKU1", 100.0, 1)
));
orderProcessor.processOrder(order);
Order saved = orderRepository.findById(order.getId()).orElseThrow();
assertEquals("PROCESSED", saved.getStatus());
verify(emailService).sendConfirmation(eq(order), any());
}
}
E2E тесты (5% времени)
Тесты всей системы через API:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class OrderApiE2ETest {
@Autowired
private TestRestTemplate restTemplate;
@Test
void testCreateOrderEndToEnd() {
OrderRequest request = new OrderRequest(Arrays.asList(
new OrderItemRequest("SKU1", 100.0, 1)
));
ResponseEntity<OrderResponse> response = restTemplate.postForEntity(
"/api/orders",
request,
OrderResponse.class
);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody().getId());
}
}
Инструменты, которые я использую
JUnit 5
Стандартный фреймворк для unit тестов.
Mockito
Для изоляции зависимостей:
@Mock
private UserRepository repository;
@InjectMocks
private UserService service;
@Test
void testGetUser() {
User user = new User(1L, "John");
when(repository.findById(1L)).thenReturn(Optional.of(user));
User result = service.getUser(1L);
assertEquals("John", result.getName());
verify(repository).findById(1L);
}
AssertJ
Читаемые проверки:
assertThat(list)
.hasSize(3)
.contains("A", "B", "C")
.doesNotContainNull();
TestContainers
Для интеграционных тестов с реальной БД в Docker:
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer("postgres:14")
.withDatabaseName("test");
JaCoCo
Для измерения покрытия кода:
mvn clean test jacoco:report
Цель: минимум 80% покрытие, для критичного кода — 95%+.
Подход: TDD (Test-Driven Development)
Очень часто я использую TDD:
- RED → Написать тест (он падает)
- GREEN → Написать минимальный код (тест проходит)
- REFACTOR → Улучшить код (тесты остаются зелёными)
Пример:
@Test
void testCalculatePriceWithTax() {
double price = calculator.calculateWithTax(100, "USA");
assertEquals(108, price);
}
public double calculateWithTax(double price, String country) {
double taxRate = getTaxRate(country);
return price * (1 + taxRate);
}
private double getTaxRate(String country) {
return switch(country) {
case "USA" -> 0.08;
case "UK" -> 0.20;
default -> 0.0;
};
}
Граничные случаи, которые я тестирую
- Null значения: null, пустые коллекции
- Граничные значения: минимум, максимум, ноль, отрицательные числа
- Исключения: какие ошибки должен выбросить метод
- Состояние: объект до и после операции
- Параллелизм: race conditions, потокобезопасность
@Test
void testWithNullInput() {
assertThrows(IllegalArgumentException.class,
() -> calculator.calculate(null));
}
@Test
void testWithZeroValue() {
assertEquals(0, calculator.calculate(0));
}
Мой подход к тестированию в команде
- Обязательные тесты: каждый PR должен иметь тесты
- Code review на тесты: проверяю качество тестов
- CI/CD: тесты запускаются автоматически перед мерджем
- Метрики: отслеживаю покрытие, скорость выполнения
Вывод
Тестирование — это не роскошь, а необходимость. Чем раньше я пишу тесты, тем:
- Меньше багов в продакшене
- Быстрее я разрабатываю
- Увереннее я себя чувствую при рефакторинге
- Проще новым разработчикам понять код
Тесты — это инвестиция в качество, которая окупается сотни раз.