В .NET обработка ошибок и тестирование — это во многом «истории фреймворков»: исключения встроены в CLR и пронизывают весь BCL, а тесты пишутся на внешних xUnit/NUnit/MSTest с Moq и FluentAssertions поверх. В Go оба подхода устроены принципиально иначе. Ошибки здесь — это обычные значения, которые функция возвращает и которые вы явно проверяете; никакого скрытого «всплытия» через стек. А тестирование — это встроенный в язык и тулчейн инструмент (testing + go test), дополненный кодогенерацией моков, а не рантайм-магией динамических прокси.
Этот раздел — финальный мостик от .NET-привычек «бросить исключение и поймать выше» и «подключить тест-фреймворк» к идиомам Go: явный возврат error, оборачивание через %w, table-driven тесты, моки на интерфейсах и интеграционные тесты на реальных зависимостях через Testcontainers.
- Понять, почему в Go ошибки — это значения (
error— интерфейс), а не исключения, и привыкнуть к идиомеif err != nil. - Создавать и оборачивать ошибки (
errors.New,fmt.Errorfс%w) и распаковывать цепочки черезerrors.Is,errors.As,errors.Unwrap,errors.Join. - Различать sentinel-ошибки и кастомные типы ошибок и понимать, когда какой инструмент уместен.
- Применять
panic/recoverстрого по назначению — для действительно исключительных ситуаций и на границах, а не вместо ошибок. - Знать точную механику
defer/panic/recover: когда вычисляются аргументыdefer, ловушку defer-в-цикле, изменение именованного возврата, правилаrecoverи конвертацию panic→error. - Писать тесты на встроенном
testing: подтесты (t.Run), параллелизм, table-driven как главную идиому, покрытие, бенчмарки, Example-тесты и фаззинг. - Строить моки на интерфейсах (вручную и через кодогенерацию
mockery/gomock) и поднимать реальные зависимости в Docker черезtestcontainers-go. - Осознанно сопоставить .NET-стек (исключения, xUnit, Moq) с Go-подходом и видеть плюсы и минусы каждого.
- Обработка ошибок — ошибки как значения, интерфейс
error,if err != nil, оборачивание через%w,errors.Is/As/Unwrap/Join, sentinel-ошибки и кастомные типы, введение вpanic/recoverиdefer. - defer, panic и recover — механика и ловушки: когда вычисляются аргументы
defer, LIFO, defer-в-цикле, изменение именованного возврата; что делаетpanic(раскрутка стека, стектрейс, выход 2); точные правилаrecoverи паттерн panic→error. - Тестирование — встроенный
testing, файлы_test.go,t.Run/t.Parallel/t.Helper/t.Cleanup, table-driven tests, покрытие, бенчмарки, Example-тесты, фаззинг иtestify. - Моки и Testcontainers — моки на интерфейсах, ручные моки vs кодогенерация (
mockery,gomock), интеграционные тесты на реальных зависимостях черезtestcontainers-go. - Сравнение с .NET — консолидированный мостик: исключения против значений-ошибок, тест-фреймворки против встроенного
testing, динамические моки против кодогенерации.
Держите таблицу под рукой при чтении раздела. Детали каждого сопоставления разбираются в соответствующих главах.
| Концепция в .NET | Аналог в Go | Ключевое различие |
|---|---|---|
throw / возбуждение исключения |
return ..., err |
Ошибка — обычное возвращаемое значение, а не аварийное всплытие по стеку |
try/catch |
if err != nil { ... } |
Поток обработки виден в коде; нет скрытых точек выхода |
finally / using |
defer |
defer отрабатывает при выходе из функции, в т.ч. при panic |
InnerException / new Ex(..., inner) |
fmt.Errorf("...: %w", err) |
Цепочка строится глаголом %w, а не свойством |
catch (SpecificException e) |
errors.As(err, &target) |
Извлечение конкретного типа из цепочки |
| Сравнение с конкретным исключением | errors.Is(err, ErrSentinel) |
Сравнение с заранее объявленным значением-маркером |
AggregateException |
errors.Join(err1, err2) (Go 1.20) |
Объединение нескольких ошибок в одну |
throw; (рефолд) / throw new(...) |
panic(...) |
panic — только для невосстановимых ситуаций |
catch на верхнем уровне |
recover() в defer |
Восстановление строго на границе (например, middleware) |
| xUnit/NUnit/MSTest | встроенный пакет testing |
Тест-раннер — часть тулчейна (go test), не внешний пакет |
[Fact] / метод-тест |
func TestXxx(t *testing.T) |
Конвенция имени вместо атрибута |
[Theory] + [InlineData] |
table-driven tests | Слайс кейсов + цикл с t.Run, а не атрибуты |
[Theory] + [MemberData] |
слайс структур-кейсов | Данные — обычный Go-код |
FluentAssertions / Assert.* |
t.Error/t.Fatal или testify |
stdlib даёт минимум; testify — популярный сторонний слой |
| Moq / NSubstitute (рантайм-прокси) | mockery / gomock (кодогенерация) |
Мок генерируется на этапе сборки из интерфейса |
BenchmarkDotNet |
func BenchmarkXxx(b *testing.B) |
Бенчмарки встроены в go test |
| Testcontainers for .NET | testcontainers-go |
Тот же проект, тот же подход — реальные зависимости в Docker |