← Назад к вопросам

Пишешь ли тесты на проекте

1.3 Junior🔥 161 комментариев
#Soft Skills и карьера#Тестирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Тестирование на проекте: лучшие практики в 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 и постоянно улучшай качество своего тестового кода.

Пишешь ли тесты на проекте | PrepBro