目录
一、传智健康项目介绍
1、项目描述
2、目标用户群体
3、项目模块
4、系统框架
二、Dubbo接口测试
1、RPC
2、Dubbo
3、查阅API文档
三、Telnet工具远程调用
1、启用telnet
2、telnet远程连接服务
3、telnet调用服务接口
四、python借助dubbo远程调用
1、安装dubboclient
2、实现步骤
3、会员服务(入门)
4、其他模块
5、分析bug来源
6、现有问题
五、接口自动化测试框架封装Dubbo接口
1、核心模块
2、基础服务对象封装
3、服务对象封装
4、测试用例对象封装
5、参数化
6、接口自动化框架封装
7、测试报告
一、传智健康项目介绍
1、项目描述
传智健康管理系统,是一款应用于健康管理机构的业务系统。采用可视化界面管理,提高健康管理师工作效率,加强与患者间的互动。
项目地址:传智健康
2、目标用户群体
3、项目模块
会员服务、预约服务、体检报告服务、健康评估服务、健康干预服务
4、系统框架
前端:http://mobile-health-test.itheima.net
后端:http://manager-health-test.itheima.net
二、Dubbo接口测试
1、RPC
- 远程过程调用(Remote Procedure Call):像调用本地方法一样,调用远程方法。
- 常见的RPC框架有 Dubbo、Thrift、grpc
2、Dubbo
- Dubbo是一款高性能、轻量级、基于Java的开源RPC框架(最早由阿里开源,2018年贡献给了Apache组织)
- Dubbo接口的作用:远程调用 java 写的方法。 需要传参、获取返回值。
3、查阅API文档
从中获取哪些信息?
- 服务名
- 方法名
- 参数类型、返回值类型
java中 方法定义语法结构:
返回值类型 方法名(数据类型 形参1,数据类型 形参2,....)
void:代表没有返回值、没有参数。
三、Telnet工具远程调用
1、启用telnet
2、telnet远程连接服务
连接语法:telnet IP 端口号
3、telnet调用服务接口
命令格式:invoke 服务名.方法名(实参) 示例:invoke MemberService.findByTelephone("13020210001")
四、python借助dubbo远程调用
Dubboclient,封装了 telnetlib 库。 telnetlib 是 python 内置模块,可实现远程调用 Dubbo 接口
1、安装dubboclient
pip install dubboclient
查验:
- 在 pip 中:pip list 或 pip show dubboclient
- 在 pycharm中:file - settings - 项目名下的 python 解释器列表
2、实现步骤
1. 导包 from dubboclient import DubboClient
2. 创建 DubboClient类实例,指定 IP 和 port
3. 使用 实例调用 invoke() 方法。 传入 :服务名、方法名、实参(方法使用)。获取响应结果
4. 打印响应结果
3、会员服务(入门)
3.1 案例1
根据手机号,查询会员信息(传递 普通参数)
dubbo> ls -l MemberService com.itheima.pojo.Member findByTelephone(java.lang.String) 接口定义:Member findByTelephone(String telephone) 参数: 字符串格式手机号。唯一 返回值: 成功:返回 会员的信息内容。string类型 包裹的 字典数据。 失败:返回 null。string类型
实现代码:
# 1. 导包 from dubboclient import DubboClient from dubboclient import DubboClient # 2. 创建 DubboClient类实例,指定 IP 和 port dubboclt = DubboClient("211.103.136.244", 6502) # 3. 使用 实例调用 invoke() 方法。 传入 :服务名、方法名、实参(方法使用)。获取响应结果 resp = dubboclt.invoke("MemberService", "findByTelephone", "13020210001") # 4. 打印响应结果 print("响应结果 =", resp) print("type(resp) =", type(resp))
3.2 案例2
添加会员(传递 对象参数)
dubbo> ls -l MemberService void add(com.itheima.pojo.Member) 接口定义:void add(Member member) 参数: 1. 自定义类 做 参数,根据接口文档,组织 “字典” 格式数据传参 2. 给字典增加 键k:”class“ ,值v:指明 类 对应的 完整 包名和类名 如:"class”:"com.itheima.pojo.Member" ls -l MemberService 可以查看完整包名和类名。 区分自定义类: 包名不以“java.”开头。一般采用:com.公司名.项目名.类名 返回值: 成功:返回 null 失败:返回 Failed
实现代码:
# 1. 导包 from dubboclient import DubboClient # 2. 创建 dubboclient 实例 dubboclt = DubboClient("211.103.136.244", 6502) # 准备 add 方法,所需要的数据 info = {"id": 911, "name": "杜甫", "phoneNumber": "13048379884"} # 如果 class 已经存在,覆盖原有class值; 如果不存在 class,新增一组 元素到 字典中。 info["class"] = "com.itheima.pojo.Member" # 3. 调用 invoke 传 服务名、方法名、实参。得响应结果 resp = dubboclt.invoke("MemberService", "add", info) # 4. 打印 print("响应结果 =", resp) print("type(resp) =", type(resp))
3.3 案例3
根据日期统计会员数(传递 字符串列表)
dubbo> ls -l MemberService java.util.List findMemberCountByMonths(java.util.List) 接口定义:List<Integer> findMemberCountByMonths(List<String> months) 参数: 1. 字符串列表。用字符串表示年、月,用“.”衔接 如:["2021.3", "2021.9"] 返回值: 成功:返回列表,对应参数设置的月份的会员数。 失败:Failed
实现代码:
# 1. 导包 from dubboclient import DubboClient # 2. 创建 dubboclient 实例 dubboclt = DubboClient("211.103.136.244", 6502) # 3. 用实例 调用invoke() ,传入 服务名、方法名、实参。 得响应结果 months = ["2021-7"] resp = dubboclt.invoke("MemberService", "findMemberCountByMonths", months) # 4. 查看响应结果 print("响应结果 =", resp) print("type(resp) =", type(resp))
4、其他模块
4.1 添加预约设置
dubbo> ls -l OrderSettingService void add(java.util.List) 接口定义:void add(List<OrderSetting> list) 参数: 1. 字典列表。字典有 orderDate 和 number 两个字段。 如:[{"orderDate":"2021-09-20 16:45:12","number":20}] 2. 日期格式:"2021-09-20 16:45:12",必须包含时分秒,否则失败。 返回值: 成功:null 失败:Failed
实现代码:
# 1. 导包 from dubboclient import DubboClient # 2. 创建 dubboclient 实例 dubboclt = DubboClient("211.103.136.244", 6502) # 准备 add 方法,所需要的数据 info = [{"orderDate": "2021-05-18 18:89:02", "number": 346}] # 3. 调用 invoke 传 服务名、方法名、实参。得响应结果 resp = dubboclt.invoke("OrderSettingService", "add", info) # 4. 打印 print("响应结果 =", resp) print("type(resp) =", type(resp))
4.2 按月统计预约设置信息
dubbo> ls -l OrderSettingService java.util.List getOrderSettingByMonth(java.lang.String) 接口定义:List getOrderSettingByMonth(String date) 参数: 字符串,如:"2021-09" 返回值: 成功:返回字符串类型数据,字符串内容为列表 失败:Failed
实现代码:
# 1. 导包 from dubboclient import DubboClient # 2. 创建 dubboclient 实例 dubboclt = DubboClient("211.103.136.244", 6502) # 月份 moths = "2021.02" # 3. 调用 invoke 传 服务名、方法名、实参。得响应结果 resp = dubboclt.invoke("OrderSettingService", "getOrderSettingByMonth", moths) # 4. 打印 print("响应结果 =", resp) print("type(resp) =", type(resp))
4.3 根据日期修改预约设置数量
dubbo> ls -l OrderSettingService void editNumberByDate(com.itheima.pojo.OrderSetting) 接口定义:void editNumberByDate(OrderSetting orderSetting) 参数: 1. 自定义类,用 字典 根据接口文档组织数据 2. 需要使用 class 指定参数对象的类型 如:{"orderDate":"2021-10-13 21:04:33","number":15, "class":"com.itheima.pojo.OrderSetting"} 3. 日期格式为:"2021-10-13 21:04:33",必须包含时分秒 返回值: 成功:null 失败:Failed
实现代码:
# 1. 导包 from dubboclient import DubboClient # 2. 创建 dubboclient 实例 dubboclt = DubboClient("211.103.136.244", 6502) # 日期 和 设置数据 date = {"orderDate": "2021-06-15 16:99:77", "number": 120} date["class"] = "com.itheima.pojo.OrderSetting" # 3. 调用 invoke 传 服务名、方法名、实参。得响应结果 resp = dubboclt.invoke("OrderSettingService", "editNumberByDate", date) # 4. 打印 print("响应结果 =", resp) print("type(resp) =", type(resp))
4.4 根据用户名查询用户信息
dubbo> ls -l UserService com.itheima.pojo.User findByUsername(java.lang.String) 接口定义:User findByUsername(String username) 参数:字符串类型,如:'admin' 返回值: 用户存在:返回用户信息 用户不存在:返回 null
实现代码:
# 1. 导包 from dubboclient import DubboClient # 2. 创建 dubboclient 实例 dubboclt = DubboClient("211.103.136.244", 6502) # 管理用户名 name = "admin" # 3. 调用 invoke 传 服务名、方法名、实参。得响应结果 resp = dubboclt.invoke("UserService", "findByUsername", name) # 4. 打印 print("响应结果 =", resp) print("type(resp) =", type(resp))
5、分析bug来源
抓取接口数据,分析bug是前端还是后端。
6、现有问题
远程调用的 7个dubbo接口 存在的问题:
1. 代码有 大量冗余
2. 测试接口时,除了要给 测试数据之外, 还需要 指定 服务名、方法名
3. 传参时,除了要考虑测试数据外,还要分析是否要添加 class 字段 及 对应数据。
4. 返回的数据类型统一为 string(不具体)
封装目标
1. 只关心:测试数据、响应结果
2. 返回的结果 分别为 不同的 具体类型。
五、接口自动化测试框架封装Dubbo接口
1、核心模块
2、基础服务对象封装
from dubboclient import DubboClient
class BaseService(object):
def __init__(self):
self.dubbo_client = DubboClient("211.103.136.244", 6502)
3、服务对象封装
3.1 会员服务封装
"""
类名:MemberService,继承于 BaseService
实例属性:
服务名称:service_name,赋值为 'MemberService'
实例方法:
def __init__(self):
# 先调父类__init__(),再添加实例属性 service_name
def find_by_telephone(self, telephone):
# 功能:根据手机号查询会员信息
# :param telephone: 手机号
# :return: 1. 会员存在,返回会员信息 2. 会员不存在,返回None
def find_member_count_by_months(self, data_list):
# 功能:根据日期统计会员数
# :param date_list: 日期列表,格式如:["2021.7"]
# :return: 返回列表,列表元素为对应月份的会员数,如:[10]
def add(self, info): 添加会员
# 功能:添加会员
# :param info: 会员信息的字典格式数据,参考接口文档填入字段数据,手机号需要唯一
# 如:{"fileNumber":"D0001", "name":"李白", "phoneNumber":"13020210002"}
# :return: 添加成功返回True, 添加失败返回False
验证结果:
# 1. 实例化对象
# 2. 通过实例对象调用实例方法
# 2.1 根据手机号查询会员信息
# 2.2 根据日期统计会员数
# 2.3 添加会员
"""
import json
from day02.base_service import BaseService
# 将 会员服务 封装成 会员服务类
class MemberService(BaseService):
def __init__(self):
super().__init__() # 调用父类 init 方法
self.service_name = "MemberService"
def find_by_telephone(self, tel):
resp = self.dubbo_client.invoke(self.service_name, "findByTelephone", tel)
if resp == "null":
return None
else:
# 作用:将 string类型的 数据,还原回成 字典 或 列表 数据。
return json.loads(resp)
def find_member_count_by_months(self, months):
resp = self.dubbo_client.invoke(self.service_name, "findMemberCountByMonths", months)
# 作用:将 string类型的 数据,还原回成 字典 或 列表 数据。
return json.loads(resp)
def add(self, info):
"""
:param info: 代表 用户 传入的 测试数据,没有 class 元素
:return:
"""
# 如果 class 已经存在,覆盖原有class值; 如果不存在 class,新增一组 元素到 字典中。
info["class"] = "com.itheima.pojo.Member"
# 3. 调用 invoke 传 服务名、方法名、实参。得响应结果
resp = self.dubbo_client.invoke(self.service_name, "add", info)
if resp == "null":
return True
else:
return False
if __name__ == '__main__':
ms = MemberService()
resp = ms.find_by_telephone("13020210001")
print("响应结果 =", resp)
print("type(resp) =", type(resp))
print("=" * 66)
months = ["2021-6"]
ms = MemberService()
resp = ms.find_member_count_by_months(months)
print("响应结果 =", resp)
print("type(resp) =", type(resp))
print("&" * 66)
# 准备 add 方法,所需要的数据
info = {"id": 911, "name": "杜甫", "phoneNumber": "13048379041"}
ms = MemberService()
resp = ms.add(info)
print("响应结果 =", resp)
print("type(resp) =", type(resp))
3.2 预约设置服务封装
"""
类名:OrderSettingService,继承于 BaseService
实例属性:
服务名称:service_name,赋值为 'OrderSettingService'
实例方法:
def __init__(self):
# 先调父类__init__(),再添加实例属性 service_name
def add(self, date_list):
# 功能:添加预约设置
# :param date_list:
# 1. 日期列表,如:[{"orderDate":"2021-09-20 16:45:12","number":20}]
# 2. 日期格式为:"2021-09-20 16:45:12",必须包括时分秒
# :return: 设置成功返回True, 设置失败返回False
def get_order_setting_by_month(self, date):
# 功能:按月统计预约设置信息
# :param date: 日期,如:"2021-08"
# :return: 列表,指定月份的预约信息
def edit_number_by_date(self, info): 根据日期修改预约设置数量
# 功能:根据日期修改预约设置数量
# :param info:
# 1. 预约设置的字典格式数据,参考接口文档填入字段数据
# 2. 如:{"orderDate":"2021-09-19 17:45:12","number":15}
# 3. 日期格式为:"2021-09-19 17:45:12",必须包括时分秒
# 4. 添加 "class":"com.itheima.pojo.OrderSetting"
# :return: 修改成功返回 True, 修改失败返回 False
验证结果:
# 1. 实例化对象
# 2. 通过实例对象调用实例方法
# 2.1 添加预约设置
# 2.2 按月统计预约设置信息
# 2.3 根据日期修改预约设置数量
"""
import json
from day02.base_service import BaseService
# 封装 预约设置服务类
class OrderSettingService(BaseService):
def __init__(self):
super().__init__()
self.service_name = "OrderSettingService"
def add(self, date_list):
# 功能:添加预约设置
# :param date_list:
# 1. 日期列表,如:[{"orderDate":"2021-09-20 16:45:12","number":20}]
# 2. 日期格式为:"2021-09-20 16:45:12",必须包括时分秒
# :return: 设置成功返回 True, 设置失败返回 False
resp = self.dubbo_client.invoke(self.service_name, "add", date_list)
if resp == "Failed":
return False
else:
return True
def get_order_setting_by_month(self, month):
# 功能:按月统计预约设置信息
# :param months: 日期,如:"2021-08"
# :return: 列表,指定月份的预约信息
resp = self.dubbo_client.invoke(self.service_name, "getOrderSettingByMonth", month)
if resp == "Failed":
return None
else:
return json.loads(resp)
def edit_number_by_date(self, date):
# 功能:根据日期修改预约设置数量
# :param info:
# 1. 预约设置的字典格式数据,参考接口文档填入字段数据
# 2. 如:{"orderDate":"2021-09-19 17:45:12","number":15}
# 3. 日期格式为:"2021-09-19 17:45:12",必须包括时分秒
# 4. 添加 "class":"com.itheima.pojo.OrderSetting"
# :return: 修改成功返回 True, 修改失败返回 False
date["class"] = "com.itheima.pojo.OrderSetting"
# 3. 调用 invoke 传 服务名、方法名、实参。得响应结果
resp = self.dubbo_client.invoke(self.service_name, "editNumberByDate", date)
if resp == "Failed":
return False
else:
return True
if __name__ == '__main__':
oss = OrderSettingService()
# 准备 add 方法,所需要的数据
info = [{"orderDate": "2021-05-18", "number": 346}]
resp = oss.add(info)
print("响应结果 =", resp)
print("type(resp) =", type(resp))
print("============== 按月统计预约设置信息 ===========")
oss = OrderSettingService()
# 月份
months = "2021.02"
resp = oss.get_order_setting_by_month(months)
print("响应结果 =", resp)
print("type(resp) =", type(resp))
print("============== 根据日期修改预约设置数量 ===========")
# 日期 和 设置数据
date = {"orderDate": "2021-06-15 16:99:77", "number": 120}
oss = OrderSettingService()
resp = oss.edit_number_by_date(date)
print("响应结果 =", resp)
print("type(resp) =", type(resp))
3.3 用户服务封装
"""
类名:UserService,继承于BaseService
实例属性:
服务名称:service_name,赋值为'UserService'
实例方法:
def __init__(self):
# 先调父类__init__(),再添加实例属性 service_name
def find_by_username(self, username):
# 功能:根据用户名查询用户信息
# :param username: 用户名
# :return: 1. 如果用户存在,返回用户信息 2. 如果不存在,返回 None
验证结果:
# 1. 实例化对象
# 2. 通过实例对象调用实例方法
"""
import json
from day02.base_service import BaseService
# 封装 用户服务类
class UserService(BaseService):
def __init__(self):
super().__init__()
def find_by_user_name(self, name):
# 3. 调用 invoke 传 服务名、方法名、实参。得响应结果
resp = self.dubbo_client.invoke("UserService", "findByUsername", name)
if resp == "null":
return None
else:
return json.loads(resp)
if __name__ == '__main__':
# 管理员用户名
name = "李白"
us = UserService()
resp = us.find_by_user_name(name)
print("响应结果 =", resp)
print("type(resp) =", type(resp))
4、测试用例对象封装
import unittest
# 借助 unittest 框架,封装测试类,从 TestCase 继承
from day02.py02_会员服务类封装设计 import MemberService
class TestFindByTelephone(unittest.TestCase):
ms = None
@classmethod
def setUpClass(cls) -> None:
# 创建MemberService实例
cls.ms = MemberService()
def test01_tel_exists(self):
tel = "13020210001"
resp = self.ms.find_by_telephone(tel)
print("手机号存在 =", resp)
self.assertEqual("13020210001", resp.get("phoneNumber"))
def test02_tel_not_exists(self):
tel = "13020218973"
resp = self.ms.find_by_telephone(tel)
print("手机号不存在 =", resp)
self.assertEqual(None, resp)
def test03_tel_has_special_char(self):
tel = "1302021abc#"
resp = self.ms.find_by_telephone(tel)
print("手机号含有字母特殊字符 =", resp)
self.assertEqual(None, resp)
5、参数化
1. 导包 from parameterized import parameterized
2. 在 通用测试方法上一行,@parameterized.expand()
3. 给 expand() 传入 [(),(),()] 类型的数据。
4. 修改 通用测试方法,添加形参,个数、顺序与 () 数据一致。
5. 在 通用测试方法 使用形参import unittest from day02.py02_会员服务类封装设计 import MemberService from parameterized import parameterized # 借助 unittest 框架,封装测试类,从 TestCase 继承 class TestMemberService(unittest.TestCase): ms = None @classmethod def setUpClass(cls) -> None: cls.ms = MemberService() # 创建MemberService实例 # 通用测试方法(参数化) @parameterized.expand([("13020210001", "13020210001"), ("13020218973", None), ("1302021abc#", None)]) def test_findByTelephone(self, tel, except_data): # print("tel =", tel, "except_data =", except_data) resp = self.ms.find_by_telephone(tel) if resp is None: self.assertEqual(None, resp) else: self.assertEqual(except_data, resp.get("phoneNumber")) @parameterized.expand([(["2021.5"], [3]), (["2017.4"], [0])]) def test_findMemberCountByMonths(self, month, except_data): resp = self.ms.find_member_count_by_months(month) print("============ resp =============", resp) self.assertEqual(except_data, resp)
6、接口自动化框架封装
7、测试报告
# 导包
import unittest
from htmltestreport import HTMLTestReport
# 创建 suite 实例
from scripts.test_member_service import TestMemberService
suite = unittest.TestSuite()
# 添加测试用例
suite.addTest(unittest.makeSuite(TestMemberService))
# 创建 HTMLTestReport 类对象
runner = HTMLTestReport("./report/传智健康测试报告.html", description="描述", title="标题")
# 调用 run() 传入 suite
runner.run(suite)