Ответ
Гнаться за 100% покрытием кода тестами — часто неэффективно и экономически нецелесообразно. Оптимальный подход — это риск-ориентированное тестирование, где усилия концентрируются на наиболее важных частях системы.
Мой подход:
Критически важная логика (70-90% покрытия):
- Бизнес-логика, обработка платежей, алгоритмы, работа с аутентификацией и авторизацией.
- Здесь высокая цена ошибки, поэтому требуется максимальное покрытие всех возможных сценариев, включая пограничные случаи.
API-контракты и интеграции (60-80%):
- Тестирование эндпоинтов: проверка кодов ответа, структуры JSON, обработки некорректных входных данных.
- Важно гарантировать, что ваш сервис корректно взаимодействует с другими системами.
Вспомогательный код и утилиты (30-50%):
- Простые функции-форматтеры, хелперы, которые легко проверить визуально и риск ошибки в которых минимален.
Пример структуры тестов в Go:
Для проверки логики сервиса мы используем табличные тесты, которые позволяют легко покрыть множество сценариев.
// user_service.go
package users
// IsAdult проверяет, является ли пользователь совершеннолетним
func (s *Service) IsAdult(userID int) (bool, error) {
user, err := s.repo.GetUserByID(userID)
if err != nil {
return false, err // Например, ErrUserNotFound
}
return user.Age >= 18, nil
}
// user_service_test.go
package users
func TestIsAdult(t *testing.T) {
mockRepo := &MockUserRepository{}
testCases := []struct {
name string
userID int
mockAge int
mockErr error
expected bool
expectedErr bool
}{
{"Совершеннолетний", 1, 25, nil, true, false},
{"Несовершеннолетний", 2, 17, nil, false, false},
{"Граничный случай (18 лет)", 3, 18, nil, true, false},
{"Пользователь не найден", 4, 0, ErrUserNotFound, false, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Настраиваем мок для каждого кейса
mockRepo.On("GetUserByID", tc.userID).Return(User{Age: tc.mockAge}, tc.mockErr).Once()
service := NewService(mockRepo)
isAdult, err := service.IsAdult(tc.userID)
assert.Equal(t, tc.expected, isAdult)
if tc.expectedErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
mockRepo.AssertExpectations(t)
}
Вывод: Покрытие тестами — это инструмент для повышения уверенности в коде, а не самоцель. Важнее покрытия — качество самих тестов, хороший code review и использование статических анализаторов.