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

Покрываешь ли код Unit тестами

1.0 Junior🔥 11 комментариев
#Soft Skills и карьера

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

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

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

Покрываешь ли код Unit тестами

Ответ: ДА, абсолютно. Unit тесты — это фундамент качественной разработки. Без них невозможно писать maintainable код.

Почему Unit тесты критичны

1. Уверенность в коде

Kогда у тебя есть хорошие тесты:

  • ✅ Можешь рефакторить без страха что-то сломать
  • ✅ Видишь регрессии сразу при коммите
  • ✅ Новые разработчики могут понять код через тесты

2. Документация

Тесты показывают, как использовать класс:

// Тест = документация
@Test
public void shouldReturnUserWhenIdExists() {
    // User service должно вернуть пользователя по ID
    UserService service = new UserService();
    User user = service.getUserById(123L);
    
    assertThat(user).isNotNull();
    assertThat(user.getId()).isEqualTo(123L);
}

3. Раннее обнаружение ошибок

Тесты ловят ошибки во время разработки, а не на продакшене.

Структура Unit тестов в Java

1. Фреймворки

  • JUnit 5 (JUnit Jupiter) — стандарт
  • TestNG — альтернатива
  • Spock — для groovy (более выразительно)
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;

public class UserServiceTest {
    
    @Test
    public void testGetUserById() {
        // Arrange
        UserService service = new UserService();
        
        // Act
        User user = service.getUserById(1L);
        
        // Assert
        assertThat(user.getName()).isEqualTo("John");
    }
}

2. Библиотеки для assertions

  • AssertJ — очень читаемо

    assertThat(user).isNotNull().hasFieldOrPropertyWithValue("age", 25);
    
  • Hamcrest — стиль matcher'ов

    assertThat(user, hasProperty("name", equalTo("John")));
    
  • Junit assertions — встроенные

    assertEquals("John", user.getName());
    

3. Моки (Mockito)

Для изоляции тестируемого кода от зависимостей:

import org.mockito.Mock;
import org.mockito.InjectMocks;

public class OrderServiceTest {
    
    @Mock
    private UserRepository userRepository;
    
    @InjectMocks
    private OrderService orderService;
    
    @Test
    public void shouldCreateOrder() {
        // Mock возвращает fake пользователя
        User mockUser = new User(1L, "John");
        when(userRepository.findById(1L)).thenReturn(mockUser);
        
        // Тестируем OrderService в изоляции
        Order order = orderService.createOrder(1L, "Product");
        
        assertThat(order.getUserId()).isEqualTo(1L);
        verify(userRepository).findById(1L); // Проверяем, что был вызван
    }
}

AAA паттерн (Arrange, Act, Assert)

@Test
public void calculateDiscount() {
    // Arrange: подготавливаем данные
    Product product = new Product("Laptop", 1000.0);
    Customer customer = new Customer("VIP");
    PricingService pricingService = new PricingService();
    
    // Act: вызываем функцию
    double discountedPrice = pricingService.calculatePrice(product, customer);
    
    // Assert: проверяем результат
    assertThat(discountedPrice).isEqualTo(900.0); // VIP скидка 10%
}

Типы Unit тестов

1. Тесты успешного пути (Happy Path)

@Test
public void shouldValidateEmail() {
    EmailValidator validator = new EmailValidator();
    boolean isValid = validator.validate("user@example.com");
    assertThat(isValid).isTrue();
}

2. Тесты граничных случаев (Edge Cases)

@Test
public void shouldHandleEmptyString() {
    EmailValidator validator = new EmailValidator();
    boolean isValid = validator.validate("");
    assertThat(isValid).isFalse();
}

@Test
public void shouldHandleNullInput() {
    EmailValidator validator = new EmailValidator();
    assertThrows(NullPointerException.class, () -> {
        validator.validate(null);
    });
}

3. Тесты исключений (Exception Testing)

@Test
public void shouldThrowExceptionForInvalidId() {
    UserService service = new UserService();
    
    assertThrows(IllegalArgumentException.class, () -> {
        service.getUserById(-1L);
    });
}

@Test
public void shouldThrowWithCorrectMessage() {
    UserService service = new UserService();
    
    Exception exception = assertThrows(IllegalArgumentException.class, () -> {
        service.getUserById(-1L);
    });
    
    assertThat(exception.getMessage()).contains("ID must be positive");
}

Параметризованные тесты (Parameterized Tests)

@ParameterizedTest
@ValueSource(strings = { "user@gmail.com", "admin@example.org", "test@domain.io" })
public void shouldValidateMultipleEmails(String email) {
    EmailValidator validator = new EmailValidator();
    assertThat(validator.validate(email)).isTrue();
}

@ParameterizedTest
@CsvSource({
    "user@gmail.com, true",
    "invalid-email, false",
    "another@test.com, true"
})
public void testEmailWithData(String email, boolean expected) {
    EmailValidator validator = new EmailValidator();
    assertThat(validator.validate(email)).isEqualTo(expected);
}

Coverage рекомендации

Хороший покрытие:

  • Бизнес-логика: 80-90% coverage
  • Контроллеры: 70-80% coverage
  • Конфигурация: 0-10% coverage (не нужно тестировать Spring configs)
  • Getters/Setters: 0-50% coverage (нет смысла)

Проверка coverage:

<!-- pom.xml -->
<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.8.8</version>
    <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>
</plugin>

Запуск coverage:

mvn clean test jacoco:report
# Отчёт в target/site/jacoco/index.html

Реальный пример: User Service

public class UserService {
    private UserRepository userRepository;
    private EmailValidator emailValidator;
    
    public UserService(UserRepository repo, EmailValidator validator) {
        this.userRepository = repo;
        this.emailValidator = validator;
    }
    
    public User createUser(String email, String name) {
        if (!emailValidator.validate(email)) {
            throw new IllegalArgumentException("Invalid email");
        }
        
        if (userRepository.findByEmail(email) != null) {
            throw new IllegalArgumentException("User already exists");
        }
        
        User user = new User(email, name);
        return userRepository.save(user);
    }
}

Тесты для UserService:

public class UserServiceTest {
    
    private UserService userService;
    private UserRepository userRepository;
    private EmailValidator emailValidator;
    
    @BeforeEach
    public void setUp() {
        userRepository = mock(UserRepository.class);
        emailValidator = mock(EmailValidator.class);
        userService = new UserService(userRepository, emailValidator);
    }
    
    // Happy path
    @Test
    public void shouldCreateUserSuccessfully() {
        when(emailValidator.validate("john@example.com")).thenReturn(true);
        when(userRepository.findByEmail("john@example.com")).thenReturn(null);
        when(userRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
        
        User user = userService.createUser("john@example.com", "John");
        
        assertThat(user.getEmail()).isEqualTo("john@example.com");
        verify(userRepository).save(any());
    }
    
    // Invalid email
    @Test
    public void shouldThrowForInvalidEmail() {
        when(emailValidator.validate("invalid")).thenReturn(false);
        
        assertThrows(IllegalArgumentException.class, () -> {
            userService.createUser("invalid", "John");
        });
    }
    
    // User already exists
    @Test
    public void shouldThrowIfUserAlreadyExists() {
        when(emailValidator.validate("john@example.com")).thenReturn(true);
        when(userRepository.findByEmail("john@example.com")).thenReturn(new User());
        
        assertThrows(IllegalArgumentException.class, () -> {
            userService.createUser("john@example.com", "John");
        });
    }
}

Best Practices для Unit тестов

Делай:

  • Один assert (или логически связанные) в тесте
  • Дай описательные имена: shouldReturnUserWhenIdExists
  • Изолируй тесты (no shared state)
  • Используй fixtures и builders
  • Тесты должны быть быстрыми (< 100ms)

Не делай:

  • Тесты, которые зависят друг от друга
  • Sleep() в тестах
  • Тестирование приватных методов (они должны тестироваться через public)
  • Mock'ирование всего подряд
  • Огромные arrange блоки (значит, класс сложный)

Вывод

ДА, я покрываю код Unit тестами, потому что:

  • ✅ Гарантия качества
  • ✅ Документация для других разработчиков
  • ✅ Раннее обнаружение ошибок
  • ✅ Уверенность при рефакторинге
  • ✅ Снижение затрат на отладку в production