服务端测试实战之rpc协议
Posted 测试baby
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了服务端测试实战之rpc协议相关的知识,希望对你有一定的参考价值。
在服务端测试(一)中详细了介绍了服务端测试中的策略和方法论,以及针对服务端测试中稳定性的测试,以及高并发下对服务的并发请求。不过在企业的应用中,除了REST API的接口,还有gRPC的协议,主要应用于金融以及货币交易等领域,当然不仅仅如此,gRPC协议是一个高性能,开源和通用的框架,主流的开发语言都提供了对应的API,如Java,Go,Python等语言。gRPC的协议是基于HTTP/2标准设计(REST API 基本是基于HTTP/1.1设计),可以处理双向流,单路复用等,这些特性在移动端的领域表现更加高效,而且更加节省空间,背后的技术团队是Google。
在服务端的测试中,不管是什么协议,首先都是客户端与服务端的交互,只是这中间会涉及到很多的协议,如HTTP,WebSocket,以及今天主要要介绍的gRPC协议,把握住这点后,那么中间的交互我们可以更多的理解为同步交互或者是异步的交互,或者更加精准的说:请求/响应模式和异步/响应模式,不管那种模式,它最后的本质思想是客户端与服务端之间建立TCP的连接之后,客户端与服务端就会进行交互(请求/响应模式和异步/响应模式),在测试的角度上而言,客户端与服务端交互后,客户端拿到服务端返回的响应数据,然后针对这些响应数据进行断言和结果准确性的验证。
在gRPC的协议中,客户端应用程序可以直接调用其他计算机上的服务器应用程序上的方法,就好像它是本地对象一样,从而更容易创建分布式应用程序和服务。与许多 RPC 系统一样,gRPC 基于定义服务的想法,指定可以远程称为其参数和返回类型的方法。在服务器方面,服务器实现此界面并运行 gRPC 服务器来处理客户端呼叫,如下图所示:
在如上截图中我们看到,使用gRPC的协议,主流的开发语言之间都是可以进行交互。
本文章主要使用Python语言来演示gRPC的应用和针对gRPC协议的接口测试应用。在gRPC的协议中,默认使用protocol buffers作为接口定义语言,来描述服务端和客户端之间的消息结构信息,如下所示,文件名称为helloworld.proto:
syntax = "proto3";
service Greeter {
rpc SayHello(HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
在如上描述了客户端和服务端之间的交互信息,当然这个接口是单向的,除了单向的,还有请求流,应答流,双向流,本文章主要介绍单向的应用(后续逐步介绍流式的应用)。在如上的文件中,我们可以看到在请求中需要的请求参数是name,请求成功后服务端返回的message,name和message的字段数据类型都是string。
在Python中,我们首先需要安装第三方的库来操作gRPC的协议,具体安装的库如下:
pip3 install grpcio
pip3 install grpcio-tools
安装成功后,执行如下命令生成pb2文件和pb2_grpc的文件,执行命令为:
python3 -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I. helloworld.proto
执行成功后,在当前的目录下就会生成名称为:helloworld_pb2.py和helloworld_pb2_grpc.py的文件,helloworld_pb2_grpc.py文件主要是服务端的内容,文件内容如下:
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc
import helloworld_pb2 as helloworld__pb2
class GreeterStub(object):
"""Missing associated documentation comment in .proto file."""
def __init__(self, channel):
"""Constructor.
Args:
channel: A grpc.Channel.
"""
self.SayHello = channel.unary_unary(
'/Greeter/SayHello',
request_serializer=helloworld__pb2.HelloRequest.SerializeToString,
response_deserializer=helloworld__pb2.HelloReply.FromString,
)
class GreeterServicer(object):
"""Missing associated documentation comment in .proto file."""
def SayHello(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def add_GreeterServicer_to_server(servicer, server):
rpc_method_handlers = {
'SayHello': grpc.unary_unary_rpc_method_handler(
servicer.SayHello,
request_deserializer=helloworld__pb2.HelloRequest.FromString,
response_serializer=helloworld__pb2.HelloReply.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'Greeter', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,))
# This class is part of an EXPERIMENTAL API.
class Greeter(object):
"""Missing associated documentation comment in .proto file."""
@staticmethod
def SayHello(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(request, target, '/Greeter/SayHello',
helloworld__pb2.HelloRequest.SerializeToString,
helloworld__pb2.HelloReply.FromString,
options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
helloworld_pb2.py的文件内容为:
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: helloworld.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='helloworld.proto',
package='',
syntax='proto3',
serialized_options=None,
create_key=_descriptor._internal_create_key,
serialized_pb=b'\\n\\x10helloworld.proto\\"\\x1c\\n\\x0cHelloRequest\\x12\\x0c\\n\\x04name\\x18\\x01 \\x01(\\t\\"\\x1d\\n\\nHelloReply\\x12\\x0f\\n\\x07message\\x18\\x01 \\x01(\\t23\\n\\x07Greeter\\x12(\\n\\x08SayHello\\x12\\r.HelloRequest\\x1a\\x0b.HelloReply\\"\\x00\\x62\\x06proto3'
)
_HELLOREQUEST = _descriptor.Descriptor(
name='HelloRequest',
full_name='HelloRequest',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='name', full_name='HelloRequest.name', index=0,
number=1, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=20,
serialized_end=48,
)
_HELLOREPLY = _descriptor.Descriptor(
name='HelloReply',
full_name='HelloReply',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='message', full_name='HelloReply.message', index=0,
number=1, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=50,
serialized_end=79,
)
DESCRIPTOR.message_types_by_name['HelloRequest'] = _HELLOREQUEST
DESCRIPTOR.message_types_by_name['HelloReply'] = _HELLOREPLY
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
HelloRequest = _reflection.GeneratedProtocolMessageType('HelloRequest', (_message.Message,), {
'DESCRIPTOR' : _HELLOREQUEST,
'__module__' : 'helloworld_pb2'
# @@protoc_insertion_point(class_scope:HelloRequest)
})
_sym_db.RegisterMessage(HelloRequest)
HelloReply = _reflection.GeneratedProtocolMessageType('HelloReply', (_message.Message,), {
'DESCRIPTOR' : _HELLOREPLY,
'__module__' : 'helloworld_pb2'
# @@protoc_insertion_point(class_scope:HelloReply)
})
_sym_db.RegisterMessage(HelloReply)
_GREETER = _descriptor.ServiceDescriptor(
name='Greeter',
full_name='Greeter',
file=DESCRIPTOR,
index=0,
serialized_options=None,
create_key=_descriptor._internal_create_key,
serialized_start=81,
serialized_end=132,
methods=[
_descriptor.MethodDescriptor(
name='SayHello',
full_name='Greeter.SayHello',
index=0,
containing_service=None,
input_type=_HELLOREQUEST,
output_type=_HELLOREPLY,
serialized_options=None,
create_key=_descriptor._internal_create_key,
),
])
_sym_db.RegisterServiceDescriptor(_GREETER)
DESCRIPTOR.services_by_name['Greeter'] = _GREETER
# @@protoc_insertion_point(module_scope)
下面我们来编写服务端的代码,具体实现代码如下:
#!/usr/bin/env python
#!coding:utf-8
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
from concurrent import futures
import time
import os
class HelloWorldServicer(helloworld_pb2_grpc.GreeterServicer):
def SayHello(self,request,context):
'''客户端与服务端之间进行交互'''
return helloworld_pb2.HelloReply(message='Hello {0}'.format(request.name))
def serve():
server=grpc.server(futures.ThreadPoolExecutor(max_workers=os.cpu_count()))
helloworld_pb2_grpc.add_GreeterServicer_to_server(HelloWorldServicer(),server)
#指定服务端的测试地址信息
server.add_insecure_port('[::]:50051')
server.start()
try:
while True:
time.sleep(60*60*24)
except KeyboardInterrupt:
server.stop(0)
if __name__ == '__main__':
serve()
不管是客户端的代码还是服务端的代码,都需要导入helloworld_pb2和helloworld_pb2_grpc。
我们在上面说到,不顾任何协议之间的通信,都是客户端与服务端之间的通信,那么对我们而言,在测试的时候需要清楚是什么协议,清楚协议后就模拟具体的协议编写客户端的代码与服务端之间进行通信,如本案例是gRPC的协议,下来在进行测试的时候,我们编写gRPC的协议模拟客户端来发送请求,客户端实现的源码如下:
#!/usr/bin/env python
#!coding:utf-8
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
import requests
def getData():
r=requests.get(url='https://api.muxiaoguo.cn/api/QqInfo?qq=2839168630')
return r.text
def run():
#连接服务端的程序
channel=grpc.insecure_channel('localhost:50051')
#调用rcp的服务
stub=helloworld_pb2_grpc.GreeterStub(channel)
r=stub.SayHello(helloworld_pb2.HelloRequest(name=getData()))
print('server response data:{0}'.format(r.message))
if __name__ == '__main__':
run()
当然我们在进行数据交互的时候,更多的是拿到目标的数据,再由客户端发送给服务端,如本案例拿到QQ的信息后,再通过参数name发送给服务端,下来启动服务端的程序和客户端的程序,客户端就会接收到服务端返回的数据信息,如下所示:
如上可以看到客户端发送请求后,服务端通过message把数据返回给了客户端。
在后面再逐步的介绍针对请求流,应答流,双向流在gRPC协议中API测试的应用和具体案例实战。感谢您的阅读!
这里给大家整理了一份《软件测试工程师进阶的技术栈》,包含了诸多技术栈,希望能帮助在升级打怪中提供中坚力量
给大家推荐下我自己建的软件测试交流学习群: 902061117 ,群里都是搞软件测试的,如果你正在学习测试 ,小编欢迎你加入,大家都是测试党,群内不定期分享干货(都是软件测试相关的),包括我自己整理的一份2021最新的进阶自动化资料。
这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你
关注我的微信公众号【伤心的辣条】免费获取~
送上一句话:
世界的模样取决于你凝视它的目光,自己的价值取决于你的追求和心态,一切美好的愿望,不在等待中拥有,而是在奋斗中争取。
如果我的博客对你有帮助、如果你喜欢我的博客内容,请 “点赞” “评论” “收藏” 一键三连哦!
好文推荐:
以上是关于服务端测试实战之rpc协议的主要内容,如果未能解决你的问题,请参考以下文章
Day785.网络通信优化之通信协议:如何优化RPC网络通信 -Java 性能调优实战