Содержание урока · 13 разделов
- Пирамида тестов go-kit: что и чем проверяем
- Тест endpoint: это просто функция
- Тест middleware: fake-next на двадцать строк
- Тест декоратора: fake-сервис вместо реализации
- Тест кодека: httptest.NewRecorder без сети
- Интеграционный тест: httptest.NewServer
- Матрица: этаж → тест → инструмент
- Чего не тестировать: библиотека и каркас
- Дедлайны и сон: тесты без flaky
- Что дальше
- Песочница урока
- Коротко: что запомнить
- Вопросы и ответы
Пирамида тестов go-kit: что и чем проверяем#
У go-сервиса четыре тестируемых этажа, и скорость теста обратно пропорциональна его ценности в рантайме: чем выше этаж, тем реже и дороже его проверяют. В основании — сервис: table-driven тесты StringService из урока 03, микросекунды на прогон, десятки кейсов на метод. Второй этаж — endpoint'ы: те же table-driven, но вызывается makeUppercaseEndpoint, и проверяется шов адаптера: type assertion, маппинг ошибок в Err-поле, Failer. Третий — обёртки обоих этажей из уроков 14 и 15: endpoint-middleware проверяются с fake-next, декораторы — с fake-сервисом; изоляция полная, никакого транспорта. Четвёртый — транспорт и кодеки: kithttp.NewServer гоняется на httptest.NewRecorder без сети, интеграционный сценарий — на httptest.NewServer с реальным HTTP-циклом. Тестирование go kit дорожает по этажам: сервис — миллисекунды, endpoint — миллисекунды, middleware — единицы миллисекунд, транспорт — десятки. Держи пропорцию пирамиды: много быстрых тестов внизу, горстка интеграционных наверху; end-to-end прогон с curl оставь деплою. Ключ к этажу — простая мысль: в go-kit почти всё — функции, а функции тестируются вызовом.
Тест endpoint: это просто функция#
Начни с главного приёма курса. Endpoint — сигнатура func(ctx, request) (response, error) — зовётся в тесте напрямую: ep := makeUppercaseEndpoint(svc); resp, err := ep(context.Background(), UppercaseRequest{S: "go"}). Никакого декодера, никакого HTTP: таблица кейсов с входной строкой, ожидаемым V и ожидаемой ошибкой — почти копия table-driven теста сервиса из урока 03, только проверяется ещё и адаптер. Что здесь проверяется, а что нет. Проверяется: assertion request-структуры, вызов метода сервиса, упаковка ответа, перенос ErrEmpty в Err-поле через Failer — весь шов из урока 02. Не проверяется: JSON, статусы, заголовки — это транспорт, его тест ниже. Вторая причина endpoint тестов — обёртки: makeUppercaseEndpoint(svc) принимает декорированный сервис, поэтому один и тот же тест можно прогнать на чистом сервисе и на матрёшке с кэшем из урока 15 — и это уже тест интеграции этажей, всё ещё без сети. Ошибки в тестах проверяются через errors.Is на sentinel: строковое сравнение err.Error() ломается при первой же обёртке fmt.Errorf с %w из урока 14.
Table-driven тест endpoint
GOfunc TestUppercaseEndpoint(t *testing.T) {
ep := makeUppercaseEndpoint(stringService{})
cases := []struct {
name string
in string
want string
wantErr error
}{
{"обычная", "go", "GO", nil},
{"пустая", "", "", ErrEmpty},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
resp, err := ep(context.Background(), UppercaseRequest{S: c.in})
if c.wantErr != nil {
if !errors.Is(err, c.wantErr) {
t.Fatalf("err = %v, хочу %v", err, c.wantErr)
}
return
}
if err != nil {
t.Fatalf("неожиданная ошибка: %v", err)
}
if resp.(UppercaseResponse).V != c.want {
t.Fatalf("V = %v, хочу %v", resp.(UppercaseResponse).V, c.want)
}
})
}
}Три проверки — на ошибку, на отсутствие ошибки и на значение: пропуск любой из веток превращает тест в решето. errors.Is на ErrEmpty — канон: sentinel остаётся различимым сквозь Failer и любые будущие обёртки ошибок. Запрос строится литералом структуры — тест не знает про JSON и умеет только сигнатуру endpoint'а.
Тест middleware: fake-next на двадцать строк#
Endpoint-middleware тестируется подстановкой next: обёртка — функция над функцией, значит в тест next ставится стаб, поведение которого ты контролируешь полностью. Два шаблона. Первый — сигнальный next: стаб-функция ставит флаг «меня вызвали» или паникует по требованию — им проверяется, что лимитер не пропустил вызов, что timeout отменил контекст, что recovery превратил панику в ошибку. Второй — записывающий next: стаб фиксирует контекст и аргументы, им проверяются обёртки-трансформеры вроде errwrap из урока 14. Отдельная прелесть — логгер-стаб: реализация log.Logger из урока 06 — один метод Log(keyvals ...interface{}) error; двадцать строк собирают логгер, который копит пары в срез под мьютексом, и middleware логирования можно проверять дословно. Мокается только next и зависимости, потому что именно они интерфейсы; сами endpoint middleware — конкретные функции, и тестируются как функции. Так и работает тестирование go kit: мокать то, что твоя обёртка вызывает, а не то, что она есть.
Тест recovery: next паникует
GOfunc TestRecoveryMiddleware(t *testing.T) {
logger := newTestLogger() // стаб из хелперов ниже
ep := recoveryMiddleware(logger)(func(ctx context.Context, req interface{}) (interface{}, error) {
panic("бум")
})
resp, err := ep(context.Background(), UppercaseRequest{S: "go"})
if err == nil {
t.Fatal("хотели ошибку, получили nil")
}
if resp != nil {
t.Fatalf("ответ должен быть nil, получен %v", resp)
}
if !strings.Contains(logger.text(), "panic: бум") {
t.Fatalf("в логе нет паники: %s", logger.text())
}
}
// next без паники — проверка прозрачности:
func TestRecoveryHappyPath(t *testing.T) {
ep := recoveryMiddleware(newTestLogger())(okEndpoint) // возвращает ("OK", nil)
resp, err := ep(context.Background(), UppercaseRequest{S: "go"})
if err != nil || resp.(UppercaseResponse).V != "OK" {
t.Fatalf("здоровый путь испорчен: %v %v", resp, err)
}
}Первый тест — негативный сценарий: паника должна стать err, ответ — nil, лог — содержать значение паники. Второй — обязательный парный: обёртка не должна ломать здоровый путь, и два теста всегда пишутся рядом. okEndpoint и newTestLogger — хелперы пакета тестов; заметь, что стаб-next — обычный литерал функции, типы совпадают с endpoint.Endpoint без объявлений.
Логгер-стаб: log.Logger за 15 строк
GOtype testLogger struct {
mu sync.Mutex
kvs [][]interface{}
}
func (l *testLogger) Log(keyvals ...interface{}) error {
l.mu.Lock()
defer l.mu.Unlock()
l.kvs = append(l.kvs, keyvals)
return nil
}
func (l *testLogger) text() string {
l.mu.Lock()
defer l.mu.Unlock()
parts := make([]string, 0, len(l.kvs)*2)
for _, kv := range l.kvs {
parts = append(parts, fmt.Sprint(kv...))
}
return strings.Join(parts, " ")
}Интерфейс kitlog — один метод: интерфейсная сегрегация в чистом виде, поэтому подстановка такая дешёвая. Мьютекс обязателен: middleware логирует из горутин параллельных запросов, и тест, читающий лог во время записи, будет гнать data race — не забудь флаг -race в CI. Тот же стаб годится для endpoint- и service-логгеров: у них одинаковый контракт log.Logger.
Как поймать пролетевший вызов
Как убедиться, что NewErroringLimiter при пустом бакете не вызвал endpoint вовсе?
Тест декоратора: fake-сервис вместо реализации#
Service-middleware из урока 15 тестируется зеркально: вместо fake-next — fake-сервис, реализующий StringService. Стаб-реализация считает вызовы метода (атомарный счётчик или канал-лог), возвращает заранее заготовленные ответы и умеет ломаться по сценарию — вернуть ErrEmpty, зависнуть на канале. Классический кейс кэша: fake-сервис, поверх которого cachingMiddleware; первый вызов инкрементирует счётчик base, второй — нет, при этом оба вернули одно значение, а sentinel-ошибка не закэшировалась. Это тест целого контракта из урока 15 — три утверждения из одной таблицы. Заметка об интеграции этажей: матрёшку для теста собирай той же applyMiddleware, что и prod-код — если тест соберёт декораторы руками, а main через срез, расхождение однажды проскочит мимо тестов. И ещё: fake-сервис — это не «мок-библиотека»: для интерфейса из двух методов ручной стаб дешевле и нагляднее кодогенерации; gomock и компания уместны, когда интерфейсов десятки и обновлять стабы руками дороже, чем держать тулчейн.
Кэш на fake-сервисе: три утверждения
GOtype fakeService struct {
calls int32
}
func (f *fakeService) Uppercase(ctx context.Context, s string) (string, error) {
atomic.AddInt32(&f.calls, 1)
return strings.ToUpper(s), nil
}
func (f *fakeService) Count(ctx context.Context, s string) int { return len(s) }
func TestCachingMiddleware(t *testing.T) {
base := &fakeService{}
svc := NewCachingMiddleware(map[string]string{})(base)
v1, err1 := svc.Uppercase(context.Background(), "go")
v2, err2 := svc.Uppercase(context.Background(), "go")
if v1 != "GO" || v2 != "GO" || err1 != nil || err2 != nil {
t.Fatalf("значения испорчены: %q %q %v %v", v1, v2, err1, err2)
}
if n := atomic.LoadInt32(&base.calls); n != 1 {
t.Fatalf("base вызван %d раз, хочу 1", n)
}
}Атомарный счётчик вместо флага: будущие параллельные тесты кэша не сломают стаб. Средняя проверка — самая важная: именно «base вызван один раз» отличает кэш от простого проброса. Тест живёт в микросекундах: ни сети, ни портов, ни сна — обычные вызовы функций по интерфейсу.
Тест кодека: httptest.NewRecorder без сети#
Транспортный этаж проверяется двумя уровнями, и первый — без сети вообще. kithttp.NewServer возвращает http.Handler, а httptest.NewRecorder — фейковый ResponseWriter, который собирает статус и тело в память. Собери сервер, сформируй httptest.NewRequest, вызови server.ServeHTTP(w, r) — и весь HTTP-цикл прошёл, не открыв ни порта. Что здесь проверяется: декодер (сломанный JSON должен дать честный 400 из урока 05), энкодер (Content-Type application/json), error encoder (ErrEmpty → 400 с телом {"error":"empty string"}, кривая ошибка → 500), Failer-контракт. Мутации канона: этот тест — единственное место, где транспортные типы легальны в тесте; всё остальное про байты живёт в кодеках. Скорость — микросекунды на кейс, поэтому транспортная таблица может быть такой же жирной, как сервисная. Второй уровень — интеграция: httptest.NewServer поднимает настоящий listener на loopback; его цена — десятки миллисекунд, его ценность — проверка полного цикла: json.Marshal, заголовки, конверт ошибки, поведение клиента. Один-два таких теста на сервис достаточно.
Recorder: статус и тело в памяти
GOfunc TestUppercaseHTTP(t *testing.T) {
ep := makeUppercaseEndpoint(stringService{})
server := kithttp.NewServer(
ep,
decodeUppercaseRequest, // из урока 04
encodeResponse, // с Failer и encodeError из урока 05
)
body := strings.NewReader("{"s":"go"}")
req := httptest.NewRequest(http.MethodPost, "/uppercase", body)
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
server.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("статус %d, хочу 200", w.Code)
}
if got := w.Body.String(); !strings.Contains(got, "GO") {
t.Fatalf("тело %q не содержит GO", got)
}
}Три действия — собрать handler, сконструировать запрос, вызвать ServeHTTP — и никаких слушателей. Рекордер позволяет проверить всё, что реально пишет энкодер: статус, тело, заголовки через w.Header().Get. Table-driven продолжается и здесь: вторая строка таблицы — пустой s, ожидание 400 и substring empty string; третья — кривой JSON, ожидание 400 из ветки сломанного декодера.
Что упадёт в Recorder
Прочитайте код и выберите, что он выведет:
req := httptest.NewRequest(http.MethodPost, "/uppercase",
strings.NewReader("{"s":""}"))
w := httptest.NewRecorder()
server.ServeHTTP(w, req)
// encodeError из урока 05: errors.Is(err, ErrEmpty) -> 400
fmt.Println(w.Code, w.Body.String())Интеграционный тест: httptest.NewServer#
Второй уровень транспортных тестов поднимает настоящий HTTP-сервер: httptest.NewServer(server) слушает loopback и отдаёт базовый URL; тест идёт обычным http.Client — как реальный потребитель. Ценность уровня: он проверяет стыки, которые Recorder не видит — сериализацию json.Marshal в энкодере, заголовки Content-Type обеих сторон, обработку редиректов и таймаутов клиентом, а главное — форму конверта ошибки глазами клиента: тест декодирует ответ в структуру и сверяет поле error, а не substring тела. Дедлайн теста — context.WithTimeout на запрос: зависший сервер не повесит CI, а упадёт с понятным сообщением за две секунды. Заметь честную деталь: этот тест ходит по сети — localhost, но сеть; он медленнее и чуть более хрупкий, поэтому его в одном экземпляре на критичный метод, а не на каждый кейс таблицы. Мок-транспорт, о котором спрашивают чаще всего, — это и есть httptest.NewServer: go-kit не требует мокать kithttp — сервер готов, а мокаешь ты свой сервис под ним.
Интеграция: клиент против httptest.NewServer
GOfunc TestUppercaseIntegration(t *testing.T) {
ep := makeUppercaseEndpoint(stringService{})
handler := kithttp.NewServer(ep, decodeUppercaseRequest, encodeResponse)
ts := httptest.NewServer(handler)
defer ts.Close()
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, http.MethodPost,
ts.URL+"/uppercase", strings.NewReader("{"s":"go"}"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("запрос не прошёл: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("статус %d", resp.StatusCode)
}
var got UppercaseResponse
if err := json.NewDecoder(resp.Body).Decode(&got); err != nil {
t.Fatalf("ответ не декодируется: %v", err)
}
if got.V != "GO" {
t.Fatalf("V = %q", got.V)
}
}Декодирование в request/response-структуру — проверка контракта глазами клиента: то же поле V, тот же json-тег из урока 02. defer ts.Close() обязателен — иначе тест слушатель живёт до конца процесса и порт пачкается. Клиент с контекстом и таймаутом — привычка из урока 19: зависший тест хуже упавшего, потому что подвешивает весь прогон.
Матрица инструментов
Тебе нужно проверить, что лимитный отказ доезжает до клиента как 429 с Retry-After. Какой уровень теста выберешь?
Матрица: этаж → тест → инструмент#
| Этаж | Что проверяем | Инструмент | Скорость |
|---|---|---|---|
| Сервис (урок 03) | логика, sentinel-ошибки | table-driven, прямой вызов | микросекунды |
| Endpoint (урок 02) | адаптер, Failer, Err-поле | table-driven, вызов ep(ctx, req) | микросекунды |
| Endpoint-middleware (14) | перехват, порядок, ошибки | fake-next, логгер-стаб | миллисекунды |
| Service-middleware (15) | кэш, ретраи, счётчик вызовов | fake-сервис с атомарным счётчиком | миллисекунды |
| Кодеки (04-05) | статусы, тело, Content-Type | httptest.NewRecorder без сети | миллисекунды |
| Транспорт | полный HTTP-цикл, клиент | httptest.NewServer + http.Client | десятки мс |
Чего не тестировать: библиотека и каркас#
Обратная сторона пирамиды — куда тесты не нужны. Не тестируй go-kit: kithttp.NewServer гарантированно вызывает endpoint, это покрыто библиотечными тестами; твой Recorder-тест уже проверяет интеграцию с твоими кодеками — дублировать внутренности пакета бессмысленно. Не тестируй обёртки других людей в их же терминах: gobreaker проверял свои состояния, твоя зона ответственности — контракт ErrOpenState → 503 в твоём encodeError. Не пиши тесты на конфигурацию в main: сборку матрёшек проверяют типы компилятора и один интеграционный тест. И не гоняйся за процентом покрытия в тестах каркаса — гони гонку и детерминизм: go test -race -count=1 в CI обязателен для всего, что касается middleware из уроков 14-15, потому что замыкания исполняются параллельными горутинами, а мьютексы и атомарные счётчики проверяются только рантаймом с гонкой. Правило выбора цели теста простое: тестируй то, что написал ты, через контракт, который объявил ты, — библиотека остаётся чёрным ящиком, твой код — белым. Финальная строка CI-конфига: go-kit тесты гоняются с -race -count=1 — привычка, которая окупается с первого инцидента.
Чья зона ответственности
Команда спорит: писать ли тест на gobreaker, что после MaxRequests в half-open цепь пропускает новые попытки?
Дедлайны и сон: тесты без flaky#
Тесты, проверяющие время, — главный источник flakiness, и у go-kit-стека есть аккуратный выход. Не пиши time.Sleep в тестах: Sleep(100ms) на быстрой машине стреляет в 99% прогонов, а на CI в час пик — в 95%, и красные тесты перестают что-либо значить. Вместо этого сжимай время: timeout-обёртка из урока 14 тестируется с дедлайном в пять миллисекунд против fake-next, который блокируется на канале без записи; TestRecovery паникует мгновенно; retry-декоратор из урока 15 конфигурируется паузой в наносекунды — зависимость паузы приходит конструктором, как и всякая другая. Если тест должен дождаться события — дождись его блокирующим чтением канала, а не опросом со сном: стаб может отдавать chan struct{}, тест читает с context.WithTimeout. Дедлайн теста — всегда: t.Deadline или контекст на запрос; упавший тест лучше зависшего. И помни про -race: обёртки, кэши и логгер-стабы — многогорутинный код, гонка в тесте — баг в проде, который успел проголосовать.
Логгер-стаб и порядок записей
Прочитайте код и выберите, что он выведет:
logger := newTestLogger()
svc := NewLoggingMiddleware(logger)(stringService{})
svc.Uppercase(context.Background(), "go")
// Из predict урока 03: внутренний пишет первым.
// Теперь — endpoint-этаж:
ep := loggingMiddleware(logger)(makeUppercaseEndpoint(svc))
ep(context.Background(), UppercaseRequest{S: "go"})Собери Recorder-тест
Восстанови транспортный тест без сети: три пропуска.
Путь теста от базы пирамиды к вершине
Расставь шаги прогонки: от самого быстрого теста к самому дорогому.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Термины урока
time.Sleep — чек на flaky: на CI тайминги плавают, и тесты краснеют случайно, после чего команда привыкает перезапускать прогон — а это уже не тесты. Время в тестах сжимается до миллисекунд, события дожидаются чтением канала с дедлайном, пауза ретрая приходит зависимостью конструктора. Прогон всего пакета — с -race и -count=1: обёртки из уроков 14-15 исполняются горутинами параллельно, и только рантайм с гонкой ловит мьютекс-ошибки.
Что дальше#
Тестовая пирамида stringsvc стоит на всех четырёх этажах: сервис и endpoint — table-driven, обёртки — на стабах, транспорт — на httptest. Всё, что написано в уроках 02-15, теперь проверяемо изоляционно, и это последний аргумент в пользу слоистой архитектуры из урока 01-filosofiya: слой тестируется по контракту соседа, а не по контракту мира. Дальше карта курса ведёт в стройку: урок 17-struktura — структура go kit проекта (каталоги, DI, шаблон большого сервиса), затем конфигурация и graceful shutdown; обе темы опираются на матрёшку и endpoint-цепочку из уроков 14-15, которые ты теперь умеешь и писать, и тестировать. Путь до итогового проекта урока 25 — на странице курса; HTTP-декодер для тестов освежи в уроке 04-http-transport.
Урок требует модуля go-kit и установленного Go: в песочнице курса сторонние пакеты не исполняются, поэтому интерактивы построены на чтении кода. Локально: скопируй логгер-стаб и fake-сервис в stringsvc_test.go, прогони тесты урока с -race -count=1; добавь таблицу Recorder-кейсов (200, 400 на ErrEmpty, 400 на кривой JSON); удали один defer cancel() в интеграционном тесте и посмотри, как -race ловит утечку; включи в CI go test -race -count=1 и запиши время прогона пирамиды — оно должно остаться в секундах.
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- Пирамида тестов go-kit: сервис и endpoint — table-driven микросекунды, middleware — стабы, кодеки — Recorder, транспорт — httptest.NewServer; дорогое — наверху и редко.
- Endpoint-тест — прямой вызов ep(ctx, request): проверяется шов адаптера, Failer и перенос ErrEmpty; ошибки — только через errors.Is на sentinel.
- Middleware тестируется fake-next: сигнальный стаб с panic ловит пролетевший вызов, записывающий — проверяет трансформеры; логгер-стаб из одного метода Log даёт проверку текста логов.
- Декораторы тестируются fake-сервисом со счётчиком вызовов; матрёшку в тесте собирает та же applyMiddleware, что и prod-код.
- Recorder-тест прогоняет kithttp.NewServer без сети: статусы, тела и заголовки проверяются на httptest.NewRecorder; sentinel-ветки encodeError — обязательная таблица.
- Интеграция — httptest.NewServer с клиентом и дедлайном: полный HTTP-цикл и контракт глазами клиента, один-два теста на сервис.
- Не тестируй библиотеку и не спи в тестах: go-kit остаётся чёрным ящиком, время сжимается, события дожидаются каналом, CI гоняет -race -count=1.
Домашнее задание
Построй тестовую пирамиду stringsvc полностью. Первое: endpoint-тест makeUppercaseEndpoint из урока с тремя кейсами — обычная строка, пустая, строка из пробелов; проверь sentinel через errors.Is. Второе: логгер-стаб из урока и два теста recoveryMiddleware — паника превращается в err с записью в лог, здоровый путь прозрачен; прогон с -race обязателен. Третье: fake-сервис со счётчиком и три утверждения кэша из урока 15 — значения равны, ошибки не кэшируются, base вызван один раз; добавь кейс на два разных ключа. Четвёртое: Recorder-таблица на три кейса — 200 на GO, 400 на ErrEmpty, 400 на кривой JSON — и проверка Content-Type на положительном кейсе. Пятое: интеграционный тест на httptest.NewServer с дедлайном контекста и defer Close, декодирование ответа в UppercaseResponse. Шестое: замерь время прогона go test -race -count=1 и запиши, какой этаж съел больше всего; если транспортный тест дольше ста миллисекунд — разберись, что мешает. Седьмое: письменно ответь, почему limiter-тест со Sleep в пятьдесят миллисекунд — плохой тест, и как его переписать.
Личные заметкиПусто
Вопросы и ответы по уроку
Нужны ли библиотеки вроде gomock или testify для тестов go-kit?
Нет, стандартная библиотека закрывает почти всё: endpoint — функция, middleware — функция над функцией, сервис — интерфейс из двух методов; ручные стабы по двадцать строк нагляднее кодогенерации. gomock уместен, когда интерфейсов десятки и стабы разрастаются; testify добавляет assert-хелперы поверх t.Fatal. Канон курса — таблицы на struct-литералах, стабы на замыканиях, -race в CI: минимум зависимостей, максимум читаемости.
Как тестировать gRPC-транспорт из урока 13?
Тем же паттерном, что и HTTP: каркас grpc-go даёт тестовый канал bufconn — gRPC-сервер слушает не порт, а буфер, и клиент подключается через grpc.DialContext с Dialer на тот же буфер; сеть и порты не нужны. Endpoint-тесты при этом те же: gRPC-кодеки — функции, grpctrans.NewServer — обёртка endpoint'а, и половина проверок делается до транспорта. Интеграцию же гоняют по bufconn с дедлайном контекста.
Тест endpoint или тест сервиса — не дубль ли это?
Нет: сервисный тест проверяет логику (uppercase и sentinel), endpoint-тест — шов: type assertion request-структуры, упаковку ответа, перенос ошибки в Err-поле через Failer. Ломаются эти этажи по-разному: рефакторинг сервиса не должен ронять тесты адаптера и наоборот. Плюс endpoint-тест принимает декорированный сервис — он ловит поломки интеграции этажей из уроков 14-15, которые сервисный тест не видит в принципе.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Изучайте дальше
Следующая ступень учебного пути
gRPC-Go: высокопроизводительный RPC
Курс gRPC-Go: proto3, кодогенерация, unary и streaming вызовы, interceptors, TLS, дедлайны, load balancing. 25 уроков и итоговый проект.
- Писать .proto-схемы и генерировать код
- Реализовывать unary и streaming сервисы
- Управлять метаданными, дедлайнами и отменами
- Строить interceptors для логов и аутентификации