目录
一、传智健康项目介绍
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、测试报告
传智健康管理系统,是一款应用于健康管理机构的业务系统。采用可视化界面管理,提高健康管理师工作效率,加强与患者间的互动。
项目地址:传智健康
会员服务、预约服务、体检报告服务、健康评估服务、健康干预服务
前端:http://mobile-health-test.itheima.net
后端:http://manager-health-test.itheima.net
- 远程过程调用(Remote Procedure Call):像调用本地方法一样,调用远程方法。
- 常见的RPC框架有 Dubbo、Thrift、grpc
- Dubbo是一款高性能、轻量级、基于Java的开源RPC框架(最早由阿里开源,2018年贡献给了Apache组织)
- Dubbo接口的作用:远程调用 java 写的方法。 需要传参、获取返回值。
从中获取哪些信息?
- 服务名
- 方法名
- 参数类型、返回值类型
java中 方法定义语法结构:
返回值类型 方法名(数据类型 形参1,数据类型 形参2,....)
void:代表没有返回值、没有参数。
连接语法:telnet IP 端口号
命令格式:invoke 服务名.方法名(实参) 示例:invoke MemberService.findByTelephone("13020210001")
Dubboclient,封装了 telnetlib 库。 telnetlib 是 python 内置模块,可实现远程调用 Dubbo 接口
pip install dubboclient
查验:
- 在 pip 中:pip list 或 pip show dubboclient
- 在 pycharm中:file - settings - 项目名下的 python 解释器列表
1. 导包 from dubboclient import DubboClient
2. 创建 DubboClient类实例,指定 IP 和 port
3. 使用 实例调用 invoke() 方法。 传入 :服务名、方法名、实参(方法使用)。获取响应结果
4. 打印响应结果
3.1 案例1
根据手机号,查询会员信息(传递 普通参数)
dubbo> ls -l MemberServicecom.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 MemberServicevoid 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 MemberServicejava.util.List findMemberCountByMonths(java.util.List)接口定义:List
findMemberCountByMonths(List 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.1 添加预约设置
dubbo> ls -l OrderSettingServicevoid add(java.util.List)接口定义:void add(List
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 OrderSettingServicejava.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 OrderSettingServicevoid 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 UserServicecom.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))
抓取接口数据,分析bug是前端还是后端。
远程调用的 7个dubbo接口 存在的问题:
1. 代码有 大量冗余
2. 测试接口时,除了要给 测试数据之外, 还需要 指定 服务名、方法名
3. 传参时,除了要考虑测试数据外,还要分析是否要添加 class 字段 及 对应数据。
4. 返回的数据类型统一为 string(不具体)
封装目标
1. 只关心:测试数据、响应结果
2. 返回的结果 分别为 不同的 具体类型。
from dubboclient import DubboClientclass BaseService(object):def __init__(self):self.dubbo_client = DubboClient("211.103.136.244", 6502)
3.1 会员服务封装
"""
类名:MemberService,继承于 BaseService
实例属性:服务名称:service_name,赋值为 'MemberService'
实例方法:def __init__(self):# 先调父类__init__(),再添加实例属性 service_namedef find_by_telephone(self, telephone):# 功能:根据手机号查询会员信息# :param telephone: 手机号# :return: 1. 会员存在,返回会员信息 2. 会员不存在,返回Nonedef 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 Noneelse:# 作用:将 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 Trueelse:return Falseif __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_namedef 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, 设置失败返回Falsedef 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, 设置失败返回 Falseresp = self.dubbo_client.invoke(self.service_name, "add", date_list)if resp == "Failed":return Falseelse:return Truedef 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 Noneelse: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, 修改失败返回 Falsedate["class"] = "com.itheima.pojo.OrderSetting"# 3. 调用 invoke 传 服务名、方法名、实参。得响应结果resp = self.dubbo_client.invoke(self.service_name, "editNumberByDate", date)if resp == "Failed":return Falseelse:return Trueif __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_namedef 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 Noneelse:return json.loads(resp)if __name__ == '__main__':# 管理员用户名name = "李白"us = UserService()resp = us.find_by_user_name(name)print("响应结果 =", resp)print("type(resp) =", type(resp))
import unittest
# 借助 unittest 框架,封装测试类,从 TestCase 继承
from day02.py02_会员服务类封装设计 import MemberServiceclass TestFindByTelephone(unittest.TestCase):ms = None@classmethoddef 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)
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@classmethoddef 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)
# 导包
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)