目录
一、传智健康项目介绍
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)