Как организуешь процесс тестирования для уменьшения количества ошибок
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Организация процесса тестирования для снижения ошибок
Качественное тестирование - это основа надёжного программного обеспечения. За 10+ лет опыта я выработал комплексный подход к организации процесса.
1. Пирамида тестирования (Test Pyramid)
E2E tests
Integration tests
Unit tests
Правильное соотношение:
- Unit тесты (70%) - быстрые, доступные, легко отлаживаются
- Integration тесты (20%) - проверяют взаимодействие компонентов
- E2E тесты (10%) - проверяют полные user scenarios
// Unit test
@Test
void testCalculateDiscount() {
double result = PricingService.calculateDiscount(100, 0.1);
assertEquals(90.0, result);
}
// Integration test
@Test
void testOrderProcessing() {
Order order = createOrder();
PaymentService paymentService = new PaymentService(mockGateway);
OrderService orderService = new OrderService(paymentService, mockDB);
boolean success = orderService.processOrder(order);
assertTrue(success);
verify(mockDB).saveOrder(order);
}
// E2E test
@Test
void testUserCheckoutFlow() {
selenium.login("user@example.com");
selenium.addProductToCart("Product1");
selenium.checkout();
assertEquals("Order confirmed", selenium.getPageText());
}
2. TDD (Test-Driven Development)
Написание тестов ПЕРЕД кодом:
// Шаг 1: RED - пишем тест, он падает
@Test
void testUserRegistration() {
UserService service = new UserService();
User user = service.register("john@example.com", "password123");
assertNotNull(user.getId());
assertEquals("john@example.com", user.getEmail());
assertTrue(user.isActive());
}
// Шаг 2: GREEN - пишем минимум кода для прохождения
public class UserService {
public User register(String email, String password) {
User user = new User();
user.setId(UUID.randomUUID());
user.setEmail(email);
user.setActive(true);
return user;
}
}
// Шаг 3: REFACTOR - улучшаем код
public class UserService {
private UserRepository repository;
private PasswordEncoder encoder;
public User register(String email, String password) {
if (isEmailTaken(email)) {
throw new EmailAlreadyTakenException();
}
User user = new User();
user.setEmail(email);
user.setPasswordHash(encoder.encode(password));
user.setActive(true);
return repository.save(user);
}
}
3. Покрытие (Coverage)
Цель: минимум 80-90% покрытия кода
# Maven
mvn clean test jacoco:report
# Gradle
./gradlew test jacocoTestReport
<!-- pom.xml -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.8</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>jacoco-check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>PACKAGE</element>
<excludes>
<exclude>*Test</exclude>
</excludes>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.80</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
4. Параметризованные тесты
@ParameterizedTest
@CsvSource({
"100, 0.1, 90.0",
"200, 0.2, 160.0",
"50, 0.5, 25.0"
})
void testDiscountCalculation(double price, double discount, double expected) {
double result = PricingService.calculateDiscount(price, discount);
assertEquals(expected, result);
}
5. Mocking и isolation
class OrderServiceTest {
@Mock
PaymentGateway paymentGateway;
@Mock
OrderRepository orderRepository;
@InjectMocks
OrderService orderService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
void testPaymentFailureHandling() {
// Arrange
when(paymentGateway.process(any())).thenThrow(PaymentException.class);
Order order = new Order(100.0);
// Act & Assert
assertThrows(PaymentException.class, () -> orderService.process(order));
verify(orderRepository, never()).save(order);
}
}
6. Непрерывная интеграция (CI/CD)
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v2
- name: Run tests
run: mvn clean test
- name: Upload coverage
uses: codecov/codecov-action@v1
7. Правила написания хороших тестов
AAA Pattern (Arrange-Act-Assert)
@Test
void testTransferMoney() {
// Arrange
Account source = new Account("John", 1000);
Account target = new Account("Jane", 500);
TransferService service = new TransferService();
// Act
service.transfer(source, target, 200);
// Assert
assertEquals(800, source.getBalance());
assertEquals(700, target.getBalance());
}
Given-When-Then (BDD)
@Test
void givenValidCredentials_whenUserLogins_thenSessionIsCreated() {
// Given
String email = "user@example.com";
String password = "correct_password";
// When
User user = authService.login(email, password);
// Then
assertNotNull(user);
assertTrue(sessionManager.hasActiveSession(user.getId()));
}
8. Edge Cases и негативные тесты
@ParameterizedTest
@ValueSource(strings = {"null", "", " ", "invalid@mail", "user@"})
void testEmailValidation_Invalid(String email) {
assertThrows(InvalidEmailException.class, () -> User.create(email));
}
@Test
void testDivisionByZero() {
assertThrows(ArithmeticException.class, () -> calculator.divide(10, 0));
}
@Test
void testNullPointer() {
assertThrows(NullPointerException.class, () -> service.process(null));
}
9. Fixtures и test data builders
public class UserBuilder {
private String email = "default@example.com";
private String name = "Default User";
private boolean active = true;
public UserBuilder withEmail(String email) {
this.email = email;
return this;
}
public UserBuilder withName(String name) {
this.name = name;
return this;
}
public User build() {
return new User(email, name, active);
}
}
// Использование
@Test
void testActiveUsers() {
User user1 = new UserBuilder().withEmail("john@example.com").build();
User user2 = new UserBuilder().withEmail("jane@example.com").withActive(false).build();
}
10. Checklist для снижения ошибок
✓ Писать тесты перед кодом (TDD) ✓ Минимум 80% покрытия кода ✓ Тестировать edge cases и граничные условия ✓ Использовать mocking для isolation ✓ Параметризовать похожие тесты ✓ CI/CD pipeline для автоматических проверок ✓ Code review перед merge ✓ Регулярный рефакторинг тестов ✓ Документировать complex test scenarios ✓ Использовать descriptive names для тестов
Итоги
Организация процесса тестирования требует дисциплины и системности. Начиная с TDD, поддерживая высокое покрытие и используя правильные инструменты, мы можем значительно снизить количество дефектов и повысить надёжность системы.