Итак, мы с вами добрались до третьей, самой «хардовой» части цикла. Сегодня поговорим про gRPC.
![](https://habrastorage.org/getpro/habr/upload_files/836/a07/c90/836a07c90e4d3efc3947fec06ead36b1.jpg)
Что такое gRPC?
Сам RPC — удалённый вызов процедур (иногда вызов удалённых процедур; RPC от англ. remote procedure call) — класс технологий, позволяющих программам вызывать функции или процедуры других программ, делая это так, как если бы они находились в одном адресном пространстве. Буква g в названии — это гугловая реализация этих технологий.
Разберем это все на примере.
Допустим, что вы — программист и сидите в монолитной репе. У вас одно приложение. Сам проект открыт в IDE и вы в нем работаете. В репе реализован определенный класс (например, на Kotlin), у которого есть метод, возвращающий вам данные по пользователю.
fun getUserInfo(id: String) {
return //some data
}
Теперь в другом месте проекта, в другом классе, вы хотите вызвать этот метод, чтобы поработать с данными о пользователе. Что вы тогда делаете? Импортируете класс и просто делаете обычный вызов метода.
var userData = getUserInfo("userID")
// Continue work with data
Он выполнится, вы получите все нужные данные. Всё здорово и всё работает.
Дальше приходит новый вызов - микросервисная архитектура, которая предлагает всё разделить и вынести всё, что мы делаем с пользователями, в отдельный сервис.
Теперь наши условные сервисы разделило сетевое взаимодействие, вот как на картинке.
![](https://habrastorage.org/getpro/habr/upload_files/dc7/a82/dce/dc7a82dce4d672f02e27e06c1927b08a.png)
Есть сервис A, который хочет данные от пользователя. Есть сервис B, у которого есть публичный API, через который эти данные можно получить.
Что делать сервису A?
Ему нужно сделать get-запрос на endpoint, который предоставит сервис B, затем получить обратно HTTP-ответ (там будет JSON), извлечь данные и работать с ними.
![](https://habrastorage.org/getpro/habr/upload_files/120/6d0/013/1206d001358b1f07ae9f46db259f189f.png)
Что предлагает gRPC?
А предлагает он вот что — пусть сервис B заведет некий протофайл с расширением .proto, в котором опишет те методы и функции, которые можно будет вызывать у него всем остальным.
![](https://habrastorage.org/getpro/habr/upload_files/977/8ed/78a/9778ed78a2cbcac71a0d29c0b752690e.png)
Получается такой аналог API, в котором сервис подтверждает, что может работать с вызовами этих функций с определенными параметрами и возвращать определенные данные. При этом внутри себя он на самом деле реализует все эти функции.
![](https://habrastorage.org/getpro/habr/upload_files/7ec/f44/5f0/7ecf445f02f574a3a6df97d717497fb1.png)
И на своем уровне поднимает RPC-службу.
![](https://habrastorage.org/getpro/habr/upload_files/098/8b6/6f6/0988b66f6493a94737d5c521a6135205.png)
Эта служба обрабатывает все запросы, которые будут приходить.
Окей, сервисом В разобрались. Что теперь делать на уровне сервиса А, чтобы получить данные о пользователе?
Мы определенным образом забираем себе модели, описанные в .proto file сервиса В. Например, подключаем в gradle через dependency этот пакет, в котором есть все описанные модели.
![](https://habrastorage.org/getpro/habr/upload_files/a0a/5ca/69c/a0a5ca69ccf94ca62108bc854096661e.png)
И дальше реализуем над ними обёртку со своей логикой и вызовом методов сервиса В.
![](https://habrastorage.org/getpro/habr/upload_files/b11/2f3/69f/b112f369f9605063813670810216e008.png)
Теперь мы начинаем писать код, опираясь именно на вызовы нашей обёртки, которая лежит у нас здесь в проекте, это уже удобней. При этом, когда мы будем вызывать свои методы, они внутри себя на самом деле будут обращаться к методам сервиса B. Они пойдут к нему в RPC-службу и скажут, что хотят с ним работать. Сервис B ответит, что готов с удовольствием нам помочь.
![](https://habrastorage.org/getpro/habr/upload_files/b76/7ca/c7e/b767cac7e2ac1cffcbb76330928a6acf.png)
При этом стоит заметить, что в gRPC все запросы используют Protocol Buffers — это специальный бинарный протокол сериализации данных, разработанный в компании Google. То есть сервис А предоставит сервису В свой запрос в бинарном виде. Сервис В поймёт, какой метод у него вызывают, выполнит его и вернет результат назад. Причем ответ будет также проходить через RPC службу и будет в двоичном виде. Сервис А уже получит такой ответ и продолжит работать дальше, как ни в чем не бывало.
Если чуть тщательнее порыться под капотом, получится такая схема.
![](https://habrastorage.org/getpro/habr/upload_files/6e4/c7a/572/6e4c7a572f0e8ccb0bfcd67238c6f62c.png)
На клиентской машине мы вызываем метод. Например, нам нужна информация о пользователях, и вызываем его так, как будто находимся на самом деле в сервисе B.
При этом мы делаем обращение именно в реализованную ранее обёртку.
Затем уже подрубаются специальные библиотеки, которые наш запрос транслируют определенным образом в двоичный запрос. Проводят сериализацию, передают его уже дальше в RPC-службу, и далее через protocol buffers всё попадает на сервер B.
Он в свою очередь обратно десериализует эту двоичную последовательность в виде имени процедуры, параметров и значения. Выполняет ее, получает данные, а затем обратно проделывает всю эту трансформацию.
Данные запаковывает в бинарную последовательность и отправляет ее обратно по сети.
Мы на стороне клиента ее получаем, распаковываем данные, возвращаем обратно нашей программе и продолжаем работать дальше.
Такой алгоритм, если в двух словах.
Честно скажу, когда я изучал эту технологию, первое, о чем я подумал — что за жесть вообще? Зачем так усложнять всё, можно же проще все делать. Есть же REST HTTP, он понятен, там есть endpoint-ы, нормальные запросы и ответы, которые можно увидеть и прочитать, увидеть данные которые передаем и получаем, их можно осмыслить. Зачем вообще все эти бинарные вещи?
На самом деле, тут есть три очень важные штуки.
Во-первых, и это по сути главная киллер-фича — скорость. Сам Google заявлял, что прирост по скорости будет в 3—10 раз, энтузиасты же, которые все это протестировали, вывели свою цифру — в 7 раз. В 7 раз быстрее происходит сериализация данных при работе с этим бинарным форматом, чем при работе с JSON.
Поэтому, если ваши сервисы, что называется, gRPC-ориентированы, то есть заточены на очень быстрые взаимодействия друг с другом, время отклика максимально быстрое, при этом очень маленькие короткие сообщения — вы получаете колоссальный прирост по производительности и эффективности потребления ресурсов. Это очень важный кейс.
Во-вторых, помните ситуацию при работе с REST HTTP, когда ребята сделали endpoint, а документацию забыли? А тестировать-то как-то надо. Поэтому в этой случае документацией станет сам разработчик.
![](https://habrastorage.org/getpro/habr/upload_files/6b6/72a/008/6b672a008f59393428ce65e125cdbd00.jpeg)
В gRPC подобное не прокатит — здесь вы сначала реализовываете именно контракт в протофайле. То есть вы как бы реализуете сначала документацию, а потом — логику. На мой взгляд, это тоже очень важная фишка
В-третьих, здесь очень сильная обратная совместимость за счет нежесткой привязки к имени поля. Двоичный протокол не знает, что такое строки, он знает, что такое числа. Определенный способ записи и организации данных в протофайле исключает ситуацию, когда разработчик поменял имя переменной, и у клиента что-то отлетело.
На практике
Давайте перейдем к практике. Для начала я выкачиваем проект отсюда. Собираем и запускаем его согласно документации
Далее заходим с вами в Postman -> NEW и выбираем gRPC.
![](https://habrastorage.org/getpro/habr/upload_files/640/f0b/a43/640f0ba43c479c11684620c5efc712cc.png)
Прежде чем начнем работать, надо объяснить Postman, с каким сервисом мы будем иметь дело. Как это делается? Правильно! Надо подгрузить в него протофайл сервиса.
Переходим во вкладку Service definition.
![](https://habrastorage.org/getpro/habr/upload_files/358/a90/d27/358a90d27374560711e5a2843cb5a11d.png)
Кнопка “Import .proto file” так и просится быть нажатой.
![](https://habrastorage.org/getpro/habr/upload_files/348/765/a65/348765a653fd8a35c011f04b147b506c.png)
Нажимаем ее и далее выбираем файл.
Нужный нам протофайл будет лежать в папке со скачанным проектом, подпапка proto. Имя GrpcExampleService.proto
Выбираем его и жмем “Next”.
![](https://habrastorage.org/getpro/habr/upload_files/f62/12e/04f/f6212e04fbc8983869482c9fb24dbb5b.png)
Postman увидел наш файл и понял, что мы будем работать с некоторым сервисом, поэтому предложит импортировать его. Я не против, поэтому жмем “Import as API”.
![](https://habrastorage.org/getpro/habr/upload_files/16f/d65/6a2/16fd656a28adcd31599ba3a970c14bca.png)
После успешного импорта мы увидим, что наш созданный пустой gRPC Request использует наш новый API.
![](https://habrastorage.org/getpro/habr/upload_files/8b4/f7e/0d4/8b4f7e0d4ad3be7697df45f21c530ff1.png)
Что после этого изменилось?
Вы можете нажать на селектор выбора метода.
![](https://habrastorage.org/getpro/habr/upload_files/52a/e57/a35/52ae57a3509591791dc58d749a4a914f.png)
Ого! Тут нас уже ждут все методы, с которыми умеет работать сервис.
![](https://habrastorage.org/getpro/habr/upload_files/8b1/c30/895/8b1c30895ee9910dd248e79476e77cca.png)
Прикольно, а можно как-то проверить, что тут реально все методы?
Можно, заходим в левом меню Postman в раздел APIs, далее выбираем наш New API — Definition и кликаем на протофайл.
![](https://habrastorage.org/getpro/habr/upload_files/928/e23/c47/928e23c47fcb696daf4b912ba279e6dd.png)
Для любознательных — можете изучить его весь, а я покажу только первые 10 строк, где у нас лежит информация о том, какие методы “открыты наружу”. Смотрим блок service.
![](https://habrastorage.org/getpro/habr/upload_files/763/20b/11f/76320b11fcf8a27ff68d629494fa66a8.png)
Действительно, 3 метода — Postman не обманул, но не протестировать его я не мог ????.
Хорошо, теперь возвращаемся к нашему запросу.
Давайте выполним первый запрос на добавление нового клиента.
Для этого сперва введем в поле url “localhost:50051”, так как сервис поднят на этом порту.
А в селекторе метода выберем “AddClient” и перейдем во вкладку “Message”.
![](https://habrastorage.org/getpro/habr/upload_files/a2b/c56/421/a2bc5642139b3e6935431c189fe92a66.png)
Message у нас в виде JSON-объекта, добавляем фигурные скобки и внутри вводим первый символ “c” C, и Postman сразу предлагает напечатать clientsinfo, подсказки тут как тут — спасибо тебе.
![](https://habrastorage.org/getpro/habr/upload_files/ddf/3e9/47f/ddf3e947f45743b8420683f639fcae3c.png)
Можем подсмотреть в протофайле, что там в clientinfo надо передать — login, email, city.
![](https://habrastorage.org/getpro/habr/upload_files/8d3/fd8/8fa/8d3fd88faf4bfe41756e8ebeb25194ea.png)
Окей, давайте сформируем такой объект.
{
"clientsinfo": [
{
"city": "City",
"email": "check",
"login": "Habra"
}
]
}
Выполним запрос, увидим Response: OK.
![](https://habrastorage.org/getpro/habr/upload_files/dda/a68/569/ddaa68569636d08b002a2a0c7d41bbcb.png)
Попробуем получить информацию по этому логину. Для этого меняем вызываемый метод в селекторе на GetClientByLogin:
![](https://habrastorage.org/getpro/habr/upload_files/1f9/3f0/8f1/1f93f08f1953428958e1491ac5e98bad.png)
А в теле сообщения указываем:
{
"login": "Habra"
}
Выполняем запрос — и получаем всю введенную нами ранее информацию.
![](https://habrastorage.org/getpro/habr/upload_files/3bb/552/6b2/3bb5526b2cff824016e3104dc31eda00.png)
Вот и вся магия, несложно, да?
Коды ответов
Стоит обратить внимание, что у gRPC есть свои коды ответов. У Google всё круто с документацией, поэтому прямо в репозитории gRPC есть вся информация о кодах ответа и их значениях,
Что нужно тестировать в gRPC?
Сразу хочу сделать маленькое отступление и сказать вот о чем. Так как это удаленный вызов процедур, то проверка логики работы самих процедур отлично покрывается юнит-тестами и нет необходимости тестировать их работу через внешние вызовы. К сожалению, в практике встречаются разные проекты, и могут быть те, где проверки юнит-тестами заменяют на внешние вызовы процедур для проверки их логики работы. В таком случае к списку ниже придется добавить проверку логики работы самих вызываемых процедур.
Окей, если логика работы покрыта юнит-тестами, то на что еще мне как тестировщику стоит обратить внимание?
Прежде всего, не забываем, что gRPC – это удаленный вызов процедур. Поэтому мы с вами должны проверить, что наша процедура с определенными параметрами может быть вызвана удаленно. Потому что, возможно, она уже и не работает через удаленный вызов.
Далее надо проверить, доступна ли процедура извне. Закрытие авторизацией никто не отменял
Круто, если будет проверен формат данных, который возвращается. В примерах выше возвращался JSON. Его, например, можно провалидировать по схеме
Ну и коды ответов. Это уже на ваше усмотрение, согласно бизнес-потребности
Подведем итог, что надо для тестирования gRPC-сервиса
Импортим .proto файл
Проверяем доступность удаленного вызова процедур
Проверяем доступность методов закрытых авторизацией
Проверяем возвращаемые данные и ошибки (при необходимости, т.к. может быть покрыто юнит-тестами)
Проверяем е2е-сценарии
Проверяем коды ответов
На этом я буду заканчивать свой цикл из трех статей по трем совершенно разных не-REST-бэкендам.
Несмотря на то, что цикл занял три поста, мы пробежались лишь по верхушкам этих трех айсбергов. Под каждой технологией ещё очень и очень много особенностей. Но, надеюсь, мои посты пригодятся вам и помогут легче подступиться к этим вещами, чтобы что-то протестировать, попробовать, пощупать, да и просто познакомиться. А потом уже, набравшись опыта, можно начать наращивать качество своего тестирования.
Всем успехов и добра!
ProTestingInfo_QA
+1 в карму! Благодарю вас за цикл статей. Эта статья просто супер! Рекомендую своим коллегам для прочтения. Для меня уж точно полезно сейчас.
Hroft356 Автор
Рад оказаться полезным :-)