Пишешь ли тесты на проекте
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Тестирование на проекте: лучшие практики в production Java
Написание тестов — критически важный аспект профессиональной разработки. Тесты гарантируют качество кода, упрощают рефакторинг и предотвращают регрессии. В современных Java проектах тестирование — обязательный стандарт.
Виды тестов
1. Unit Tests
Тестирование отдельных единиц кода (методов, функций):
@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void testGetUserSuccess() {
// Arrange
Long userId = 1L;
User expectedUser = new User(userId, "John");
when(userRepository.findById(userId)).thenReturn(Optional.of(expectedUser));
// Act
User result = userService.getUser(userId);
// Assert
assertEquals(expectedUser, result);
verify(userRepository).findById(userId);
}
@Test
void testGetUserNotFound() {
Long userId = 999L;
when(userRepository.findById(userId)).thenReturn(Optional.empty());
assertThrows(UserNotFoundException.class, () -> {
userService.getUser(userId);
});
}
}
2. Integration Tests
Тестирование взаимодействия компонентов:
@SpringBootTest
@ActiveProfiles("test")
public class UserServiceIntegrationTest {
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@Test
@Transactional
void testCreateAndRetrieveUser() {
// Create user through service
UserRequest request = new UserRequest("Jane", "jane@example.com");
User created = userService.createUser(request);
// Verify saved to DB
Optional<User> retrieved = userRepository.findById(created.getId());
assertTrue(retrieved.isPresent());
assertEquals("Jane", retrieved.get().getName());
}
}
3. End-to-End Tests
Тестирование полного пути запроса через приложение:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserControllerE2ETest {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Test
void testCreateUserEndpoint() {
String url = "http://localhost:" + port + "/api/users";
UserRequest request = new UserRequest("Bob", "bob@example.com");
ResponseEntity<User> response = restTemplate.postForEntity(
url, request, User.class
);
assertEquals(HttpStatus.CREATED, response.getStatusCode());
assertEquals("Bob", response.getBody().getName());
}
}
Best Practices
1. Следуй AAA паттерну (Arrange-Act-Assert):
@Test
void testCalculateDiscount() {
// Arrange - подготовка данных
Order order = new Order(100.0);
DiscountService service = new DiscountService();
// Act - выполнение
double discounted = service.applyDiscount(order, 0.1);
// Assert - проверка результата
assertEquals(90.0, discounted);
}
2. Один assert за тест или используй AssertJ:
// ❌ ПЛОХО - много проверок в одном тесте
@Test
void testUser() {
User user = new User("John", 30, "john@example.com");
assertEquals("John", user.getName());
assertEquals(30, user.getAge());
assertEquals("john@example.com", user.getEmail());
assertTrue(user.isActive());
}
// ✅ ХОРОШО - separate tests
@Test
void testUserNameCorrect() {
assertEquals("John", user.getName());
}
@Test
void testUserAgeCorrect() {
assertEquals(30, user.getAge());
}
// ИЛИ с AssertJ:
@Test
void testUser() {
assertThat(user)
.hasFieldOrPropertyWithValue("name", "John")
.hasFieldOrPropertyWithValue("age", 30)
.hasFieldOrPropertyWithValue("email", "john@example.com");
}
3. Используй мокирование для изоляции кода:
@Test
void testUserServiceWithMock() {
UserRepository mockRepo = mock(UserRepository.class);
when(mockRepo.findById(1L)).thenReturn(Optional.of(new User(1L, "John")));
UserService service = new UserService(mockRepo);
User result = service.getUser(1L);
assertEquals("John", result.getName());
verify(mockRepo, times(1)).findById(1L);
verify(mockRepo, never()).findAll();
}
4. Тестируй edge cases и error scenarios:
@Test
void testPaymentProcessing() {
// Happy path
assertTrue(paymentService.process(100.0));
// Edge cases
assertFalse(paymentService.process(0.0)); // Zero amount
assertThrows(IllegalArgumentException.class, () -> {
paymentService.process(-50.0); // Negative amount
});
// Error scenarios
when(paymentGateway.charge(anyDouble())).thenThrow(PaymentException.class);
assertThrows(PaymentException.class, () -> {
paymentService.process(100.0);
});
}
5. Используй фикстуры (fixtures) для переиспользуемых данных:
@SpringBootTest
public class UserServiceTest {
private User testUser;
@BeforeEach
void setUp() {
testUser = new User("TestUser", "test@example.com");
}
@Test
void testFirstScenario() {
// Используем готовый testUser
assertNotNull(testUser);
}
@Test
void testSecondScenario() {
// Повторно используем testUser
assertEquals("TestUser", testUser.getName());
}
}
6. Используй параметризованные тесты:
@ParameterizedTest
@ValueSource(ints = {1, 3, 5, 7, 11})
void testIsPrimeNumber(int number) {
assertTrue(PrimeChecker.isPrime(number));
}
@ParameterizedTest
@CsvSource({
"1,1,2",
"2,3,5",
"10,20,30"
})
void testAdd(int a, int b, int expected) {
assertEquals(expected, a + b);
}
7. Используй TestContainers для integration тестов:
@SpringBootTest
@Testcontainers
public class UserRepositoryTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(
DockerImageName.parse("postgres:14")
).withDatabaseName("test");
@Autowired
private UserRepository userRepository;
@Test
void testSaveAndRetrieveUser() {
User user = new User("John", "john@example.com");
userRepository.save(user);
Optional<User> retrieved = userRepository.findByEmail("john@example.com");
assertTrue(retrieved.isPresent());
}
}
Coverage Requirements
Целевой уровень покрытия:
# Команды для запуска тестов и проверки coverage
# Unit tests
mvn test
# Coverage report
mvn jacoco:report
# Отчёт в target/site/jacoco/index.html
# Целевой уровень:
# - Business logic: 90%+
# - Controllers: 80%+
# - Utilities: 80%+
# - Getters/setters: опционально
# Минимум для production:
# 80% overall coverage
Common Testing Mistakes
❌ Плохие практики:
// 1. Тестирование implementation вместо behavior
@Test
void testPrivateFieldValue() {
// Тестируем внутреннее состояние - BAD
assertEquals(true, getPrivateField(obj, "flag"));
}
// 2. Зависимость от порядка тестов
static int counter = 0;
@Test
void testIncrement() {
counter++; // ❌ Тесты зависят от порядка выполнения
assertEquals(1, counter);
}
// 3. Медленные тесты
@Test
void testSlowDatabase() {
// Каждый раз обращаемся к БД - медленно
user = databaseService.getUserFromDatabase(1L);
}
// 4. Тесты, которые иногда проходят, иногда нет (flaky tests)
@Test
void testAsync() {
service.asyncTask();
assertEquals(expected, result); // Race condition!
}
✅ Хорошие практики:
// 1. Тестируем публичный API
@Test
void testGetUserReturnsCorrectData() {
User user = userService.getUser(1L);
assertEquals("John", user.getName());
}
// 2. Тесты независимы друг от друга
@Test
@Isolated
void testIndependentScenario() {
// Каждый тест создаёт свой контекст
}
// 3. Используем мокирование
@Test
void testWithMock() {
when(database.getUser(1L)).thenReturn(mockUser);
// Быстро, нет зависимости от реальной БД
}
// 4. Синхронизируем async код
@Test
void testAsync() {
CompletableFuture<String> future = service.asyncTask();
String result = future.join(); // Ждём результат
assertEquals(expected, result);
}
Testing Tools and Libraries
<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.2</version>
</dependency>
<!-- Mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.2.0</version>
</dependency>
<!-- AssertJ для лучших assertions -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.24.1</version>
</dependency>
<!-- TestContainers для integration тестов -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.17.6</version>
</dependency>
Вывод: Написание тестов — неотъемлемая часть профессиональной Java разработки. Цель — не просто получить высокое coverage, а написать качественные, поддерживаемые тесты, которые предотвращают регрессии и облегчают рефакторинг. Используй правильные инструменты, следуй best practices и постоянно улучшай качество своего тестового кода.