用Python进行gRPC接口测试(三)

2492次阅读  |  发布于4年以前

在近期的测试中,小编又遇到了一些关于grpc接口的测试,踩了一些坑,也总结了一些经验,想与大家分享交流一下。本期我们主要来谈谈有关protobuf中一些特殊数据类型在python中的处理方式。由于目前protobuf3已经成为主流,本文将直接以proto3进行探讨。

一、标量值类型

标量值类型与我们在编程语言使用的基本数据类型概念类似,用来携带的数据也大体相同。在python中,这些标量值类型都能找到与之对应的python数据类型,处理起来简单便捷。

使用举例:

message Student {
  string name = 1;
  int32 age = 2;
  // true: male, false:female
  bool sex = 3;
}

Python实现代码:

name="小王"
age=15
sex=True

#方式1
student=Student(name=name,age=age,sex=sex)

#方式2
student=Student()
student.name=name
student.age=age
student.sex=sex

二、一些特殊类型

除了上面提到的标量值类型,proto3中还定义了其他一些特殊的数据类型,方便我们用来构造、传递各种复杂的数据结构。官方给出了一个关于这些类型的JSON映射表,可以直观地看到各种类型所含数据的基本结构。

这些类型使得我们可以方便地构造出各种各样的数据形式。这其中有几个较为常用的类型,在小编进行的测试中经常遇到,下面我们就结合实际中的例子来为大家介绍一下。

1、message

message,根据映射表我们可以看到,它类似于我们在编程语言中所使用的类的对象(object)。在一个类中,我们可以添加各种其他类型的数据,也包括类本身。通过类比,message也有类似的概念,我们可以在里面添加各种proto类型的数据,也包括message。其实正如message的名字一样——消息,它是protobuf中的核心类型,在grpc接口中,我们正是通过发送和接收消息来完成数据交互,来实现对应的功能。

简单的message:

message Person {
   int32 id = 1;
   string name = 2;
   string email = 3;
}

含其他message的message:

message Point {
    int32 latitude = 1;
    int32 longitude = 2;
}

message Feature {
    string name = 1;
    Point location = 2;
}

在Python中的使用:

location=Point(latitude=5,longitude=10)
Feature=Feature(name="我是个名字",location=location)

2、Timestamp、Duration

这两种类型都是关于时间的,Timestamp是时间戳,Duration表示的时间长度。

在AI平台账号服务的测试中,某Account类型的message定义如下:


message Account {
  string account_id = 1;
  google.protobuf.Timestamp update_at = 2;
  google.protobuf.Duration time_limit = 3;
}

在Python中的使用:

update_at=Timestamp()
#从字符串获取
update_at.FromJsonString("1970-01-01T00:00:00Z")
#获取当前时间
update_at.GetCurrentTime()

time_limit=Duration()
#从纳秒转换
time_limit.FromNanoseconds(1999999999)
#从秒转换
time_limit.FromSeconds(100)

account=Account(account_id="account1",update_at=update_at,time_limit=time_limit)

3、Any

Any类型比较特殊,它可以包含不同的message,结合pack和unpack,只需声明一个Any,即可传递各种类型的message而不用声明多个字段。

在大会同传项目中,某个请求的message中需要传递两种信息——图片和音频,于是通过Any类型来实现同一字段的复用:

message ImageData {
    string index = 1;
    bytes  image = 2;
}

message Data {
    string appid = 1;
    bytes payload = 2;
    string extra = 3;
}

message Request {
    google.protobuf.Any body = 1;
}

在Python中的使用:

imageData=msg_pb2.ImageData(index="001",image=open("1.jpg","rb").read())
req1=msg_pb2.Request()
req1.body.Pack(imageData)

data=msg_pb2.Data(name="no.1",payload=open("1.wav","rb").read(),extra="no use")
req=msg_pb2.Request()
req.body.Pack(data)

4、enum

enum枚举类型和其他大多数编程语言的枚举类型概念相同,主要是通过提前设定好一些固定的值来限定可以传递的内容。

在AI平台实名认证服务的测试中,需要一个认证人类型的字段,由于认证人类型收敛,于是使用enum类型来定义:

enum PersonType {
  PERSONTYPE_UNSPECIFIED = 0;
  INDIVIDUAL = 1;
  LEGAL = 2;
  AUTHORIZE = 3;
}

message Person {
  string real_name = 1;               
  PersonType person_type = 2;
}

在Python中的应用:

person_type=PersonType.Value("INDIVIDUAL")
Person(real_name="小王",person_type=person_type)

5、map

map相当于json中的键值对,在Python中类似于字典(dict),我们可以利用Python的dict类型数据来对map进行设置。map在proto中声明时一般会带有尖括号,来指定key和value的具体类型,如map<string,string>就表示键值对的key、value都为string类型。

在AI平台鉴权相关的测试中,需要为用户创建的应用绑定若干个不同的特殊属性,每个特殊属性对应着一个属性值,此处采用了map类型:

message App {
  string appid = 1;
  map<string, string> extra_informations = 2;
}

在Python中的应用:

extra_informations={"name":"app1","expired":"no"}
app=App(appid="1234567", extra_informations=extra_informations)

6、repeated

repeated相当于json中的list,在Python中类似于列表(list),我们可以利用Python的list类型数据来对repeated进行设置。

在AI平台账号服务的测试中,需要为账号添加各种不同的能力,每个能力有多个属性,而每个能力属性的种类和数据类型一致。此处采用了repeated类型:

message Audience {
  string name = 1;
  string tier = 2;
}

message Account {
  string account_id = 1;
  repeated Audience audience = 2;
}

在Python中的应用:

audience=[{"name":"ASR","tier":"stand"},{"name":"TTS","tier":"free"},{"name":"MT","tier":"stand"}]
account=Account(account_id="account1",audience=audience)

三、实际应用中的问题与技巧

1、repeated类型赋值问题

如果把上面所讲repeated类型例子中的Python代码改成如下形式,那么在运行时会报错:

audience=[{"name":"ASR","tier":"stand"},{"name":"TTS","tier":"free"},{"name":"MT","tier":"stand"}]
account=Account(account_id="account1")
account.audience=audience

错误信息:

AttributeError: Assignment not allowed to repeated field "name" in protocol message object.

这与我们上面所说的message的两种赋值方式似乎有所出入,但事实是因为protobuf中的repeated类型并不是我们想象的那样与python中的list完全对应,因此在这里会出现问题。所以在实际应用中,我们应避免这种写法,尽量采用上面例子中的方式。另外我们还可以采用另外一种方式来达到同样的效果:

audience=[{"name":"ASR","tier":"stand"},{"name":"TTS","tier":"free"},{"name":"MT","tier":"stand"}]
for audience1 in audience:
    a=account.audience.add()
    a.name=audience1['name']
    a.tier=audience1['tier']

2、复杂message的数据构造问题

在实际测试的接口中,有时某个message的结构可能会非常复杂,比如像语音识别服务一些接口,协议里包含很多不同的message和repeated类型,这样对于我们编写测试客户端代码以及构造case、解析case都会有一些影响。之前我们介绍过使用命令行的方式传递参数的方式显然难以满足这种情景下的需求,手动拼message的方式也显得十分不便。经过一番调研发现,对于这种情况,我们可以使用protobuf库中json_format里面的Parse、MessageToJson两个方法来有效解决,这两个方法可以实现protobuf message和json的互转。因为处理json的方式有很多,也很灵活,因此我们在构造case时可以使用json的方式,通过Parse方法直接将json转换成message。在收到返回结果之后,可以使用MessageToJson方法将message转换成json,这样对于我们测试人员来说,发送和接收的数据看起来都是json,无论是准备测试数据还检验结果都会轻松不少。

示例:


from google.protobuf import json_format
json_obj='{"a1":1,"a2":2}'
request = json_format.Parse(json_obj,MessageName())
json_result = json_format.MessageToJson(request)
print (json_result)

其中MessageName为message的名称,json_result为转为json后的返回结果。

小结

本文介绍了protobuf数据类型与Python数据类型的一些联系以及构造方法。结合前面两篇所介绍的四种gRPC接口测试请求方法,我们就可以构造各种类型的数据、对各种不同的gRPC接口进行测试了。好了,本期就到这里,我们下期再见~

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8