· обновлено
Как тестировать gRPC-сервисы
Тестирование gRPC начинается не с JSON и URL, а со схемы. Клиент не сможет закодировать сообщение, пока не знает полное имя метода и protobuf-типы. Когда схема загружена, дальше всё похоже на обычную проверку API: задаёте адрес и тело, добавляете metadata, отправляете вызов и разбираете статус. Разница начинается в потоках и deadline; отдельная история — TLS.
Ниже мы используем grpcurl для воспроизводимых команд. Те же шаги доступны в графических клиентах с поддержкой gRPC, включая Tetiva: подключить reflection или .proto, выбрать метод, заполнить сообщение и отправить вызов.
Тестовая схема
Представим сервис заказов со всеми четырьмя формами вызовов:
syntax = "proto3";
package shop.v1;
service OrderService {
rpc GetOrder(GetOrderRequest) returns (Order);
rpc WatchOrders(WatchOrdersRequest) returns (stream Order);
rpc ImportOrders(stream CreateOrderRequest) returns (ImportSummary);
rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}
message GetOrderRequest {
string id = 1;
}
message WatchOrdersRequest {
string customer_id = 1;
}
message CreateOrderRequest {
string customer_id = 1;
repeated string sku = 2;
}
message Order {
string id = 1;
string status = 2;
}
message ImportSummary {
int32 accepted = 1;
}
message ChatMessage {
string text = 1;
}
Полное имя unary-метода — shop.v1.OrderService/GetOrder. Пакет важен: вызов только OrderService/GetOrder часто заканчивается UNIMPLEMENTED, хотя сервер работает.


Server reflection: получить схему от сервера
Через server reflection клиент запрашивает список сервисов и их дескрипторы прямо в рантайме. При разработке это быстрее всего: не нужно искать совпадающую версию .proto.
Проверьте, включён ли reflection:
grpcurl -plaintext localhost:50051 list
grpcurl -plaintext localhost:50051 list shop.v1.OrderService
grpcurl -plaintext localhost:50051 describe shop.v1.OrderService.GetOrder
Если сервер отвечает, выберите метод и отправьте сообщение:
grpcurl -plaintext \
-d '{"id":"ord_123"}' \
localhost:50051 \
shop.v1.OrderService/GetOrder
В production reflection часто отключают, чтобы не публиковать каталог сервисов. Тогда передайте исходные .proto и пути импортов:
grpcurl -plaintext \
-import-path ./proto \
-proto shop/v1/orders.proto \
-d '{"id":"ord_123"}' \
localhost:50051 \
shop.v1.OrderService/GetOrder
Если схема импортирует google/type/date.proto, одного главного файла недостаточно: добавьте каталог, внутри которого начинается путь google/type/date.proto.
Как получить пример запроса из схемы
Не придумывайте JSON по названию метода. Откройте входное сообщение в дескрипторе и заполните поля с учётом соответствия protobuf и JSON. Имена можно писать как customerId; многие инструменты принимают и исходное customer_id. Значения int64 в JSON обычно передаются строкой, enum — именем, bytes — строкой Base64, Timestamp — временем в RFC 3339.
Минимальный осмысленный пример для CreateOrderRequest:
{
"customerId": "cus_42",
"sku": ["book-1", "pen-2"]
}
Автогенерация в API-клиенте экономит время, но результат остаётся заготовкой. Пустая строка, ноль и нулевое значение enum — допустимые значения protobuf, однако бизнес-валидацию они могут не пройти. В Tetiva пример тела строится из загруженной схемы; перед отправкой замените заглушки доменными значениями.
Unary и три вида streaming
У gRPC четыре формы вызова:
- Unary: один запрос, один ответ. Его проще всего повторять и включать в smoke-тесты.
- Server streaming: один запрос, несколько ответов. Проверяйте порядок событий, завершение потока и поведение при отмене клиентом.
- Client streaming: несколько запросов, один итоговый ответ. Поток со стороны клиента нужно явно закрыть, иначе сервер продолжит ждать сообщения.
- Bidirectional streaming: обе стороны отправляют сообщения независимо. Тестируйте не только содержимое, но и последовательность: ответ не обязан соответствовать последнему отправленному сообщению один к одному.
Серверный поток в grpcurl запускается обычной командой; ответы печатаются по мере поступления:
grpcurl -plaintext \
-d '{"customerId":"cus_42"}' \
localhost:50051 \
shop.v1.OrderService/WatchOrders
Клиентский и двунаправленный поток удобнее проверять в интерактивном режиме или графическом клиенте: откройте соединение и отправьте несколько сообщений, а в конце завершите клиентскую половину потока. Зафиксируйте ожидаемые события и таймаут, иначе зависший тест будет выглядеть как медленный.
Metadata и аутентификация
Metadata — это пары ключ-значение поверх HTTP/2. Ключи пишутся в нижнем регистре; бинарные ключи оканчиваются на -bin. Токен обычно передают так:
grpcurl -plaintext \
-H 'authorization: Bearer dev-token' \
-H 'x-request-id: test-001' \
-d '{"id":"ord_123"}' \
localhost:50051 \
shop.v1.OrderService/GetOrder
Если приходит UNAUTHENTICATED, сначала проверьте наличие и формат токена. PERMISSION_DENIED чаще означает, что пользователь аутентифицирован, но у него нет прав на действие. Не сохраняйте рабочие токены прямо в коллекции: используйте локальное окружение или хранилище секретов.
Deadline: ограничить ожидание
У каждого тестового вызова должен быть deadline. Без него зависшая зависимость, незакрытый stream или соединение без финального статуса могут держать проверку сколько угодно.
grpcurl -plaintext \
-max-time 3 \
-d '{"id":"ord_123"}' \
localhost:50051 \
shop.v1.OrderService/GetOrder
Статус DEADLINE_EXCEEDED не доказывает, что сервер ничего не сделал: операция могла завершиться после того, как клиент перестал ждать. Для изменяющих запросов используйте идемпотентный ключ и проверяйте серверные логи по request ID. Слишком короткий deadline создаёт нестабильные тесты, слишком длинный скрывает деградацию; выбирайте предел по допустимому времени конкретной операции.
TLS, mTLS и частые ошибки адреса
Для локального сервера без TLS нужен -plaintext. На защищённом адресе флаг убирают:
grpcurl api.example.com:443 list
С собственным центром сертификации передайте CA, а для mTLS — клиентский сертификат и ключ:
grpcurl \
-cacert ./certs/ca.pem \
-cert ./certs/client.pem \
-key ./certs/client-key.pem \
api.example.com:443 list
Ошибка certificate is valid for ... указывает на несовпадение имени хоста в сертификате. Подключение по IP вместо DNS-имени по умолчанию подставляет другое имя в TLS SNI, что влияет на проверку сертификата и маршрутизацию прокси. Отключать проверку сертификата допустимо лишь для локальной диагностики: такой обход не должен попадать в сохранённый production-профиль.
Минимальный чек-лист
Подтвердите адрес и режим TLS. Загрузите схему через reflection или .proto и сверьте полное имя метода. Тело сгенерируйте по message-типу, добавьте metadata и разумный deadline. Streaming прогоните отдельно: отправка, получение, закрытие, отмена. Удачный вызов сохраните как коллекционный тест — ручная проверка станет воспроизводимой.