Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Типы тестов: стратегия тестирования для Java разработчика
Тестирование — критическая часть разработки, которая гарантирует качество кода и уверенность в изменениях. Разработчик должен писать разные типы тестов для разного уровня покрытия.
1. Unit тесты (юнит-тесты)
Что это: Тесты отдельных методов/функций в изоляции.
Характеристики:
- Тестируют одно поведение за раз
- Быстрые (миллисекунды)
- Mockируют все зависимости
- Запускаются часто (на каждое изменение)
public class CalculatorTest {
private Calculator calculator;
@Before
public void setUp() {
calculator = new Calculator();
}
@Test
public void testAddition() {
// Arrange
int a = 5;
int b = 3;
// Act
int result = calculator.add(a, b);
// Assert
assertEquals(8, result);
}
@Test
public void testDivisionByZero() {
assertThrows(IllegalArgumentException.class,
() -> calculator.divide(10, 0));
}
}
Инструменты: JUnit, TestNG, Mockito, AssertJ
Преимущества:
- Быстрая обратная связь при разработке
- Документирует поведение кода
- Упрощает рефакторинг
- Помогает выявить баги на ранней стадии
Coverage: 60-80% кода
2. Integration тесты
Что это: Тесты взаимодействия между компонентами (БД, API, очереди).
Характеристики:
- Используют реальные или тестовые БД
- Медленнее (секунды)
- Минимизируют mocking
- Проверяют contracts между сервисами
@SpringBootTest
@ActiveProfiles("test")
public class UserServiceIntegrationTest {
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@Test
public void testCreateAndFetchUser() {
// Arrange
User newUser = new User("john", "john@example.com");
// Act
User savedUser = userService.createUser(newUser);
User fetchedUser = userRepository.findById(savedUser.getId())
.orElseThrow();
// Assert
assertEquals("john", fetchedUser.getUsername());
}
@Test
public void testTransactionRollback() {
// Проверяем, что при ошибке транзакция откатывается
assertThrows(RuntimeException.class,
() -> userService.createInvalidUser());
assertEquals(0, userRepository.count());
}
}
Инструменты: Testcontainers, Spring Boot Test, WireMock
Преимущества:
- Проверяют реальное взаимодействие
- Ловят проблемы, которые юнит тесты пропускают
- Тестируют интеграцию с БД и внешними сервисами
Coverage: 20-40% кода
3. End-to-End (E2E) тесты
Что это: Тесты полного потока пользователя через весь stack.
Характеристики:
- Тестируют UI + Backend + БД
- Очень медленные (минуты)
- Запускаются редко (перед release)
- Требуют реального окружения
// Selenium / Playwright
public class UserRegistrationE2ETest {
private WebDriver driver;
@Before
public void setup() {
driver = new ChromeDriver();
}
@Test
public void testCompleteUserFlow() {
// Заходим на сайт
driver.get("http://localhost:3000");
// Кликаем на регистрацию
driver.findElement(By.id("signup-btn")).click();
// Заполняем форму
driver.findElement(By.name("username")).sendKeys("newuser");
driver.findElement(By.name("email")).sendKeys("user@example.com");
driver.findElement(By.name("password")).sendKeys("SecurePass123");
// Подтверждаем
driver.findElement(By.id("register-btn")).click();
// Проверяем, что пришли на главную
WebDriverWait wait = new WebDriverWait(driver, 10);
wait.until(ExpectedConditions.presenceOfElementLocated(
By.id("dashboard")));
assertTrue(driver.getCurrentUrl().contains("dashboard"));
}
}
Инструменты: Selenium, Cypress, Playwright
Преимущества:
- Проверяют весь user journey
- Ловят баги на границах слоёв
Недостатки:
- Очень медленные
- Хрупкие (часто сломаются)
- Дорогие в поддержке
- Запускать редко
4. Performance тесты
Что это: Тесты проверяющие производительность под нагрузкой.
Характеристики:
- Проверяют latency, throughput
- Моделируют высокую нагрузку
- Запускаются перед release
- Специальные инструменты
// JMH (Java Microbenchmark Harness)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class ListPerformanceBenchmark {
private List<Integer> arrayList;
private List<Integer> linkedList;
@Setup
public void setup() {
arrayList = new ArrayList<>(100000);
linkedList = new LinkedList<>();
for (int i = 0; i < 100000; i++) {
arrayList.add(i);
linkedList.add(i);
}
}
@Benchmark
public int accessArrayList() {
return arrayList.get(50000);
}
@Benchmark
public int accessLinkedList() {
return linkedList.get(50000);
}
}
Инструменты: JMH, Gatling, Apache JMeter, Locust
5. Security тесты
Что это: Тесты проверяющие уязвимости и безопасность.
Примеры:
- SQL Injection
- XSS атаки
- Authentication/Authorization
- CSRF защита
public class SecurityTest {
@Test
public void testSQLInjectionProtection() {
String userInput = "'; DROP TABLE users; --";
// Используем prepared statements
String query = "SELECT * FROM users WHERE id = ?";
// Это безопасно, userInput не может сломать query
List<User> users = repository.findByCustomQuery(query, userInput);
assertEquals(0, users.size());
}
@Test
public void testUnauthorizedAccess() {
// Без auth token не должна быть доступна
ResponseEntity<String> response =
restTemplate.getForEntity("/api/admin", String.class);
assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode());
}
}
6. Smoke тесты
Что это: Минимальные тесты проверяющие, что основной функционал работает.
Характеристики:
- Быстрые (максимум 10 минут)
- Проверяют только критичные пути
- Запускаются после каждого deploy
- Дают быструю обратную связь
public class SmokeTest {
@Test
public void testServerIsUp() {
ResponseEntity<String> response =
restTemplate.getForEntity("http://localhost:8080/health",
String.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
}
@Test
public void testDatabaseConnection() {
User user = userRepository.findById(1L).orElseThrow();
assertNotNull(user);
}
}
Pyramid тестирования (Test Pyramid)
/\ E2E тесты (5-10%)
/ \ Медленные, хрупкие, дорогие
/ \
/ \ Integration тесты (20-30%)
/ \ Средняя скорость, проверяют взаимодействие
/ \
/ \ Unit тесты (60-80%)
/______________\Быстрые, дешёвые, много
Что должен писать разработчик
ОБЯЗАТЕЛЬНО:
-
Unit тесты для всей бизнес-логики
- Каждый публичный метод
- Happy path и edge cases
- Error handling
-
Integration тесты для:
- Взаимодействия с БД
- API endpoints
- Очереди сообщений
-
Smoke тесты для критичного функционала
ЖЕЛАТЕЛЬНО: 4. E2E тесты для основных user journeys (но не всех) 5. Security тесты для чувствительных операций 6. Performance тесты для критичных операций
Рекомендуемое покрытие
- Code Coverage: минимум 80%, идеально 90%+
- Critical path coverage: 100%
- Branch coverage: 70%+
Best Practices
1. TDD (Test-Driven Development)
RED → GREEN → REFACTOR
Сначала пиши тест, потом код
2. AAA паттерн
@Test
public void testSomething() {
// Arrange: подготовь данные
User user = new User("john");
// Act: выполни действие
boolean isValid = user.validate();
// Assert: проверь результат
assertTrue(isValid);
}
3. Один assert на тест (когда возможно)
- Упрощает понимание
- Быстрее найти проблему
- Иногда несколько asserts OK
4. Meaningful names
// Плохо
@Test
public void test1() { }
// Хорошо
@Test
public void shouldThrowExceptionWhenUserIsNull() { }
5. Не тестируй внутреннее состояние (state-based)
// Плохо: тестируешь внутреннее состояние
User user = new User();
user.setName("john");
assertEquals("john", user.getName());
// Хорошо: тестируешь поведение
User user = new User();
assertTrue(user.isValid());
Правильная стратегия тестирования — это баланс между скоростью разработки, уверенностью в коде и стоимостью поддержки тестов.