Как относишься к тестам
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Мой подход к тестированию
Тестирование — это не просто обязанность, а основной инструмент качественной разработки. Вот мой профессиональный подход.
Философия тестирования
Для меня тесты — это:
1. Страховка от регрессии
Тесты защищают от простых ошибок при рефакторинге:
// Когда я меняю реализацию, тесты гарантируют, что поведение не изменилось
@Test
void testUserCreationIncrementCount() {
// Arrange
UserService service = new UserService();
int initialCount = service.getUserCount();
// Act
service.createUser("John", "john@example.com");
// Assert
assertEquals(initialCount + 1, service.getUserCount());
}
Если я потом оптимизирую реализацию (например, меняю внутреннюю структуру), этот тест гарантирует корректность.
2. Документация кода
Тесты показывают, как код предполагается использовать:
// Из этого теста видно, что:
// - можно создать пользователя
// - нельзя создать пользователя с null email
// - повторное создание с тем же email возвращает старого пользователя
@Test
void testUserCreationVariations() {
UserService service = new UserService();
// Успешное создание
User user = service.createUser("john@example.com");
assertNotNull(user);
// Null email
assertThrows(IllegalArgumentException.class,
() -> service.createUser(null));
// Повторное создание
User same = service.createUser("john@example.com");
assertEquals(user.id, same.id); // Тот же пользователь
}
3. Инструмент проектирования (TDD)
Я часто использую Test-Driven Development при разработке:
// Шаг 1: Пишу тест (RED — падает)
@Test
void testCalculateMonthlyRevenue() {
RevenueCalculator calculator = new RevenueCalculator();
calculator.addOrder(new Order(100, "2024-01-15"));
calculator.addOrder(new Order(50, "2024-01-20"));
calculator.addOrder(new Order(75, "2024-02-10"));
double january = calculator.calculateMonthlyRevenue("2024-01");
assertEquals(150.0, january);
}
// Шаг 2: Пишу минимальный код (GREEN — проходит)
public class RevenueCalculator {
private List<Order> orders = new ArrayList<>();
public void addOrder(Order order) {
orders.add(order);
}
public double calculateMonthlyRevenue(String month) {
return orders.stream()
.filter(o -> o.getDate().startsWith(month))
.mapToDouble(Order::getAmount)
.sum();
}
}
// Шаг 3: Рефакторю (REFACTOR — остаётся GREEN)
Виды тестов и мой подход
Unit тесты (70% времени)
Тестирую отдельные компоненты:
@SpringBootTest
class UserRepositoryTests {
@MockBean
private EmailService emailService;
@Autowired
private UserRepository userRepository;
@Test
void testFindByEmail() {
// Arrange
User user = new User();
user.setEmail("test@example.com");
userRepository.save(user);
// Act
Optional<User> found = userRepository.findByEmail("test@example.com");
// Assert
assertTrue(found.isPresent());
assertEquals(user.id, found.get().id);
}
}
Integration тесты (20% времени)
Тестирую взаимодействие компонентов:
@SpringBootTest
class UserServiceIntegrationTests {
@Autowired
private UserService userService;
@Autowired
private EmailService emailService;
@Test
void testUserCreationSendsEmail() {
// Arrange
User user = new User("john@example.com", "John");
// Act
User created = userService.createUser(user);
// Assert
verify(emailService).sendWelcomeEmail("john@example.com");
assertEquals("john@example.com", created.getEmail());
}
}
E2E тесты (10% времени)
Тестирую полные сценарии через API:
@SpringBootTest(webEnvironment = RANDOM_PORT)
class UserApiE2ETests {
@Autowired
private TestRestTemplate restTemplate;
@Test
void testCompleteUserJourney() {
// 1. Регистрация
var register = restTemplate.postForObject(
"/api/users/register",
new RegisterRequest("john@example.com", "password123"),
User.class
);
assertNotNull(register);
// 2. Логин
var login = restTemplate.postForObject(
"/api/auth/login",
new LoginRequest("john@example.com", "password123"),
LoginResponse.class
);
assertNotNull(login.getToken());
// 3. Получение профиля
var headers = new HttpHeaders();
headers.setBearerAuth(login.getToken());
var profile = restTemplate.exchange(
"/api/users/me",
GET,
new HttpEntity<>(headers),
User.class
);
assertEquals("john@example.com", profile.getBody().getEmail());
}
}
Лучшие практики, которые я соблюдаю
1. Arrange-Act-Assert (AAA)
@Test
void testPaymentProcessing() {
// Arrange — подготовка данных
Payment payment = new Payment(100.0, "USD");
PaymentProcessor processor = new PaymentProcessor();
// Act — выполнение
PaymentResult result = processor.process(payment);
// Assert — проверка результата
assertTrue(result.isSuccess());
assertEquals("USD", result.getCurrency());
}
2. One assertion per test (когда возможно)
// ❌ Плохо — много проверок
@Test
void testUser() {
User user = createUser();
assertEquals("John", user.getName());
assertEquals(25, user.getAge());
assertNotNull(user.getEmail());
}
// ✅ Хорошо — одна проверка
@Test
void testUserHasCorrectName() {
User user = createUser();
assertEquals("John", user.getName());
}
3. Мокирование внешних зависимостей
@Test
void testOrderCreationCallsPaymentGateway() {
// Mock внешний сервис
PaymentGateway gatewayMock = mock(PaymentGateway.class);
when(gatewayMock.charge(anyDouble()))
.thenReturn(new ChargeResult("success", "txn_123"));
OrderService orderService = new OrderService(gatewayMock);
// Act
Order order = orderService.createOrder(new Order(99.99));
// Assert
verify(gatewayMock).charge(99.99);
assertTrue(order.isPaid());
}
4. Параметризованные тесты
@ParameterizedTest
@CsvSource({
"admin, true", // email, isValid
"user@test, false",
"test@example.com, true"
})
void testEmailValidation(String email, boolean expectedValid) {
EmailValidator validator = new EmailValidator();
assertEquals(expectedValid, validator.isValid(email));
}
Когда я НЕ пишу тесты
Буду честен — не всё нужно тестировать:
// ❌ Не тестирую очевидные getters/setters
public class User {
private String email;
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
// ❌ Не тестирую конфигурацию Spring (она проверяется при запуске)
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() { ... }
}
// ❌ Не тестирую код третьих библиотек (они уже протестированы)
Мой опыт с тестами
За 10+ лет разработки я понял:
-
Тесты экономят время — удобнее писать тесты, чем вручную проверять одно и то же 100 раз
-
Хорошие тесты — ценнее, чем хороший код — плохой код с хорошими тестами можно рефакторить. Хороший код без тестов становится хрупким
-
100% coverage — не цель — цель — тестировать критичный код. Coverage 70-85% обычно хороший баланс
-
Тесты требуют навыков — писать быстрые, понятные, не-flaky тесты — это отдельное мастерство
-
TDD работает — когда пишу тесты сначала, код получается чище и легче тестировать
Резюме
Тестирование для меня — это не обязанность, а инструмент качества и скорости разработки. Я пишу тесты потому что:
- Они дают уверенность при изменении кода
- Они служат документацией
- Они ловят баги на ранних стадиях
- Они помогают проектировать лучший код
Я ищу баланс между полнотой тестирования и скоростью разработки, и всегда готов объяснить, какие части кода нужно тестировать и почему.