Ответ
Гнаться за 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 и использование статических анализаторов.
Ответ 18+ 🔞
А, ну вот, опять про эти 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.NoNoError(t, err)
}
})
}
mockRepo.AssertExpectations(t)
}
Вывод, блядь: Покрытие тестами — это не священная корова, а инструмент. Качественные тесты на важных местах, нормальный код-ревью и статические анализаторы дадут тебе куда больше уверенности, чем погоня за циферкой в 100%, которая, как правило, хуйня полная и доверия ебать ноль.