测试技能提升HM-接口测试

10 篇文章 0 订阅
订阅专栏

接口测试理论

概念:
接口:系统之间(外部系统与内部系统。内部系统与内部系统)数据交换的通道
接口校验:校验接口回发的响应数据与预期结果是否一致
在这里插入图片描述

在这里插入图片描述
接口测试:绕过前端界面,直接对服务器进行测试

价值:
可以发现⻚⾯测试发现不了的问题
符合 质量控制前移理念
低成本,⾼收益!

实现方式:
⼯具:
postman:使⽤简单,上⼿难度低。功能较少。
jmeter:使⽤难度较⼤。上⼿难度⼤。功能⻬全。
代码:
Python + requests + Unittest
java + HttpClient

http协议

协议:
就是规则,要求使用协议的双方必须严格遵守
http协议
超文本传输协议,基于请求和响应的应用层协议
特点:
1、客户端、服务器模式
2、简单快速
3、灵活
4、无连接
5、无状态

URL格式

URL:统一资源定位符,网络资源地址,http通过URL来建立连接和传输数据
URL组成:
协议 : // hostname[:port] / path / [? 查询参数1 & 查询参数2]
协议:
http、https作用:指定数据传输规则
IP地址:
域名,在网络环境中唯一定位一台主机
端口号:
在主机上,唯一定义某个应用程序
可以省略,如果省略,跟随协议,http-80,https - 443
资源路径:
应用对应的数据资源
可以省略,如果省略了,资源路径为/
查询参数:
给资源传递参数
可以省略,如果省略,没有?分割符
可以有多组,每组k = v格式,各组之间&隔分

练习:
http://tpshop-test.itheima.net/index.php?m=Home&c=User&a=do_login
协议:http
端口:80
域名:tpshop-test.itheima.net
资源路径:index.php
查询参数:m=Home&c=User&a=do_login

http请求

产生端:一定产生于客户端,当客户端给服务器发送请求时,使用该协议(请求报文,请求包)
整体格式
请求行:请求方法、url、协议版本
请求头:k:v
空行:代表请求头结束
请求体:发送给服务器请求时,携带的数据
在这里插入图片描述

POST http://demo.zentao.net/user-login.html HTTP/1.1
Host: demo.zentao.net
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101
Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://demo.zentao.net/user-login.html
Content-Type: application/x-www-form-urlencoded
Content-Length: 54
Connection: keep-alive
Upgrade-Insecure-Requests: 1
account=demo&password=efc4a3b32e48054865e5a8321cfda3e4

请求行
位置:⼀定位于 http请求协议的,第⼀⾏
格式:请求⽅法(空格) URL(空格) 协议版本
作用:说明请求方法、访问的资源、协议版本

POST http://demo.zentao.net/user-login.html HTTP/1.1

常见的请求方法:
GET:查询–没有请求体(从服务器获取资源)
POST :添加(注册、登录)-从服务器新建一个资源
PUT:修改-从服务器更新资源
DELETE:从服务器删除资源

协议版本:常见 HTTP/1.1

请求头
位于请求⾏之下,空⾏之上的部分。 数据组织格式 ⼀定是 k:v 对
作用:通知服务器客户端请求信息
Content-Type:作用,指定请求体的数据类型
text/html: HTML格式
 text/plain:纯文本格式
 image/jpeg:jpg图片格式
 application/json: JSON数据格式
 application/x-www-form-urlencoded: 表单默认的提交数据格式
 multipart/form-data: 在表单中进行文件上传时使用

请求体
位置:位于空行之下
作用:传输数据实体
有的请求是没有请求体的,如:get、delete
请求体常在post、put方法中使用
请求体的数据类型, 受 请求头中 Content-Type 的值影响

练习:
以下是抓取的某个HTTP协议数据包的请求报⽂,请问其中,请求⾏,请求头,请求体分别是什么?

POST ://tpshop-test.itheima.net/index.php HTTP/1.1   # 请求行
Host: tpshop-test.itheima.net   # 请求头
Connection: keep-alive
Content-Length: 53
Accept: application/json, text/javascript, */*; q=0.01
Origin: http://tpshop-test.itheima.net
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90
Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://tpshop-test.itheima.net/Home/user/login.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: is_mobile=0; province_id=1; city_id=2; district_id=3;
is_distribut=1; PHPSESSID=1b80tii77mhle0fqd2bg52ucg5; cn=0
username=13800138006&password=123456&verify_code=8888

post请求无请求体

练习2
抓包获取 ihrm系统的登录接⼝ 和 tpshop登录接⼝,解析请求数据
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

http响应

产生端:一定产生于服务端,当服务器收到http请求后,才会产生http响应协议(响应报文、响应包)
整体格式:
响应⾏:协议版本、状态码、状态码描述
响应头:K:V 格式数据。
空⾏:代表响应头 结束。
响应体:服务回发给客户端的 数据。⼏乎 所有的 响应包,都有响应体。
在这里插入图片描述

HTTP/1.1 200 OK
Date: Fri, 22 May 2009 06:07:21 GMT
Content-Type: text/html; charset=UTF-8
<html>
 <head></head>
 <body>...</body>
</html>

响应行
协议版本、状态码、状态码描述
位置:响应数据的第一行
作用:描述服务器处理结果
内容:状态行由协议版本号、状态码、状态消息组成

HTTP/1.1 200 OK

状态码由三位数字组成,第一个数字定义响应类别:

 1xx:指示信息
 2xx:成功
 3xx:重定向
 4xx:客户端错误
 5xx:服务器端错误
常见的响应状态码:
在这里插入图片描述
响应头
位于响应行之下,空行之上的部分。数据组织格式是k:v对
作用:描述客户端要是用的一些附加信息

响应体
位置:响应数据空白行之后
作用:服务器返回的数据实体
特点:有图片、json、xml、html等多种类型
响应体中 包含的数据,是接⼝测试过程中,所要使⽤的 实际结果!!

练习:
以下是HTTP协议的响应报⽂内容,请问,状态⾏、响应头、响应体分别是哪⼏个部分?

HTTP/1.1 200 OK   # 状态行
Server: nginx     # 响应头
Date: Mon, 29 Jun 2020 03:36:28 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Set-Cookie: is_mobile=0; expires=Mon, 29-Jun-2020 04:36:28 GMT;
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Content-Length: 805
{"status":1,"msg":"\u767b\u9646\u6210\u529f","result": # 响应体
{"user_id":3338,"email":"","password":"519475228fe35ad067744465
c42a19b2","paypwd":null,"sex":0,"birthday":0,"user_money":"0.00
","frozen_money":"0.00","distribut_money":"0.00","underling_num
ber":0,"pay_points":100,"address_id":0,"reg_time":1590980161,"l
ast_login":1590980161,"last_ip":"","qq":"","mobile":"13800138006","level_name":"\u6ce8\u518c\u4f1a\u5458"},"url":""}

抓包获取ihrm系统的登录接⼝ 和 tpshop登录接⼝,解析响应数据
在这里插入图片描述

接口规范和测试流程

传统风格接口
接⼝统⼀采⽤ get/post 实现 所有操作。
URL 与 资源不是 ⼀⼀对应的。在 URL 中查看出,是何种操作
状态码统⼀返回 200
在这里插入图片描述
RESTful风格接口
RESTful是一种网络应用程序的设计风格和开发方式,并不是必须要遵守的标准,只是提供了一组设计原则和约束条件
接⼝使⽤的⽅法,与 http协议的 请求⽅法,⼀⼀对应。
get - 查、post - 增、put - 改、delete - 删
URL 与 资源 ⼀⼀对应!不能从 URL 中,看出 是 何种操作。 需要通过 结合 请求⽅法 来识别何种操作。
响应状态码 使⽤较为全⾯
在这里插入图片描述
总结:
传统⻛格接⼝:只⽤ get、post⽅法。 URL 不唯⼀。 统⼀返回 200
RESTful⻛格接⼝:URL 唯⼀,定位资源。结合 请求⽅法对应不同操作。 返回状态码 较灵活
在这里插入图片描述

接口测试流程

  1. 需求分析(产品经理的需求⽂档)
  2. 接口文档解析(开发编写的 接⼝API⽂档)
  3. 设计 接⼝测试⽤例(编写 Excel 表格形式的⽤例)
  4. 准备接⼝测试脚本
    postman 工具生成脚本
    python 代码 编写脚本
  5. 执行测试⽤例,跟踪缺陷
  6. 生成接口测试报告
  7. 接口自动化化持续集成(可选)
    . 在这里插入图片描述

接口文档解析

解析接口文档
核心目标
1、请求报文关键数据
请求方法、URL、请求数据(请求头、请求体)
2、响应报文关键数据
状态响应码、响应数据(响应体)

postman的基础使用

安装
1、下载https://www.postman.com/downloads/
2、安装
双击Postman安装包,安装过程全⾃动,不需要任何⼈为⼲预。
安装完成,默认打开英⽂注册⻚⾯ (如没有跳转,⼿动进⼊注册⻚⾯)
注意
Postman⼀旦安装成功,不要轻易卸载!
不要轻易卸载!不要轻易卸载!不要轻易卸载!
Postman有BUG,默认不⽀持同⼀版本重复安装。
如果重复安装,新安装的版本号 必须 ⼤于已卸载版本才⾏。否则⽆法安装成功
在这里插入图片描述
填写:邮箱、⽤户名、密码,完成注册。注意⽤户名要复合Postman要求的规范
(经常会因为⽤户名已存在,注册失败,重新改换⽤户名,再重新注册)。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
启动 postman,使⽤⾃⼰注册的账号登录。
页面大致如下(可能略有不同)。
在这里插入图片描述
注册成功后,只能在浏览器中打开postman,⽽不能在 app中打开,可尝试如下操作。

  1. 在 app 中 点击 “sign in

在这里插入图片描述
2. 跳转⾄浏览器后,在⻚⾯选择你⾃⼰的账号在这里插入图片描述
3. 在 弹出的⻚⾯中,选择在 postman 这个 app 中打开
在这里插入图片描述
4. 成功使⽤账号密码登录,会在 app 有响应图标提示
在这里插入图片描述

安装Postman插件newman

要想给 postman 安装 newman 插件,必须 先 安装 node.js。 这是前提!

  1. 安装node.js
    可能你使⽤的电脑,曾经安装过 node.js。先测试下,有没有。
    cmd 打开命令提示符,输⼊命令 npm -v,如果能查看到npm的版本号信息(具体版本号是多少⽆所谓),
    可跳过 “1. 安装node.js” 这步,直接看 “
  2. 安装 newman
    安装newman前,必须保证 node.js 已经装成功!!!
    在线安装命令:npm install -g newman
    注意:使⽤ “管理员” 身份,启动 命令提示符,写⼊命令。
    如果安装失败。避开⽹络访问⾼峰期(早7:30~9:30 午11:30~14:30 晚18:30~22:30)再安装,尝试 10⼏次 或 ⼏
    ⼗次 也是有可能的!
    注意:Windows 终端 有缺陷!安装过程中,不要⽤⿏标 点击 终端⻚⾯(别⽤⿏标点⿊窗⼝⾥⾯)。
    在这里插入图片描述
    安装newman-reporter-htmlextra
    安装命令:npm install -g newman-reporter-htmlextra
    注意:使⽤ “管理员” 身份,启动 命令提示符,写⼊命令。
    只要 newman 安装成功,此插件⼀定能安装成功,如失败,可多尝试⼏次。
    在这里插入图片描述
    扩展
    Git安装
    1、下载安装包
    在这里插入图片描述
    2、双击安装
    3、一路next,全部使用默认选项,不建议修改安装目录位置,否则后面使用时pycharm会找不到
    在这里插入图片描述
    4、查看是否安装成功,cmd打开终端。输入git --version看到版本信息,说明安装成功
    5、必须给git配置用户名和油箱,双引号包裹
git config --global user.email "邮箱名称@xxx.com"
git config --global user.name "有户名(可任意,不要写中文)

在这里插入图片描述
注册Gitee码云账号
1、使用163邮箱作为用户名,注册云账号,并激活https://www.git.com
在这里插入图片描述
新建仓库:
在这里插入图片描述
在这里插入图片描述
能创建仓库,说明注册成功
在这里插入图片描述看到自己注册的邮箱,说明注册成功
在这里插入图片描述

持续集成

概念:
团队成员将自己的工作成果,持续集成到一个公共平台的过程。成员可以每天集成一次,也可以一天集成多次。
相关工具:
本地代码管理:git
远程代码管理:gitee(国内)、github(国外)、gitlib(公司私有服务器)
持续集成:jenkins

git

简介和安装

安装:
双击 exe文件,一路 next 自动安装,全部默认选项。 (不建议修改默认安装目录)
查看:
cmd打开终端,输入 git --version 能看到版本信息,说明安装成功
配置:
安装成功后,打开 cmd 输入命令。 这一步必须操作!!! 操作后无返回结果!!!
git config --global user.email “你⾃⼰的邮箱名@xxx.com”
git config --global user.name “⽤户名(可任意写,不要⽤中⽂)”

简介:
git 是一款 在 本地 管理代码的工具。 自带一个小型 仓库,存储本地代码。

Gitee

注册网易邮箱
注册 Gitee 账号

新建仓库

在这里插入图片描述
在这里插入图片描述

git 和 gitee 管理代码工作原理
在这里插入图片描述

PyCharm 配置 Gitee 插件

在这里插入图片描述
PyCharm 与 Gitee 相关操作

  1. 将 Gitee的项目 Checkout到 Pycharm中
    使用场景:
    第一次加入某个项目,第一次从 gitee 获取代码时。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  2. 推送 PyCharm 新项目到 Gitee远程仓库
    应用场景:
    本地开发的项目,第一次上传到 gitee 中。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  3. 将 Pycharm代码 push到 Gitee远程仓库
    应用场景:
    本地 和 gitee 有 相同的项目。 本地代码做了新增。需要将新增的代码,推送gitee上。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  4. 将 Gitee仓库的新代码 pull 到 PyCharm中
    应用场景:
    本地 和 gitee 有 相同的项目。 gitee上代码做了新增。需要将新增的代码,拿到本地来。
    在这里插入图片描述
    在这里插入图片描述

  5. 解决冲突

应用场景
本地 和 gitee 有 相同的项目。 gitee上代码做了新增。本地对同一处代码,做了不同的新增
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

jenkins

简介、安装、启动

简介:
基于Java开发(必须安装jdk)的一种开源、跨平台的持续集成工具
必须 安装 jdk,要配置 环境变量。
查验:java -version 能看到 1.8 版 jdk 即可。
启动:

  1. 在 jenkins.war 文件 所在 目录地址栏 输入 cmd 打开 终端。
  2. 输入命令 java -jar jenkins.war 启动服务。
  3. 启动成功后,终端窗口 不能关闭。最小化。
  4. 在浏览器地址栏 输入 localhost:8080
    插件安装介绍
    Jenkins左侧菜单栏 —> “Manage Jenkins” —> 选择 “Manage Plugins” —> “可选插件” —> 插件名称
    如:“HTML Publisher”
    注意:不要随意更新为最新版,可能出现不兼容的问题。

系统设置

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
配置小结:

  1. Manage Jenkins —> Configure System
  2. Jenkins Location:
    系统管理员邮件地址: —— 自己申请的邮箱。 (黄色警告,不理)
  3. Extended E-mail Notification:(第一个“高级”)
    注意:不要随意更新为最新版,可能出现不兼容的问题。
    操作细节见《 Jenkins安装及配置.pdf》中 “配置Jenkins系统邮箱” 小节。
    SMTP Username: —— 自己申请的邮箱
    SMTP Password: —— POP3/SMTP 服务授权码。
  4. 邮件通知:(勾选“使用SMTP认证”)
    用户名:—— 自己申请的邮箱
    密码: —— POP3/SMTP 服务授权码。
  5. 点击 “应用” —> “保存

持续集成postman

准备工作

  1. 打开已完成并测试无误的 postman 项目脚本。 再次执行测试。
  2. 导出( 测试用例集、环境变量 两个文件)“不 支 持 中 文” —— 全部改成英文!
  3. 文件所在目录地址栏 输入 cmd 打开终端。注意:用 “绝对路径” 测试。方便使用 Jenkins。
  4. 执行无误, 查看生成的测试报告文件
# extra版报告
newman run 测试用例集名.json -e 环境变量文件.json -d 数据文件.json -r htmlextra --reporterhtmlextra-export 报告名称.html
# 示例:
newman run "C:\Users\xwp\Desktop\postman_jenkins\iHRM.postman_collection.json" -e
"C:\Users\xwp\Desktop\postman_jenkins\iHRM_env.postman_environment.json" -r htmlextra --
reporter-htmlextra-export report.html
# 示例:
newman run "C:\Users\xwp\Desktop\ihrm\ihrm.postman_collection.json" -e
"C:\Users\xwp\Desktop\ihrm\postman_environment.json" -r htmlextra --reporter-htmlextra-export
report.html

在这里插入图片描述

使用jenkins管理-手动构建

操作步骤:

  1. 打开 Jenkins 首页,点击 “新建Item” 创建一个 新任务
  2. 输入任务名,如:bjtestAPITestIHRMPostman。选择 “Freestyle project”,点 “确定” 。跳至 “配置”页面。
  3. 回主页,可看到 ,多出任务 bjtestAPITestIHRMPostman。 点 任务名称,“配置” 可以继续刚才的配置。
  4. 跳至 “构建” 标签。(General 、源码管理、构建触发器、构建环境 四个标签先跳过)
  5. 点击 “增加构建步骤”,选择 “Execute Windows batch command” 选项(macOS选择 “Execute shell”
  6. 将 cmd 终端 测试无误的 命令, 粘入“命令” 编辑框中(如有红色浪线警告,忽略)。
  7. “构建后操作” 标签。
  8. 点击 “增加构建后操作步骤”,选择 “Pulish HTML reports”,点击 “新增” 按钮。
  9. 将 Index page[s] 后的值改为:“report.html” 。名称 应与上面 命令中 生成的 测试报告名称 一致。

说明:
因为生成报告时,没有指定目录。 所以:上面 “HTML directory to archive” 是空的。
如果,指定报告生成到其他位置。 要配置 “HTML directory to archive”的值。

  1. Report title 是生成的报告 标题,可修改为 “Report666” 试试看。

6.点击应用–>保存
11. 在 自动跳至 页面中,点击 “Build Now”,可在下面 Build History(构建历史)中, 看到构建正在进行。
12. 点 #1 后面的名称 , 点击 “控制台输出” 查看 执行的命令。 2. 再次点 “Build Now”,可以 再构建一次 #2。 以此类推 8. 完成后,左侧菜单中 多出 “Report666” 菜单栏。点击可查看 测试报告。可能会样式错乱。后续解决。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
使用Jenkins管理-自动构建
简单来说:自动构建,就是设置一个定时器,定时时间到, Jenkins 自动执行测试用例。
操作步骤:

  1. Jenkins 首页,点击任务名:如: bjtestAPITestIHRMPostman。跳至 “配置” 页面。
  2. “构建触发器” 标签。 选择 “Build periodically(定期地)”
  3. “日程表” 中写入:* * * * *(空格隔分) —> 分别对应 “分 时 日 月 年”
    修改:10 16 * * * 就是 每天的 16 点 10 分 执行。
  4. 点击 “应用” --> “保存”。 等待 。。。自动构建 —— 成功!
  5. 报告样式错乱 原因:
    Jenkins为了避免受到恶意攻击,设置了CSP安全策略。只允许加载 Jenkins服务器上托管的 CSS文件 和
    图片文件。
    我们的用例、代码都是放在自己的服务器上,被恶意攻击的可能性极低。
  6. 解决 报告样式错乱:
  7. 在 启动时,添加参数 :java -Dhudson.model.DirectoryBrowserSupport.CSP= -jar Jenkins.war 2. 如果,已生成的报告,样式依然错乱, 重新 “Build Now” 生成新的报告即可。 在这里插入图片描述
    在这里插入图片描述
    持续集成-代码
    准备工作
    将 运行无误,能生成报告的 iHRM项目代码 上传至 Gitee中。
    在这里插入图片描述
    使用Jenkins管理-手动构建
    1. 打开 Jenkins 首页, 点击 “新建Item” 创建一个新任务。
    2. 输入任务名,如: bjtestAPITestIHRMCode。选择 “Freestyle project”,点 “确定”,跳至 “配置” 页面。
    3. “源码管理” 标签。 选择 “Git”。 在 Repository URL 后写入 项目代码在 Gitee的 URL。
    4. “构建” 标签。
    5. 点击 “增加构建步骤”, 选择 “Execute Windows batch command” 选项(macOS选择 “Execute shell”
    6. 输入命令 python run_suite.py ( 与在 pycharm 的 Terminal 中执行,相同含义)
    7. “构建后操作” 标签
    8. 点击 “增加构建后操作步骤”,选择 “Pulish HTML reports”,点击 “新增” 按钮。
    9. 在 “HTML directory to archive” 中 写入 报告生成的位置。 如:./report (与项目目录一致)
    10. Index page[s] 后的值,与 run_suite.py 中代码,生成的测试报告名称保持一致。如:ihrm.html。
    11. 再次点击 “增加构建后操作步骤”, 选择 “Editable Email Notification” 设置 邮件发送测试报告。
      1. 在 “Project Recipient List” ,$DEFAULT_RECIPIENTS 后使用 英文 “,” 隔分,添加邮箱地址。
      2. 下面 “Content-Type” 的值,选择 HTML(text/html)
      3. 复制讲义中 “邮件测试报告模板” 代码到 “Default Content ” 中
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志</title>
</head>
<body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4" offset="0">
<div>
<h2>项目信息</h2>
<ul>
<li>项目名称:${PROJECT_NAME}</li>
<li>详细测试报告:<a
href="${PROJECT_URL}HTML_20Report/">${PROJECT_URL}HTML_20Report/</a></li>
<li>触发原因:${CAUSE}</li>
<li>项目Url:<a href="${PROJECT_URL}">${PROJECT_URL}</a></li>
</ul>
<hr/>
<h2>构建日志</h2>
<div>${JELLY_SCRIPT,template="html"}</div>
<hr/>
</div>
</body>
</html>
	7. 点击右下角 “Advanced Settings” 按钮。将 “Trigger” 原有的内容 点 最外层 “红叉” 删除。
	点击 “Add Trigger” 选择 Always 。
	8. 点击 “应用” --> “保存”。
	9. 点击 “Build Now” 开始手动构建。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
使用Jenkins管理-自动构建

  1. 点击 “配置” 查看,在 “构建触发器” 标签。 选择 “Build periodically(定期地)” 添加 定时构建!
  2. “日程表” 中写入:* * * * *(空格隔分) —> 分别对应 “分 时 日 月 年”
    修改:10 16 * * * 就是 每天的 16 点 10 分 执行。
  3. 也可以在 “构建触发器” 标签 中选择 “Poll SCM”。 写入 :*/1 * * * * (空格隔分)
    代表1分钟 检查一次 gitee 上的代码,查验是否有更新。
  4. PyCharm 修改代码 Commit、Push,或者 直接在 Gitee修改,导致 Gitee上的代码有 变动。会触发Jenkins
    会自动构建。
    在这里插入图片描述
    在这里插入图片描述

postman基础使用

传递查询参数
访问TPshop搜索商品的接⼝,搜索关键字iPhone,并查看响应数据
1、创建新用例集
在这里插入图片描述
2、用例集上右键,添加请求
在这里插入图片描述
3、在下拉列表中选择请求方式
在这里插入图片描述
在这里插入图片描述
案例二
【提交表单数据】:使⽤ Postman 向 tpshop 商城 登录接⼝ 发送 登录请求
在这里插入图片描述
响应数据
在这里插入图片描述案例三
post发送登录请求【发送json数据】
在这里插入图片描述

接口用例设计

接口测试的测试点:
接口测试维度
功能测试
单接⼝功能测试:
⼀个单独的业务,就对⼀个独⽴的接⼝。如:登录业务,对应登录接⼝。

业务场景功能测试:
多个接⼝被连续调⽤。(模拟⽤户的实际使⽤场景)

性能测试
响应时⻓:从发送请求到接收到服务器回发响应包所经历的时间。
错误率:服务器运⾏出错的概率
吞吐量:服务器单位时间内,处理请求的数量。
服务器资源利⽤率:cpu、内存、⽹络、磁盘等 硬件资源的占⽤率

安全测试
攻击安全:木马、病毒…
由具备专业安全技术,会使⽤专业安全测试⼯具的 安全测试⼯程师 负责。

业务安全:
必须登录,才能访问 ⽤户数据。
敏感数据加密存储
SQL注入

接口用例设计方法

单接口测试
⼀个单独的业务,就对⼀个独⽴的接⼝。如:登录业务,对应登录接⼝。注册业务,对应注册接⼝。⽀付业务,对应⽀付接⼝

正向:
必选 参数。 所有必选项,给正确数据
组合 参数。 所有必选 + 任意可选,给正确数据。
全部 参数。 所有必选 + 所有可选。给正确数据。
反向:
功能异常:数据格式正确,不能履⾏接⼝功能。
数据异常:数据格式不正确(空值、特殊字符、汉字、字⺟、⻓度、范围 ---- 等价类、边界值)
参数异常:
多参:多出 必选参数
少参:缺少 必选参数
⽆参:没有 指定参数
错误参数:参数名 错误

业务场景测试
⼀定在单接⼝测试 之后!
尽量模拟⽤户实际使⽤场景。
尽量⽤最少的⽤例,覆盖最多的接⼝请求。
⼀般情况下,覆盖正向测试即可

单接口测试用例案例
登录
正向:
登录成功
反向:
功能异常:
⼿机未注册
密码错误
数据异常:
⼿机号为空
⼿机号含有字⺟、特殊字符
⼿机号12位
⼿机号10位
密码为空
密码含有字⺟、特殊字符
密码1位
密码100位
参数异常:
多参:多出abc
少参:缺少-mobile
⽆参:
错误参数:修改 mobile 为 abc

业务场景用例
分析测试点
指导思想:模拟⽤户实际使⽤,⽤较少的测试⽤例,覆盖更多接⼝,测试正向即可。
登录 - 添加员⼯ - 查询员⼯ - 修改员⼯ - 删除员⼯ - 查询员⼯列表
在这里插入图片描述

postman常用断言

断言状态响应码

status code: Code is 200

//断言状态响应码为200
pm.test("Status code is 200", function () {
    pm.response.to.have.status(200);
});

pm:postman的实例
test() postman实例的测试方法。这个方法有两个参数
参数1:Status code is 200 这个参数可以随便修改,不影响断言
作用:在断言结束后,显示给用户,断言结果的提示文字
参数2:是一个匿名函数调用

pm.response.to.have.status(200);
意思是响应的结果中,应该有响应状态码200 —这里的200是预期结果

在这里插入图片描述

断言包含某字符串

response body :contatins string

pm.test("Body matches string", function () {
    pm.expect(pm.response.text()).to.include("string_you_want_to_search");
});

pm:postman的实例
test() postman实例的测试方法。这个方法有两个参数
参数1:“Body matches string” 这个参数可以随便修改,不影响断言
作用:在断言结束后,显示给用户,断言结果提示的是文字
参数2:是一个匿名函数调用

pm.expect(pm.response.text()).to.include(“string_you_want_to_search”); 的意思是:
postman 期望 响应文本中,应该包含 “你想搜索的字符串”(预期结果)
在这里插入图片描述

断言json数据

response body:json value check

pm.test("Your test name", function () {
    var jsonData = pm.response.json();
    pm.expect(jsonData.value).to.eql(100);
});

pm:postman的实例。
test() postman实例的测试方法。 这个方法 有 2 个参数。
参1:“Body matches string”。 这个参数可以任意修改,不影响 断言。
作用:在断言结束后,显示给用户,断言结果的提示文字。
参2:是一个 匿名函数 调用。
var jsonData = pm.response.json(); 将 整个 json响应体 赋值到 变量 jsonData 上。
pm.expect(jsonData.value).to.eql(100); postman 期望 json结果中 指定key 的值为 xxx
value 能取值:success、code、message
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

postman断言工作原理

在这里插入图片描述

postman关联

简介:当接口和接口之间,有依赖关系时,需要借助 postman 关联技术,来实现。
如: 登录接口 返回的 令牌数据,被 添加员工接口依赖。
添加员工接口 返回 员工id,被 查询员工接口依赖。

实现步骤
假定:接口B 产生的数据,被 接口A 依赖
1、发送接口B请求,获取响应数据
2、将响应数据,放到公共容器(全局变量、环境变量)中
3、接口A从容器中,提取数据、发送请求
在这里插入图片描述
核心代码:
在这里插入图片描述

代码

//1、获取响应数据,转为json格式,保存到变量jsondata中
var jsonData = pm.reponse.json
//2.1 使用全局变量作为容器
pm.global.set("全局变量名",全局变量值)
//2.2 使用环境变量做容器
pm.environment.set("环境变量名",环境变量值)
//3.在postman界面中(URL、请求头header、请求体body)提取全局变量、环境变量数据
{{全局变量名}}{{环境变量名}}

创建环境

全局变量:在 整个postman中都可以使用的变量。不需要 单独创建环境。
环境变量:在 特定的环境下,才能使用的变量。需要给此变量创建单独的环境。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述1. 使用 postman 关联,实现下面案例
从获取天气接口,http://www.weather.com.cn/data/sk/101010100.html
获取返回结果中的城市名称
调用百度搜索接口: http://www.baidu.com/S?wd=北京 ,把获取到的城市名称,如:北京,作为请求参数

思路:

  1. 发送 获取天气请求,获取响应结果
  2. 从响应结果中,拿到城市名,存入 全局变量
  3. 百度搜索接口从 全局变量中,取城市名,发送搜索请求。
//1、获取响应结果
var jsonData = pm.reponse.json()
//2、 在响应结果中,提取城市名
 var city = jsonData.weatherinfo.city
//3、 将城市保存到全局变量中
pm.globals.set("glb_city",city)

在这里插入图片描述
在这里插入图片描述
案例2
使用 postman 关联技术,实现 添加员工 接口。
登录成功,返回的 “令牌” 被 添加员工 接口依赖。
思路:

  1. 发送登录请求(必须登录成功),获取响应结果
  2. 从 json 响应结果中,提取 data 值。拼接上 “Bearer ” 前缀。
  3. 将拼接无误的 令牌,存入 环境变量。 从 “眼睛” 图标查看。
  4. 添加员工 接口,从 环境变量 中,提取 令牌。设置到请求头中,作为 Authorization 的 值。
  5. 填写 添加员工 接口 其他信息(post、URL、请求体),发送请求。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

postman参数化

简介
什么是参数化:
将 测试数据,组织到数据文件中,通过脚本的反复迭代,使用不同的数据,达到测试不同用例的目标。
应用场景:
一般在测试同一个接口的不同 测试点时,只有测试数据不同。考虑使用 参数化。

数据文件简介
CSV:
优点:数据组织格式简单
缺点:

  1. 不能测试 bool 类型。因为 postman 读取 csv后,将所有非数值类型数据,自动添加 ”“ 变为字符串
  2. 不能存储复杂数据类型(元组、列表、字典)。
  3. 不能实现 参数测试。
    应用场景:数据量较大,数据组织格式简单
    在这里插入图片描述
    JSON:
    优点:
  4. 可以测试 bool类型
  5. 能使用 复杂数据类型
  6. 可以实现 参数测试。
    缺点:相同数据量,json文件要远大于 csv 文件。
    应用场景:数据量较少,数据组织格式复杂。需要进行 参数测试!
    在这里插入图片描述

编写数据文件

CSV文件
在这里插入图片描述
json文件
在这里插入图片描述
导入数据文件到postman中
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

读取数据文件数据

理论
根据会用位置不同,有两种方法
第一种:请求参数(请求行、请求头、请求体)中,使用 数据文件中 的数据
csv文件:{{字段名}}; json文件:{{键名}}

第二种:代码(Tests)中,使用 数据文件中 的数据
使用 postman 内置的 关键字 data,索引 字段名 或 键名
csv文件:data.字段名; json文件:data.键名

案例

需求:批量查询 手机号 所属运营商,校验运营商数据正确性
接口: http://cx.shouji.360.cn/phonearea.php?number=13012345678
测试数据:
手机号: 13012345678 运营商: 联通 手机号: 13800001111 运营商: 移动 手机号: 18966778899 运营商: 电

在这里插入图片描述
在http请求页中使用了数据文件中国的关键字,不能在使用send发送请求
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

postman控制台调试

在这里插入图片描述

json文件参数化

在这里插入图片描述

安装postman插件newman

导出用例集

导出
在这里插入图片描述
在这里插入图片描述
扩展,导入用例集
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

导出环境文件

如果,测试用例脚本中,包含 环境使用。必须要导出 环境文件!!!
在这里插入图片描述
在这里插入图片描述

newman生成测试报告
完整命令

newman run 用例集文件.json -e 环境文件.json -d 数据文件.json/.csv -r htmlextra --reporterhtmlextra-export 测试报告名.html
-e 和 -d 是 非必须的。
如果没有使用 环境,不需要指定 -e
如果没有使用 数据文件(做参数化),不需要指定 -d

实例1:指定用例集和数据文件

在这里插入图片描述
示例2:指定用例集文件和环境文件
在这里插入图片描述
环境安装检查
在这里插入图片描述

案例练习

初始化项目环境
新建用例集
在这里插入图片描述
创建环境
在这里插入图片描述
登录模块
登录成功接口
请求接口
在这里插入图片描述
添加断言
在这里插入图片描述
其他接口共性分析

  1. 由于是同一个接口,因此:请求方法、URL、请求头 完全一致。
  2. 测试点(测试用例名称)、和 请求数据(请求体),各不相同
  3. 响应结果(用作断言),共 3 种情况
    1. 操作成功
    2. 用户名或密码错误
    3. 抱歉,系统繁忙…
      实现其他接口
      在这里插入图片描述
      员工管理业务场景
      总析
      共有两种依赖:
  4. 登录成功的 令牌, 被 添加、修改、删除、查询 接口依赖。
  5. 添加员工成功 得到的 员工id,被 修改、删除、查询 接口依赖。

提取令牌
代码写在 “登录成功”接口请求 的 Tests 标签页中
在这里插入图片描述
添加员工
注意:

  1. 登录的令牌,在 请求头中使用
  2. 请求体中的手机号,要保证唯一。

在这里插入图片描述
在这里插入图片描述
提取添加员工的id
代码写在 “添加员工成功” 接口请求 的 Tests 标签页中

// 获取添加员工成功的 响应结果 json
var jsonData = pm.response.json()
// 提取 员工id
var emp_id = jsonData.data.id
// 设置到 环境变量
pm.environment.set("env_emp_id", emp_id)

查询员工
在这里插入图片描述
在这里插入图片描述
其他员工操作
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
批量运行测试用例
在这里插入图片描述
注意:业务场景,批量执行之前,必须修改 “添加员工”接口使用的手机号,否则 ,查询、修改、删除 都无法正
常批量运行。

在这里插入图片描述
生成测试报告
登录接口生成测试报告:

  1. 导出用例集文件。(没有使用环境,不需要导出环境文件)
  2. 使用命令 生成测试报告

newman run ihrm项目.postman_collection.json -r htmlextra --reporter-htmlextra-export ihrm登录接口
测试报告.html

带有业务场景接口,生成测试报告:

  1. 导出用例集文件。
    newman run ihrm项目.postman_collection.json -r htmlextra --reporter-htmlextra-export ihrm登录接口
    测试报告.html
  2. 必须 要导出 环境文件。
  3. 使用命令 生成测试报告

newman run ihrm项目.postman_collection.json -e 测试环境.postman_environment.json -r htmlextra –
reporter-htmlextra-export ihrm项目完整测试报告.html.

注意:由于添加员工时,手机号要求唯一! 因此上述生成报告的命令, 第二次+ 执行,生成的报告中,会有断
言失败

实战练习
创建环境
新建测试集,按照模块和分类进行划分不同的文件夹
在这里插入图片描述

request库

简介:
Requests库 是 Python编写的,基于urllib 的 HTTP库,使用方便
安装
方法1:

pip install requests

方法2:

pip install requests -i https://pypi.douban.com/simple/

豆瓣镜像:https://pypi.douban.com/simple/
加上-i参数添加镜像源
在这里插入图片描述
检验:
步骤一:pip 中查验
pip list

pip shw 库名

步骤二:pycharm中查验
在这里插入图片描述
在这里插入图片描述

设置http请求语法

res = requests.请求方法(url = “URL地址”,params={k:v},headers ={k:v},data= {k:v},cookies =‘cookies数据’{如:令牌})
请求方法:
get请求 --get()
post请求 --post()
put请求 --put()
delete请求 – delete()

url :待请求的url -string类型
params:查询参数 -字典
data :表单格式的 请求体 -字典
json:json格式的请求体 – 字典
cookies:cookie数据-string类型

入门案例:使用Requests库访问 百度 http://www.baidu.com

import requests
resp = requests.get(url="http://www.baidu.com")
print(resp.text)

应用案例

案例一
【带 查询参数 的get请求】使用Requests库,请求 tpshop商城 搜索商品接口。查询 iphone

import requests
# 发送get请求,指定url,获取响应结果
# 方法一
# resp =requests.get(url ="http://192.168.13.141/Home/Goods/search.html?q=iPhone")
# print(resp.text)

# 方法二
resp = requests.get(url ="http://192.168.13.141/Home/Goods/search.html",params={"q":"iPhone"})
print(resp.text)

在这里插入图片描述
案例二
【带表单数据 的post请求】使用Requests库,完成 tpshop商城 登录接口调用。返回 ”验证码错误“ 即可。

# 发送post请求,指定url,请求头,请求体,获取响应结果
import requests
resp = requests.post("http://192.168.13.141/index.php?m=Home&c=User&a=do_login&t=0.7094195931397276",headers={"Content-Type":"application/x-www-form-urlencoded"},
                     data={"username":"13012345678","password":"1234567","verify_code":"8888"})

# 打印响应结果-文本
print(resp.text)
# 打印响应结果-json
print(resp.json())

{“status”:0,“msg”:“\u9a8c\u8bc1\u7801\u9519\u8bef”}
{‘status’: 0, ‘msg’: ‘验证码错误’}

案例三
【带 json数据 的post请求】使用Requests库,完成 iHRM系统 成功登录。返回 ”令牌数据“。

import requests
# 发送 post 登录请求,指定 url、请求头、请求体,获取响应结果
resp = requests.post(url="http://ihrm-test.itheima.net/api/sys/login",
# headers={"Content-Type": "application/json"},
json={"mobile": "13800000002", "password": "123456"})
# 打印响应结果
print(resp.json())

案例四
【发送 put、delete请求】使用Requests库发送 ihrm系统 修改员工信息、删除员工信息 请求。

# -------- 修改 put
import requests
resp = requests.put(url="http://ihrm-test.itheima.net/api/sys/user/1467780995754229760",
headers={"Authorization": "Bearer 4c51c601-c3f7-4d1a-a738-7848f2439f45"},
json={"username": "齐天大圣"})
print(resp.json())
# -------- 删除 delete
import requests
resp = requests.delete(url="http://ihrm-test.itheima.net/api/sys/user/1467780995754229760",
headers={"Authorization": "Bearer 4c51c601-c3f7-4d1a-a738-7848f2439f45"})
print(resp.json())

cookie

简介
简介:工程师针对 http协议是无连接、无状态特性,设计的 一种技术。可以在浏览器端 存储用户的信息。
特性:
cookie 用于存储 用户临时的不敏感信息。
cookie 位于浏览器(客户端)端。默认大小4k(可以调整)
cookie 中的数据,可以随意被访问,没有安全性可言。
cookie 中存储的数据类型, 受浏览器限制。

Cookie+Session认证方式
在计算机中,认证用户身份的方式有多种!课程中接触 2种:
ihrm项目:token认证。
tpshop项目:cookie+Session认证
在这里插入图片描述
案例5
完整实现 TPshop商城登录,并获取 “我的订单” 页面数据。
获取验证码:http://tpshop-test.itheima.net/index.php?m=Home&c=User&a=verify
登录:http://tpshop-test.itheima.net/index.php?m=Home&c=User&a=do_login
我的订单:http://tpshoptest.itheima.net/Home/Order/order_list.html

import requests
# 发送验证码请求
resp_v = requests.get(url="http://192.168.13.141/index.php?m=Home&c=User&a=verify&r=0.0.21519623710645064")
# 从获取短信验证码的响应结果中,提取cookie
my_cookie = resp_v.cookies

# 发送登录请求url/请求头、请求体,携带cookie,得到响应结果
resp = requests.post("http://192.168.13.141/index.php?m=Home&c=User&a=do_login&t=0.7094195931397276", data={"username":"13012345678","password":"1234567","verify_code":"8888"},cookies = my_cookie)

print(resp.json())
# 发送查看我的订单请求
resp_o = requests.get(url ="http://192.168.13.141/Oreder/order_list.html",cookies =my_cookie)
print(resp_o.text)

session

简介
简介:也叫会话。通常出现在网络通信中,从客户端借助访问终端登录上服务器,直到 退出登录 所产生的通信数据,保存在会话中。
特性:
Session 用于存储用户的信息。
Session 位于服务端。大小直接使用服务器存储空间
Session 中的数据,不能随意被访问,安全性较高。
Session 中存储的数据类型,受服务器影响,几乎能支持所有的数据类型。

Session自动管理Cookie
因为 Cookie 中的数据,都是 Session 传递的。因此,Session可以直接自动管理 cookie
案例6
借助session重新实现 上述 TPshop商城登录,并获取 “我的订单” 页面数据。

实现步骤:

  1. 创建一个 Session 实例。
  2. 使用 Session 实例,调 get方法,发送 获取验证码请求。(不需要获取cookie)
  3. 使用 同一个 Session 实例,调用 post方法,发送 登录请求。(不需要携带 cookie)
  4. 使用 同一个 Session 实例,调用 get方法,发送 查看我的订单请求。(不需要携带 cookie)
import requests
# 1、创建一个session实例
session = requests.Session()

# 2、使用session实例,调用get方法,发送获取验证码请求(不需要获取cookie)
resp_v = session.get(url="http://192.168.13.141/index.php?m=Home&c=User&a=verify&r=0.0.21519623710645064")

# 3、使用同一个session实例,调用post方法,发送登录请求(不需要获取cookie)
resp = session.post(url="http://192.168.13.141/index.php?m=Home&c=User&a=do_login&t=0.7094195931397276", data={"username":"13012345678","password":"1234567","verify_code":"8888"})
print(resp.json())

# 4、使用同一个session实例,调用get方法,发送登录请求,发送查看我的订单请求
resp_o =session.get(url ="http://192.168.13.141/Oreder/order_list.html")
print(resp_o)

cookie和session的区别

  1. 数据存储位置:
    cookie存储在浏览器;session存储在服务器。
  2. 安全性:
    cookie中的数据可以随意获取,没有安全性可言。Session的数据多为加密存储,安全较高!
  3. 数据类型:
    cookie支持的数据类型受浏览器限制,较少;Session直接使用服务器存储,支持所有数据类型
  4. 大小:
    cookie大小默认 4k; Session 大小约为服务器存储空间大小

获取指定响应数据

常用:
获取url:resp.url
获取响应状态码:resp.status_code
获取cookie:resp.cookies
获取响应头:resp.headers
获取响应体:
文本格式:resp.text
json格式:resp.json()

import requests
resp = requests.get(url="http://www.baidu.com")

# 获取url
print("url =",resp.url)

# 获取响应状态码
print("status_code=",resp.status_code)

# 获取cookie
print("cookies=",resp.cookies)

# 获取响应头
print("header=")

# 获取响应体-文本格式
print("body_text=",resp.text)

# 获取响应体-json格式,当显示jsondecodeerror错误时,说明resp不能转换为json格式
#print("body_json =",resp.json())

在这里插入图片描述

UnitTest框架

UnitTest 是开发人员用来实现 “单元测试” 的框架。测试工程师,可以在自动化 “测试执行” 时使用。
使用 UnitTest 的好处:

  1. 方便管理、维护测试用例。
  2. 提供丰富的断言方法。
  3. 生成测试报告。(需要插件 HTMLTestReport)

TestCase

# 1、导包 import unittest
# 2、定义测试类从 TestCase类继承
class TestXXX(unittest.TestCase):
	pass
# 3、测试方法定义必须以test开头,建议添加编号
class TestXXX(unittest.TestCase):
	def test01_xxx(self):
		pass

Fixture

1、方法级别的setUp(self)tearDown(self)每个普通方法执行前后自动运行
2、类级别的setUpClass(cls)tearDownClass(cls)在类内所有方法之前、之后运行一次

TestSuite

1、实例化测试集对象 suite = unitest.TestSuite()
2、添加指定类的全部测试方法
suite.addTest(unittest.makeSuite(类名))

TestSuite 通过搜索创建测试集
suite = unittest.TestLoader().discover(搜索目录,搜索文件名)
suite = unittest.TestLoader().discover(“./”,"test*.py)

TestRunner

runner = HTMLTestReport(“./report1.html”,description=“描述信息”,title ="报告标题”)
runner.run(suite)

UnitTest框架基础代码回顾

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

示例

练习案例

登录成功

import unittest
import requests
# 定义测试类
class TestIhrmLogin(unittest.TestCase):
# 添加测试方法
def test01_login_ok(self):
# 发送 post 登录请求,指定 url、请求头、请求体,获取响应结果
resp = requests.post(url="http://ihrm-test.itheima.net/api/sys/login",
json={"mobile": "13800000002", "password": "123456"})
# 打印响应结果
print(resp.json())
# 断言- 响应状态码为 200
self.assertEqual(200, resp.status_code)
# 断言 success 的值为 true
self.assertEqual(True, resp.json().get("success"))
# 断言 code 的值为 10000
self.assertEqual(10000, resp.json().get("code"))
# 断言 message 的值为 操作成功!
self.assertIn("操作成功", resp.json().get("message"))

在这里插入图片描述
断言方法:
assertEqual(参1,参2) :
参1:预期结果。 参2:实际结果。
成功:完全相等。断言通过。不报错!
失败:报错!
assertIn(参1,参2):
参1:预期结果。参2:实际结果。
成功:实际结果中,包含预期结果。断言通过。不报错!
失败:报错!

数据库操作应用场景

检验测试数据
接口发送请求后明确会对数据库中的某个字段进行修改,但响应结果中无该字段数据时。
如:ihrm 删除员工接口。 is_delete 字段,没有在响应结果中出现! 需要 借助数据库校验!
构造测试数据
测试数据使用一次就失效。
如:ihrm 添加员工接口,使用的手机号!
测试前,无法保证测试数据是否存在。
如:ihrm 查询员工接口,使用的员工id

PyMySql操作数据库

安装
方法1:
pip install PyMySql

方法2:
pip install PyMySQL -i https://pypi.douban.com/simple/
操作步骤
在这里插入图片描述
1、导包 import pymysql
2、创建连接 conn = pymysql.connect(host,port,user,password,database,charset)
3、获取游标 cursor = conn.cursor()
4、执行sql cursor.execute(“sql语句”)
查询语句(select):
处理结果集(提取数据fetch*)
增删改语句(insert/update/delete)
成功:提交事务conn.commit()
失败:回滚事务conn.rollback()
5、关闭游标。cursor.close()
6、关闭连接。conn.close()

事务的概念

事务:是关系系数据库(MySQL)特有的概念
事务,可以看做一个虚拟的容器,在容器中存放一系列的数据库操作,看做一个整体。内部的所有操作,要么一次性成功,只要有一个失败,就全部失败
在这里插入图片描述
事务操作:只有两种情况
提交:conn.commit()
回滚:conn.rollback()

PyMySql连接数据库

建立连接方法
conn = pymysql.connect(host=“”, port=0,
user=“”, password=“”, database=“”, charset=“”)
host:数据库所在主机 IP地址 - string
port:数据库使用的 端口号 - int
user:连接数据库使用的 用户名 - string
password:连接数据库使用的 密码 - string
database:要连接的那个数据库的名字 - string
charset:字符集。常用 utf8 - string
conn:连接数据库的对象。

入门案例
查询数据库,获取MySQL服务器版本信息
在这里插入图片描述

# 1. 导包
import pymysql
# 2. 建立连接
conn = pymysql.connect(host="211.103.136.244", port=7061, user="student",
password="iHRM_student_2021", database="test_db", charset="utf8")
# 3. 获取游标
cursor = conn.cursor()
# 4. 执行 sql 语句(查询)
cursor.execute("select version()")
# 5. 获取结果
res = cursor.fetchone()
print("res =", res[0])
# 6. 关闭游标
cursor.close()
# 7. 关闭连接
conn.close()

pymsql操作数据库

sql语法回顾
查询语法:

select 字段1,字段2,… from 表 where 条件;
示例:select id,title, pub_date from t_book where title = ‘读者’;

添加语法:

insert into 表名(字段1, 字段2, …) values(值1, 值2, …);
示例:insert into t_book(id, title, pub_date) values(17, ‘红楼梦’, ‘2021-11-11’);

更新语法;

update 表名 set 字段名 = 字段值 where 条件
示例:update t_book set title = ‘三国’ where id = 17;

删除语法:

delete from 表名 where 条件
示例:delete from t_book where title = ‘三国’;

数据库查询
查询操作流程
在这里插入图片描述
cursor游标
在这里插入图片描述
常用方法
fetchone():从结果集中,提取一行。
fetchmany(size):从结果集中,提取 size 行。
fetchall():提取所有结果集。
属性rownumber:可以设置游标位置。

案例:
查询t_book表,获取 第一条 数据
查询t_book表,获取 前两条 数据
查询t_book表,获取 全部 数据
查询t_book表,获取 第3条和第4条 数据

# 1. 导包
import pymysql
# 2. 建立连接
conn = pymysql.connect(host="211.103.136.244", port=7061, user="student",
password="iHRM_student_2021", database="test_db", charset="utf8")
# 3. 获取游标
cursor = conn.cursor() # 指向 0 号位置。
# 4. 执行 sql 语句(查询)--- t_book
cursor.execute("select * from t_book;")
# 5. 获取结果 - 提取第一条
res1 = cursor.fetchone()
print("res1 =", res1)
# 修改游标位置:回零
cursor.rownumber = 0
# 5. 获取结果 - 提取前 2 条
res2 = cursor.fetchmany(2)
print("res2 =", res2)
# 修改游标位置:回零
cursor.rownumber = 0
res3 = cursor.fetchall()
print("res3 =", res3)
# 修改游标位置:指向第 2 条记录
cursor.rownumber = 2
res4 = cursor.fetchmany(2)
print("res4 =", res4)
# 6. 关闭游标
cursor.close()
# 7. 关闭连接
conn.close()

异常捕获

try:
尝试执行的代码
except Exception as err:
有错误出现时,执行的代码
finally:
无论有没有错误,都会执行的代码
在这里插入图片描述
在这里插入图片描述

# 1. 导包
import pymysql

# 定义全局变量,初值为 None
conn = None
cursor = None
try:
	# 2. 建立连接
	conn = pymysql.connect(host="211.103.136.244", port=7061, user="student",
	password="iHRM_student_2021", database="test_db", charset="utf8")
	# 3. 获取游标
	cursor = conn.cursor() # 指向 0 号位置。
	# 4. 执行 sql 语句(查询)--- t_book
	cursor.execute("select * from t_book;")
	# 5. 获取结果 - 提取第一条
	res1 = cursor.fetchone()
	print("res1 =", res1)
	# 修改游标位置:回零
	cursor.rownumber = 0
	# 5. 获取结果 - 提取前 2 条
	res2 = cursor.fetchmany(2)
	print("res2 =", res2)
	# 修改游标位置:回零
	cursor.rownumber = 0
	res3 = cursor.fetchall()
	print("res3 =", res3)
	# 修改游标位置:指向第 2 条记录
	cursor.rownumber = 2
	res4 = cursor.fetchmany(2)
	print("res4 =", res4)
except Exception as err:
	print("查询语句执行出错:", str(err))
finally:
# 6. 关闭游标
cursor.close()
# 7. 关闭连接
conn.close()

数据库UID

更新操作
在这里插入图片描述
单独实现如下操作: ①:新增一条图书数据(id:5 title:西游记 pub_date:1986-01-01 ) ②:把图书名称
为‘西游记’的阅读量加一 ③:删除名称为‘西游记’的图书
插入数据

"""
新增一条图书数据(id:5 title:西游记 pub_date:1986-01-01 )
insert into t_book(id, title, pub_date) values(5, '西游记', '1986-01-01');
1. 导包
2. 创建连接
3. 获取游标
4. 执行 insert 语句
5. 提交/回滚事务
6. 关闭游标
7. 关闭连接
"""
# 1. 导包
import pymysql
# 定义全局变量
conn = None
cursor = None
try:
	# 2. 创建连接
	conn = pymysql.connect(host="211.103.136.244", port=7061, user="student",
	password="iHRM_student_2021",
	database="test_db", charset="utf8")
	# 3. 获取游标
	cursor = conn.cursor()
	# 4. 执行 insert 语句
	cursor.execute("insert into t_book(id, title, pub_date) values(175, '西游记', '1986-01-
	01');")
	# 查看 sql执行,影响多少行
	print("影响的行数:", conn.affected_rows())
	# 5. 提交事务
	conn.commit()
except Exception as err:
	print("插入数据错误:", str(err))
# 回滚事务
	conn.rollback()
finally:
# 6. 关闭游标
	cursor.close()
# 7. 关闭连接
	conn.close()

修改数据:

"""
把图书名称为‘西游记’的阅读量加一
update t_book set `read` = `read` + 1 where id = 6;
1. 导包
2. 建立连接
3. 获取游标
4. 执行 update语句
5. 提交、回滚事务
6. 关闭游标
7. 关闭连接
"""
# 1. 导包
import pymysql
conn = None
cursor = None
try:
	# 2. 建立连接
	conn = pymysql.connect(host="211.103.136.244", port=7061, user="student",
	password="iHRM_student_2021",
	database="test_db", charset="utf8")
	# 3. 获取游标
	cursor = conn.cursor()
	# 4. 执行 update语句。字段名,需要使用 反引号(`)包裹
	cursor.execute("update t_book set `read` = `read` + 1 where id = 1023;")
	print("影响的行数:", conn.affected_rows())
	# 5. 提交、回滚事务
	conn.commit()
except Exception as err:
	print("更新失败:", str(err))
# 回滚事务
	conn.rollback()
finally:
# 6. 关闭游标
	cursor.close()
# 7. 关闭连接
	conn.close()

删除数据

"""
删除名称为‘西游记’的图书
delete from t_book where title = '西游记';
1. 导包
2. 建立连接
3. 获取游标
4. 执行 delete 语句
5. 提交、回滚事务
6. 关闭游标
7. 关闭连接
"""
# 1. 导包
import pymysql
conn = None
cursor = None
try:
	# 2. 建立连接
	conn = pymysql.connect(host="211.103.136.244", port=7061, user="student",
	password="iHRM_student_2021",
	database="test_db", charset="utf8")
	# 3. 获取游标
	cursor = conn.cursor()
	# 4. 执行 delete语句。
	cursor.execute("delete from t_book where id = 151;")
	print("影响的行数:", conn.affected_rows())
	# 5. 提交、回滚事务
	conn.commit()
except Exception as err:
	print("更新失败:", str(err))
# 回滚事务
	conn.rollback()
finally:
# 6. 关闭游标
	cursor.close()
# 7. 关闭连接
	conn.close()

数据库工具类封装

封装的目的
将 常用的数据库操作,封装到 一个方法。 后续再操作数据库时,通过调用该方法来实现。
提高代码的复用性!
设计数据库工具类

# 封装数据库工具类
class DBUtil(object):
	@classmethod
	def __get_conn(cls):
		pass
	@classmethod
	def __close_conn(cls):
		pass
# 常用方法:查询一条
	@classmethod
	def select_one(cls, sql):
		pass
# 常用方法:增删改
	@classmethod
	def uid_db(cls, sql):
		pass

实现类方法
获取、关闭连接

# 封装数据库工具类
class DBUtil(object):
# 添加类属性
	conn = None
	@classmethod
	def __get_conn(cls):
# 判断 conn 是否为空, 如果是,再创建
		if cls.conn is None:
			cls.conn = pymysql.connect(host="211.103.136.244", port=7061, user="student",
										password="iHRM_student_2021", database="test_db",
										charset="utf8")
# 返回 非空连接
		return cls.conn
	@classmethod
	def __close_conn(cls):
# 判断,conn 不为空,需要关闭。
		if cls.conn is not None:
			cls.conn.close()
			cls.conn = None

查询一条记录

# 封装数据库工具类
class DBUtil(object):
# 常用方法:查询一条
    @classmethod
    def select_one(cls, sql):
        cursor = None
        res = None
        try:
# 获取连接
            cls.conn = cls.__get_conn()
# 获取游标
            cursor = cls.conn.cursor()
# 执行 查询语句
            cursor.execute(sql)
# 提取一条结果
            res = cursor.fetchone()
        except Exception as err:
            print("查询sql错误:", str(err))
        finally:
# 关闭游标
            cursor.close()
            # 关闭连接
			cls.__close_conn()
# 将查询sql执行的 结果,返回
			return res
if __name__ == '__main__':
res = DBUtil.select_one("select * from t_book;")
print("查询结果为:", res)
            

增删改数据

# 封装数据库工具类
class DBUtil(object):
# 常用方法:增删改
    @classmethod
    def uid_db(cls, sql):
        cursor = None
        try:
# 获取连接
            cls.conn = cls.__get_conn()
# 获取游标
            cursor = cls.conn.cursor()
# 执行 uid 语句
            cursor.execute(sql)
            print("影响的行数:", cls.conn.affected_rows())
# 提交事务
            cls.conn.commit()
        except Exception as err:
# 回滚事务
            cls.conn.rollback()
            print("增删改 SQL 执行失败:", str(err))
        finally:
# 关闭游标
            cursor.close()
# 关闭连接
            cls.__close_conn()
if __name__ == '__main__':
DBUtil.uid_db("update t_book set is_delete = 1 where id = 1111;")

完整封装代码实现

import pymysql
# 封装数据库工具类
class DBUtil(object):
# 添加类属性
    conn = None
    @classmethod
    def __get_conn(cls):
# 判断 conn 是否为空,如果是,再创建
        if cls.conn is None:
            cls.conn = pymysql.connect(host="211.103.136.244", port=7061, user="student",
                                        password="iHRM_student_2021", database="test_db",
                                        charset="utf8")
# 返回 非空连接
            return cls.conn
    @classmethod
    def __close_conn(cls):
# 判断,conn 不为空,需要关闭。
        if cls.conn is not None:
            cls.conn.close()
            cls.conn = None
# 常用方法:查询一条
    @classmethod
    def select_one(cls, sql):
        cursor = None
        res = None
        try:
# 获取连接
            cls.conn = cls.__get_conn()
# 获取游标
            cursor = cls.conn.cursor()
# 执行 查询语句
            cursor.execute(sql)
# 提取一条结果
            res = cursor.fetchone()
        except Exception as err:
            print("查询sql错误:", str(err))
        finally:
# 关闭游标
            cursor.close()
# 关闭连接
        cls.__close_conn()
# 将查询sql执行的 结果,返回
        return res
# 常用方法:增删改
    @classmethod
    def uid_db(cls, sql):
        cursor = None
        try:
# 获取连接
            cls.conn = cls.__get_conn()
# 获取游标
            cursor = cls.conn.cursor()
# 执行 uid 语句
            cursor.execute(sql)
            print("影响的行数:", cls.conn.affected_rows())
# 提交事务
            cls.conn.commit()
        except Exception as err:
# 回滚事务
            cls.conn.rollback()
            print("增删改 SQL 执行失败:", str(err))
        finally:
# 关闭游标
            cursor.close()
# 关闭连接
        cls.__close_conn()
if __name__ == '__main__':
res = DBUtil.select_one("select * from t_book;")
print("查询结果为:", res)
DBUtil.uid_db("update t_book set is_delete = 1 where id = 1111;")

接口对象封装,解决问题
代码冗余度⾼(有⼤量重复代码)
代码耦合度⾼
代码维护成本⾼
核心思想
代码分层
在这里插入图片描述
分层思想:
将普通方法实现的,分为接口对象层和测试脚本层
接口对象层:
对接口进行封装。封装好之后,给测试用例层调用
面向对象类封装实现
测试用例层
调用接口对象层封装的方法,拿到响应结果,断言进程接口测试
借助unittest框架实现

封装Tpshop商城
普通方式实现

import unittest
import requests


class TestTpshopLogin(unittest.TestCase):
    # 测试 登录成功
    def test01_login_ok(self):

     # 创建 session 实例
        session = requests.Session()
        # 使⽤实例,调⽤get 发送获取验证码请求
        session.get(url="http://tpshop-test.itheima.net/index.php? m = Home & c = User & a = verify & r = 0.21519623710645064")
    # 使⽤实例,调⽤post 发送登录请求
        resp = session.post(url="http://tpshop-test.itheima.net/index.php?m = Home & c = User & a = do_login & t = 0.7094195931397276 ",
        data = {"username": "13012345678", "password": "123456", "verify_code":
        "8888"})
        print("响应结果 =", resp.json())
        # 断⾔:
        self.assertEqual(200, resp.status_code)
        self.assertEqual(1, resp.json().get("status"))
         self.assertEqual("登陆成功", resp.json().get("msg"))
    # 测试 ⼿机号不存在

    def test02_tel_not_exists(self):

        # 创建 session 实例
        session = requests.Session()
        # 使⽤实例,调⽤get 发送获取验证码请求
        session.get(url="http://tpshop-test.itheima.net/index.php?m = Home & c = User & a = verify & r = 0.21519623710645064")
        # 使⽤实例,调⽤post 发送登录请求
        resp = session.post(url="http://tpshop-test.itheima.net/index.php?m = Home & c = User & a = do_login & t = 0.7094195931397276",
        data = {"username": "13847834701", "password": "123456", "verify_code":
        "8888"})
        print("响应结果 =", resp.json())
         # 断⾔:
        self.assertEqual(200, resp.status_code)
        self.assertEqual(-1, resp.json().get("status"))
        self.assertEqual("账号不存在!", resp.json().get("msg"))
        # 测试 密码错误

    def test03_pwd_err(self):
        # 创建 session 实例
            session = requests.Session()
        # 使⽤实例,调⽤get 发送获取验证码请求
            session.get(url="http://tpshop-test.itheima.net/index.php?m = Home & c = User & a = verify & r = 0.21519623710645064")
        # 使⽤实例,调⽤post 发送登录请求
            resp = session.post(
            url="http://tpshop-test.itheima.net/index.php?m = Home & c = User & a = do_login & t = 0.7094195931397276",
            data = {"username": "13012345678", "password": "123456890",
                "verify_code": "8888"})
            print("响应结果 =", resp.json())
        # 断⾔:
            self.assertEqual(200, resp.status_code)
            self.assertEqual(-2, resp.json().get("status"))
            self.assertEqual("密码错误!", resp.json().get("msg"))

登录接口对象层
封装思想:
将动态变化的数据,设计到方法的参数
将固定不变的,直接写成方法实现
将响应结果,通过返回值传出

分析
在这里插入图片描述
封装实现

class TpshopLoginApi(object):
 	# 发送验证码请求
 	@classmethod
 	def get_verify(cls, session):
 		session.get(url="http://tpshop-test.itheima.net/index.php?m=Home&c=User&a=verify&r=0.21519623710645064")
	# 发送登录请求
	@classmethod
 	def login(cls, session, login_data):
	 	resp = session.post(
	 	url="http://tpshop-test.itheima.net/index.php?m=Home&c=User&a=do_login&t=0.7094195931397276",
	 	data=login_data)
 	return resp

登录接口测试用例层
优化前

import unittest
import requests
from tpshop_login_api import TpshopLoginApi
class TestTpshopLogin(unittest.TestCase):
 # 测试 登录成功
 	def test01_login_ok(self):
 	# 创建 session实例
 		s = requests.Session()
 		# ⽤实例,调⽤⾃⼰封装的 获取验证码 接⼝
 		TpshopLoginApi.get_verify(s)
 		# 调⽤ ⾃⼰封装的接⼝,登录
		 abc = {"username": "13012345678", "password": "123456", "verify_code":"8888"}
 		resp = TpshopLoginApi.login(s, abc)
 		print(resp.json())
 # 断⾔
 		self.assertEqual(200, resp.status_code)
 		self.assertEqual(1, resp.json().get("status"))
 		self.assertEqual("登陆成功", resp.json().get("msg"))
 # 测试 ⼿机号不存在
 	def test02_tel_not_exists(self):
 # 创建 session实例
		s = requests.Session()
 # ⽤实例,调⽤⾃⼰封装的 获取验证码 接⼝
 		TpshopLoginApi.get_verify(s)
 # 调⽤ ⾃⼰封装的接⼝,登录
 		abc = {"username": "13048932745", "password": "123456", "verify_code":"8888"}
 		resp = TpshopLoginApi.login(s, abc)
 		print(resp.json())
 # 断⾔
 		self.assertEqual(200, resp.status_code)
 		self.assertEqual(-1, resp.json().get("status"))
 		self.assertIn("账号不存在", resp.json().get("msg"))
 # 测试 密码错误
 		def test03_pwd_err(self):
 # 创建 session实例
 			s = requests.Session()
 # ⽤实例,调⽤⾃⼰封装的 获取验证码 接⼝
 			TpshopLoginApi.get_verify(s)
 # 调⽤ ⾃⼰封装的接⼝,登录
 			abc = {"username": "13012345678", "password": "123456789", "verify_code":"8888"}
 			resp = TpshopLoginApi.login(s, abc)
 			print(resp.json())
 # 断⾔
			self.assertEqual(200, resp.status_code)
			self.assertEqual(-2, resp.json().get("status"))
			self.assertIn("密码错误", resp.json().get("msg"))

优化后:

import unittest
import requests
from tpshop_login_api import TpshopLoginApi
# 封装 通⽤ 的 断⾔函数
def common_assert(self, resp, status_code, status, msg):
 	self.assertEqual(status_code, resp.status_code)
 	self.assertEqual(status, resp.json().get("status"))
	self.assertIn(msg, resp.json().get("msg"))
class TestTpshopLogin(unittest.TestCase):
 # 添加类属性
 	session = None
 @classmethod
	 def setUpClass(cls) -> None: # 在 类中 所 有 测试⽅法执⾏之前,⾃动执⾏1次。
 		cls.session = requests.Session()
 	 def setUp(self) -> None: # 在 每个 测试⽅法执⾏之前,⾃动执⾏1次。
 # 调⽤ ⾃⼰封装的接⼝,获取验证码
 		TpshopLoginApi.get_verify(self.session)
 # 测试 登录成功
	 def test01_login_ok(self):
 # 调⽤ ⾃⼰封装的接⼝,登录
 		data = {"username": "13012345678", "password": "123456", "verify_code":
"8888"}
 		resp = TpshopLoginApi.login(self.session, data)
 # 断⾔
 		common_assert(self, resp, 200, 1, "登陆成功")
 # 测试 ⼿机号不存在
 	def test02_tel_not_exists(self):
 # 调⽤ ⾃⼰封装的接⼝,登录
 		data = {"username": "13048392845", "password": "123456", "verify_code":
"8888"}
 		resp = TpshopLoginApi.login(self.session, data)
 # 断⾔
 		common_assert(self, resp, 200, -1, "账号不存在")
 # 测试 密码错误
	 def test03_pwd_err(self):
 # 调⽤ ⾃⼰封装的接⼝,登录
 		data = {"username": "13012345678", "password": "123456890", "verify_code":
"8888"}
 		resp = TpshopLoginApi.login(self.session, data)
 # 断⾔
 		common_assert(self, resp, 200, -2, "密码错误")

封装断言方法:
在这里插入图片描述

# 封装 通⽤ 的 断⾔函数
def common_assert(self, resp, status_code, status, msg):
	 self.assertEqual(status_code, resp.status_code)
	 self.assertEqual(status, resp.json().get("status"))
	 self.assertIn(msg, resp.json().get("msg"))
# 调⽤
common_assert(self, resp, 200, 1, "登陆成功")
common_assert(self, resp, 200, -1, "账号不存在")
common_assert(self, resp, 200, -2, "密码错误")

封装ihrm登录

登录接口
普通方式实现

import unittest
import requests
class TestIhrmLogin(unittest.TestCase):
 # 登陆成功
 	def test01_login_success(self):
 		resp = requests.post(url="http://ihrm-test.itheima.net/api/sys/login",
 		json={"mobile": "13800000002", "password": "123456"})
 		print(resp.json())
 		# 断⾔
		self.assertEqual(200, resp.status_code)
	    self.assertEqual(True, resp.json().get("success"))
		self.assertEqual(10000, resp.json().get("code"))
		self.assertIn("操作成功", resp.json().get("message")
 

登录接口对象层
思路:
动态变化的,写⼊参数
固定不变,直接写到⽅法实现内
响应结果,通过返回值 return
在这里插入图片描述

# 接⼝对象层
import requests
class IhrmLoginApi(object):
 	@classmethod
 	def login(cls, json_data):
 		resp = requests.post(url="http://ihrm-test.itheima.net/api/sys/login",
 						     json=json_data)
 		return resp

登录接口测试用例层

import unittest
from ihrm_login_api import IhrmLoginApi
# 定义测试类
class TestIhrmLogin(unittest.TestCase):
 # 测试⽅法 - 登录成功
 def test01_login_success(self):
 # 调⽤ ⾃⼰封装 login
 login_data = {"mobile": "13800000002", "password": "123456"}
 resp = IhrmLoginApi.login(login_data)
 print("登录成功:", resp.json())
 # 断⾔
 self.assertEqual(200, resp.status_code)
 self.assertEqual(True, resp.json().get("success"))
 self.assertEqual(10000, resp.json().get("code"))
 self.assertIn("操作成功", resp.json().get("message"))
 # 测试⽅法 - ⼿机号未注册
 def test02_mobile_not_register(self):
 # 调⽤ ⾃⼰封装 login
 login_data = {"mobile": "1384780932", "password": "123456"}
 resp = IhrmLoginApi.login(login_data)
 print("⼿机号未注册:", resp.json())
 # 断⾔
 self.assertEqual(200, resp.status_code)
 self.assertEqual(False, resp.json().get("success"))
 self.assertEqual(20001, resp.json().get("code"))
 self.assertIn("⽤户名或密码错误", resp.json().get("message"))
 # 测试⽅法 - 密码错误
 def test03_pwd_error(self):
 # 调⽤ ⾃⼰封装 login
 login_data = {"mobile": "13800000002", "password": "890"}
 resp = IhrmLoginApi.login(login_data)
 print("密码错误:", resp.json())
 # 断⾔
 self.assertEqual(200, resp.status_code)
 self.assertEqual(False, resp.json().get("success"))
 self.assertEqual(20001, resp.json().get("code"))
 self.assertIn("⽤户名或密码错误", resp.json().get("message"))
 # 测试⽅法 - ⼿机号为空
 def test04_mobile_is_none(self):
 # 调⽤ ⾃⼰封装 login
 login_data = {"mobile": None, "password": "123456"}
 resp = IhrmLoginApi.login(login_data)
 print("⼿机号为空:", resp.json())
 # 断⾔
 self.assertEqual(200, resp.status_code)
 self.assertEqual(False, resp.json().get("success"))
 self.assertEqual(20001, resp.json().get("code"))
 self.assertIn("⽤户名或密码错误", resp.json().get("message"))
 # 测试⽅法 - 多参
 def test12_more_params(self):
 # 调⽤ ⾃⼰封装 login
 login_data = {"mobile": "13800000002", "password": "123456", "abc":"123"}
 resp = IhrmLoginApi.login(login_data)
 print("⼿机号为空:", resp.json())
 # 断⾔
 self.assertEqual(200, resp.status_code)
 self.assertEqual(True, resp.json().get("success"))
 self.assertEqual(10000, resp.json().get("code"))
 self.assertIn("操作成功", resp.json().get("message"))
 # 测试⽅法 - ⽆参
 def test14_none_params(self):
 # 调⽤ ⾃⼰封装 login
 login_data = None
 resp = IhrmLoginApi.login(login_data)
 print("⼿机号为空:", resp.json())
 # 断⾔
 self.assertEqual(200, resp.status_code)
 self.assertEqual(False, resp.json().get("success"))
 self.assertEqual(99999, resp.json().get("code"))
 self.assertIn("抱歉,系统繁忙,请稍后重试", resp.json().get("message"))

封装断言方法

  1. 创建 ⽂件 assert_util.py
  2. 在 ⽂件内,定义 common_assert() 函数
  3. 直接粘贴 unittest框架中的断⾔代码,修改参数。
  4. 回到 unittest框架 实现的 测试脚本中, 调⽤该函数,实现断⾔,传递 实际参数。
# 定义 断⾔ 函数
# def common_assert():
# self.assertEqual(200, resp.status_code) # self / resp 报错
# self.assertEqual(True, resp.json().get("success"))
# self.assertEqual(10000, resp.json().get("code"))
# self.assertIn("操作成功", resp.json().get("message"))
def common_assert(self, resp, status_code, success, code, message):
 self.assertEqual(status_code, resp.status_code)
 self.assertEqual(success, resp.json().get("success"))
 self.assertEqual(code, resp.json().get("code"))
 self.assertIn(message, resp.json().get("message"))
 
# 调⽤演示
 common_assert(self, resp, 200, True, 10000, "操作成功")
 common_assert(self, resp, 200, False, 20001, "⽤户名或密码错误")
 common_assert(self, resp, 200, False, 99999, "抱歉,系统繁忙,请稍后重试")

导包注意事项;
在这里插入图片描述
Tpshop商城参数化
在这里插入图片描述
提取每个测试⽤例 使⽤的 测试数据 和 断⾔数据
在这里插入图片描述
封装函数,将数据转换为元组列表
在这里插入图片描述
参数化步骤:

  1. 导包 from parameterized import parameterized
  2. 在 通⽤测试⽅法,上⼀⾏,添加 @parameterized.expand()
  3. 给 expand() 传⼊ [(),(),()](调⽤ 转换 [{},{},{}] --> [(),(),()] 的函数)
  4. 修改 通⽤测试⽅法,添加形参,个数、顺序,与 [{},{},{}] 中 { } 内的所有 key 完全⼀⼀对应。
  5. 在 通⽤测试⽅法内,使⽤形参。
    代码实现
from parameterized import parameterized
class TestTpshopLogin(unittest.TestCase):
 # 添加类属性
 session = None
 	@classmethod
 	def setUpClass(cls) -> None:
 		cls.session = requests.Session()
 	def setUp(self) -> None:
 # 调⽤ ⾃⼰封装的接⼝,获取验证码
 		TpshopLoginApi.get_verify(self.session)
 # 测试 tpshop 登录
 	@parameterized.expand(read_json_data())
 	def test_tpshop_login(self, req_body, status_code, status, msg):
 		resp = TpshopLoginApi.login(self.session, req_body)
 		common_assert(self, resp, status_code, status, msg)

获取请求头

  1. 在 common/ 下 创建 get_header.py 文件
  2. 在 文件内 创建 get_header() 函数,实现 登录成功,获取令牌,拼接成 请求头,返回。
  3. 在 scripts/ 的测试脚本文件中,添加 setUpClass 方法,调用 get_header() 函数。 将返回值 保存到 类属性上
  4. 在 使用 请求头的位置,直接从类属性获取
# 在 common/ 下 创建 get_header.py 文件 实现 get_header 函数
import requests
def get_header():
url = "http://ihrm-test.itheima.net/api/sys/login"
data = {"mobile": "13800000002", "password": "123456"}
resp = requests.post(url=url, json=data)
print(resp.json())
# 从 响应体中,获取 data的值
token = resp.json().get("data")
header = {"Content-Type": "application/json",
"Authorization": "Bearer " + token}
return header
----------------------------
# 在 scripts/ 的测试脚本文件中,添加 setUpClass 方法,调用 get_header 函数。 将返回值 保存到 类属性上
from common.get_header import get_header
class TestEmpAdd(unittest.TestCase):
# 类属性
header = None
@classmethod
def setUpClass(cls) -> None:
cls.header = get_header()
----------------------------
# 在 使用 请求头的位置,直接从类属性获取
resp = IhrmEmpCURD.add_emp(self.header, json_data)

提取项目目录

相关知识:
__ file__ : 获取 当前文件的 绝对路径。
BASE_DIR = os.path.dirname(__ file__) : 获取 到 当前文件的 上一级目录。
此行代码,写在 config.py 中, 可以直接获取 项目目录

项目中使用:

  1. 在 config.py 文件中,添加 获取项目路径 全局变量 BASE_DIR = os.path.dirname(file)
  2. 修改 common/ 下 read_json_util.py 文件中,读取 json 文件 函数read_json_data(),添加 参数
    path_filename
  3. 在 使用 read_json_data()函数 时, 拼接 json 文件路径, 传入到 函数中。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

生成测试报告

步骤:

  1. 创建测试套件实例。 suite
  2. 添加 测试类
  3. 创建 HTMLTestReport 类实例。 runner
  4. runner 调用 run(), 传入 suite
import unittest
from config import BASE_DIR
from scripts.test_emp_add_params import TestEmpAddParams
from scripts.test_ihrm_login_params import TestIhrmLoginParams
from htmltestreport import HTMLTestReport
# 1. 创建测试套件实例。 suite
suite = unittest.TestSuite()
# 2. 添加 测试类, 组装测试用例
suite.addTest(unittest.makeSuite(TestIhrmLoginParams))
suite.addTest(unittest.makeSuite(TestEmpAddParams))
# 3. 创建 HTMLTestReport 类实例。 runner
# runner = HTMLTestReport(BASE_DIR + "/report/ihrm.html") # 绝对路径
runner = HTMLTestReport("./report/ihrm.html", description="描述", title="标题") # 相对路径
# 4. runner 调用 run(), 传入 suite
runner.run(suite)

日志收集

什么是日志
日志也叫 log,通常对应的 xxx.log 的日志文件。文件的作用是记录系统运行过程中,产生的信息。
搜集日志的作用
查看系统运行是否正常。
分析、定位 bug。

日志的级别

logging.DEBUG:调试级别【高】
logging.INFO:信息级别【次高】
logging.WARNING:警告级别【中】
logging.ERROR:错误级别【低】
logging.CRITICAL:严重错误级别【极低】
特性:
日志级别设定后,只有比该级别低的日志会写入日志。
在这里插入图片描述

日志代码实现

在这里插入图片描述

# 0. 导包
# 1. 创建日志器对象
# 2. 设置日志打印级别
# logging.DEBUG 调试级别
# logging.INFO 信息级别
# logging.WARNING 警告级别
# logging.ERROR 错误级别
# logging.CRITICAL 严重错误级别
# 3. 创建处理器对象
# 创建 输出到控制台 处理器对象
# 创建 输出到日志文件 处理器对象
# 4. 创建日志信息格式
# 5. 将日志信息格式设置给处理器
# 设置给 控制台处理器
# 设置给 日志文件处理器
# 6. 给日志器添加处理器
# 给日志对象 添加 控制台处理器
# 给日志对象 添加 日志文件处理器
# 7. 打印日志
"""
import logging.handlers
import logging
import time
# 1. 创建日志器对象
logger = logging.getLogger()
# 2. 设置日志打印级别
logger.setLevel(logging.DEBUG)
# logging.DEBUG 调试级别
# logging.INFO 信息级别
# logging.WARNING 警告级别
# logging.ERROR 错误级别
# logging.CRITICAL 严重错误级别
# 3.1 创建 输出到控制台 处理器对象
st = logging.StreamHandler()
# 3.2 创建 输出到日志文件 处理器对象
fh = logging.handlers.TimedRotatingFileHandler('a.log', when='midnight', interval=1,
backupCount=3, encoding='utf-8')
# when 字符串,指定日志切分间隔时间的单位。midnight:凌晨:12点。
# interval 是间隔时间单位的个数,指等待多少个 when 后继续进行日志记录
# backupCount 是保留日志文件的个数
# 4. 创建日志信息格式
fmt = "%(asctime)s %(levelname)s [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s"
formatter = logging.Formatter(fmt)
# 5.1 日志信息格式 设置给 控制台处理器
st.setFormatter(formatter)
# 5.2 日志信息格式 设置给 日志文件处理器
fh.setFormatter(formatter)
# 6.1 给日志器对象 添加 控制台处理器
logger.addHandler(st)
# 6.2 给日志器对象 添加 日志文件处理器
logger.addHandler(fh)
# 7. 打印日志
while True:
# logging.debug('我是一个调试级别的日志')
# logging.info('我是一个信息级别的日志')
logging.warning('test log sh-26')
# logging.error('我是一个错误级别的日志')
# logging.critical('我是一个严重错误级别的日志')
time.sleep(1)

日志使用

可修改的位置
在这里插入图片描述
使用步骤:

  1. 调用 init_log_config() 函数,初始化日志信息。
  2. 指定 日志级别,打印 日志信息。
    在这里插入图片描述
    在这里插入图片描述

全量字段校验

概念:校验接⼝返回响应结果的全部字段(更进一步的断言)
校验内容:
字段值
字段名 或 字段类型
校验流程:
定义json语法校验格式
⽐对接口实际响应数据是否符合json校验格式
安装jsonschema:
查验:
pip 查验:pip list 或 pip show jsonschema
pycharm 中 查验:file — settings — 项目名中查看 python解释器列表

JSON Schema⼊⻔

在这里插入图片描述
{
“type”: “object”,
“properties”: {
“success”: {“type”: “boolean”},
“code”: {“type”: “integer”},
“message”: {“type”: “string”}
},
“required”: [“success”, “code”, “message”]
}

校验方法
在线工具
http://json-schema-validator.herokuapp.com
https://www.jsonschemavalidator.net
在这里插入图片描述
在这里插入图片描述

python代码校验

实现步骤:
1 导包 import jsonschema
2 定义 jsonschema格式 数据校验规则
3 调⽤ jsonschema.validate(instance=“json数据”, schema=“jsonshema规则”)
查验校验结果:
校验通过:返回 None
校验失败
schema 规则错误,返回 SchemaError
json 数据错误,返回 ValidationError

# 1. 导包
import jsonschema
# 2. 创建 校验规则
schema = {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"code": {
"type": "int"
},
"message": {
"type": "string"
}
},
"required": ["success", "code", "message"]
}
# 准备待校验数据
data = {
"success": True,
"code": 10000,
"message": "操作成功"
}
# 3. 调用 validate 方法,实现校验
result = jsonschema.validate(instance=data, schema=schema)
print("result =", result)
# None: 代表校验通过
# ValidationError:数据 与 校验规则不符
# SchemaError: 校验规则 语法有误

json schema语法

在这里插入图片描述
type关键字
作用:约束数据类型

integer —— 整数
string —— 字符串
object —— 对象
array —— 数组 --> python:list 列表
number —— 整数/⼩数
null —— 空值 --> python:None
boolean —— 布尔值
语法:
{
"type": "数据类型"
}

示例

import jsonschema
# 准备校验规则
schema = {
"type": "object" # 注意 type 和 后面的 类型,都要放到 ”“ 中!
}
# 准备数据
data = {"a": 1, "b": 2}
# 调用函数
res = jsonschema.validate(instance=data, schema=schema)
print(res)

properties关键字
说明:是 type关键字的辅助。用于 type 的值为 object 的场景。
作用:指定 对象中 每个字段的校验规则。 可以嵌套使用。

语法:
{
"type": "object",
"properties":{
"字段名1":{规则},
"字段名2":{规则},
......
}
}

案例

{
"success": true,
"code": 10000,
"message": "操作成功",
"money": 6.66,
"address": null,
"data": {
"name": "tom"
},
"luckyNumber": [6, 8, 9]
}
import jsonschema
# 准备校验规则
schema = {
"type": "object",
"properties": {
"success": {"type": "boolean"},
"code": {"type:": "integer"},
"message": {"type": "string"},
"money": {"type": "number"},
"address": {"type": "null"},
"data": {"type": "object"},
"luckyNumber": {"type": "array"}
}
}
# 准备测试数据
data = {
"success": True,
"code": 10000,
"message": "操作成功",
"money": 6.66,
"address": None,
"data": {
"name": "tom"
},
"luckyNumber": [6, 8, 9]
}
# 调用方法进行校验
res = jsonschema.validate(instance=data, schema=schema)
print(res)

案例2:要求定义JSON对象中包含的所有字段及数据类型

data = {
"success": True,
"code": 10000,
"message": "操作成功",
"money": 6.66,
"address": None,
"data": {
"name": "tom",
"age": 18,
"height": 1.78
},
"luckyNumber": [6, 8, 9]
}
import jsonschema
# 准备校验规则
schema = {
"type": "object",
"properties": {
"success": {"type": "boolean"},
"code": {"type:": "integer"},
"message": {"type": "string"},
"money": {"type": "number"},
"address": {"type": "null"},
"data": {
"type": "object",
"properties": { # 对 data 的对象值,进一步进行校验
"name": {"type": "string"},
"age": {"type": "integer"},
"height": {"type": "number"}
}
},
"luckyNumber": {"type": "array"}
}
}
# 准备测试数据
data = {
"success": True,
"code": 10000,
"message": "操作成功",
"money": 6.66,
"address": None,
"data": {
"name": "tom",
"age": 18,
"height": 1.78
},
"luckyNumber": [6, 8, 9]
}
# 调用方法进行校验
res = jsonschema.validate(instance=data, schema=schema)
print(res)

required关键字
作用:校验对象中必须存在的字段。字段名必须是字符串,且唯⼀

语法:
{
"required": ["字段名1", "字段名2", ...]
}
import jsonschema
# 测试数据
data = {
"success": True,
"code": 10000,
"message": "操作成功",
"data": None,
}
# 校验规则
schema = {
"type": "object",
"required": ["success", "code", "message", "data"]
}
# 调用方法校验
res = jsonschema.validate(instance=data, schema=schema)
print(res)

const关键字
作用:校验字段值是⼀个固定值。

语法:
{
"字段名"{"const": 具体值}
}
import jsonschema
# 测试数据
data = {
"success": True,
"code": 10000,
"message": "操作成功",
"data": None,
}
# 校验规则
schema = {
"type": "object",
"properties": {
"success": {"const": True},
"code": {"const": 10000},
"message": {"const": "操作成功"},
"data": {"const": None}
},
"required": ["success", "code", "message", "data"]
}
# 调用方法校验
res = jsonschema.validate(instance=data, schema=schema)
print(res)
pattern关键字
作用:指定正则表达式,对字符串进行模糊匹配
基础正则举例:
1 包含字符串:hello
2 以字符串开头 ^: ^hello 如:hello,world
3 以字符串结尾 $: hello$ 如:中国,hello
4 匹配[]内任意1个字符[]: [0-9]匹配任意⼀个数字 [a-z]匹任意一个小写字母 [cjfew9823]匹配任意一个
5 匹配指定次数{}: [0-9]{11}匹配11位数字。
匹配 手机号:^[0-9]{11}$
语法:
{
"字段名"{"pattern": "正则表达式"}
}
import jsonschema
# 测试数据
data = {
"message": "!jeklff37294操作成功43289hke",
"mobile": "15900000002"
}
# 校验规则
schema = {
"type": "object",
"properties": {
"message": {"pattern": "操作成功"},
"mobile": {"pattern": "^[0-9]{11}$"}
}
}
# 调用方法校验
res = jsonschema.validate(instance=data, schema=schema)
print(res)

综合案例应用

# 测试数据
import jsonschema
data = {
"success": False,
"code": 10000,
"message": "xxx登录成功",
"data": {
"age": 20,
"name": "lily"
}
}
# 校验规则
schema = {
"type": "object",
"properties": {
"success": {"type": "boolean"},
"code": {"type": "integer"},

"message": {"pattern": "登录成功$"},
"data": {
"type": "object",
"properties": {
"name": {"const": "lily"},
"age": {"const": 20}
},
"required": ["name", "age"]
}
},
"required": ["success", "code", "message", "data"]
}
# 调用测试方法
res = jsonschema.validate(instance=data, schema=schema)
print(res)

案例练习

传智健康管理系统,是一款应用于健康管理机构的业务系统。采用可视化界面管理,提高健康管理师工作效
率,加强与患者间的互动
在这里插入图片描述
前端:http://mobile-health-test.itheima.net
后端:http://manager-health-test.itheima.net
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Dubbo接口测试

RPC

远程过程调用(Remote Procedure Call):像调用本地方法一样,调用远程方法。
常见的RPC框架有 Dubbo、Thrift、grpc

Dubbo

Dubbo是一款高性能、轻量级、基于Java的开源RPC框架(最早由阿里开源,2018年贡献给了Apache组织)
Dubbo接口的作用:远程调用 java 写的方法。 需要传参、获取返回值。
查阅API文档
在这里插入图片描述
从中获取哪些信息?
服务名
方法名
参数类型、返回值类型

java中 方法定义语法结构:
返回值类型 方法名(数据类型 形参1,数据类型 形参2,…)
void:代表没有返回值、没有参数。

在这里插入图片描述

Telnet工具远程调用

启用telnet
在这里插入图片描述
telnet远程连接服务
连接语法:连接语法:telnet IP 端口号
在这里插入图片描述
在这里插入图片描述
telnet调用服务接口
命令格式:invoke 服务名.方法名(实参)
示例:invoke MemberService.findByTelephone(“13020210001”)
在这里插入图片描述
python借助dubbo远程调用
Dubboclient,封装了 telnetlib 库。 telnetlib 是 python 内置模块,可实现远程调用 Dubbo 接口
安装dubboclient

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. 打印响应结果

会员服务(入门)

案例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))

添加会员(传递 对象参数)

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))

根据日期统计会员数(传递 字符串列表)

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))

其他模块
添加预约设置

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))

按月统计预约设置信息

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))

根据日期修改预约设置数量

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))

根据用户名查询用户信息

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))

在这里插入图片描述
现有问题:
远程调用的 7个 dubbo接口 存在的问题:

  1. 代码有 大量冗余
  2. 测试接口时,除了要给 测试数据之外,还需要 指定 服务名、方法名
  3. 传参时,除了要考虑测试数据外,还要分析是否要添加 class 字段 及 对应数据。
  4. 返回的数据类型统一为 string(不具体)

封装目标
5. 只关心:测试数据、响应结果
6. 返回的结果 分别为 不同的 具体类型。

Dubbo接口自动化测试框架

核心模块
在这里插入图片描述

基础服务对象封装

from dubboclient import DubboClient
class BaseService(object):
def __init__(self):
self.dubbo_client = DubboClient("211.103.136.244", 6502)

服务对象封装
会员服务封装

"""
类名: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))

预约设置服务封装

"""
类名: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))

用户服务封装

"""
类名: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))

测试用例对象封装

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)

参数化

  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)
# 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)

接口自动化框架封装
在这里插入图片描述
测试报告

# 导包
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)
OpenHarmony 快速上手 BearPi-HM Micro 一个带显示屏的开发板
Halifax
01-05 762
快速入门并掌握如何使用BearPi-HM Micro显示屏开发板,并成功烧录系统源码,并教大家如何安装一个Hap包
人工智能演进之路:神经网络两落三起
程序员光剑
07-05 4326
本文我将以"人工智能演进之路:神经网络两落三起"为标题,撰写一篇详细的技术博客文章。这篇文章将深入探讨神经网络在人工智能发展历程中的起起落落,以及其对AI领域的深远影响。我会严格遵循您提供的约束条件和内容要求。下面是文章的正文内容:人工智能(AI)作为计算机科学的一个重要分支,自20世纪50年代诞生以来,经历了几代人的努力和探索。在这漫长的发展历程中,神经网络无疑是最具代表性和影响力的技术之一。它模仿人脑的结构和工作原理,通过大量的互连节点(神经元)构建复杂的网络,以实现类似人类的学习和决策能力。然而,神经
测试技能提升-接口测试6-pymysql增删改查,工具类封装、案例删除接口
Xiaowu2048的博客
06-24 214
接口测试
【黑马程序员项目】传智健康项目概述和环境搭建
黑马程序员官方博客
01-11 748
传智健康管理系统是一款应用于健康管理机构的业务系统,实现健康管理机构工作内容可视化、会员管理专业化、健康评估数字化、健康干预流程化、知识库集成化,从而提高健康管理师的工作效率,加强与会员间的互动,增强管理者对健康管理机构运营情况的了解。详情可在另外资料当中,可以找我要!软件开发一般会经历如下几个阶段,整个过程是顺序展开,所以通常称为瀑布模型。本项目采用maven分模块开发方式,即对整个项目拆分为几个maven工程,每个maven工程存放特定的一类代码,具体如下:各模块职责定位:health_parent:父
接口测试(十)—— telnet和python代码测试dubbo接口
此心光明事上练的博客
12-18 1312
传智健康管理系统,是一款应用于健康管理机构的业务系统。采用可视化界面管理,提高健康管理师工作效率,加强与患者间的互动。传入 :服务名、方法名、实参(方法使用)。2. 测试接口时,除了要给 测试数据之外, 还需要 指定 服务名、方法名。3. 给 expand() 传入 [(),(),()] 类型的数据。返回值类型 方法名(数据类型 形参1,数据类型 形参2,....)4. 修改 通用测试方法,添加形参,个数、顺序与 () 数据一致。会员服务、预约服务、体检报告服务、健康评估服务、健康干预服务。
黑马项目公开:传智健康移动端开发-体健预约
黑马程序员官方博客
03-01 1039
注册成功后就可以使用注册的邮箱和设置的密码进行登录,登录成功后点击左侧“自定义菜单”进入自定义菜单页面在自定义菜单页面可以根据需求创建一级菜单和二级菜单,其中一级菜单最多可以创建3个,每个一级菜单下面最多可以创建5个二级菜单。每个菜单由菜单名称和菜单内容组成,其中菜单内容有3中形式:发送消息、跳转网页、跳转小程序。目前市面上有很多第三方提供的短信服务,这些第三方短信服务会和各个运营商(移动、联通、电信)对接,我们只需要注册成为会员并且按照提供的开发文档进行调用就可以发送短信。
测试技能提升-接口测试11-dubbo接口测试
Xiaowu2048的博客
07-03 532
接口测试
基于 SpringBoot(Spring + Springboot + MyBatis)+ES+-hm_mall.zip
11-07
项目的代码组织、接口设计、异常处理、测试等方面都是开发者需要关注和优化的地方。 总之,"hm_mall" 是一个综合性的电商项目,结合了 Spring、Spring Boot、MyBatis 和 Elasticsearch 等主流技术,旨在提供一个...
UAV021_2-IIC_10DOF_MPU6050_HM5883_MS5611.zip
10-26
在本项目中,我们主要探讨的是如何利用STM32F4微控制器通过IIC通信协议来读取并处理10维度传感器数据,其中包括六轴...通过深入研究和实践,不仅可以提升你的硬件驱动编程技能,还能增强你在无人机领域的应用能力。
Altair HyperWorks软件二次开发:二次开发与自动化测试教程
最新发布
kkchenjj的博客
08-11 861
HyperMesh是HyperWorks中的一个强大的前处理工具,用于创建和编辑有限元模型。它支持多种网格类型,包括四面体、六面体、壳单元等,能够处理复杂的几何模型。HyperMesh还提供了高级的网格优化功能,确保模型的准确性和计算效率。函数是封装代码块以执行特定任务的工具。"""计算两个数的和"""# 调用函数print("结果:", result)HyperMeshAPI是Altair HyperMesh软件提供的应用程序编程接口,允许用户通过Python脚本控制HyperMesh的大部分功能。
python习题#讲解13参数化讲解python+requests+pytest接口自动化
weixin_68440128的博客
05-13 454
1.先了解项目目录架构 测试框架 目录结构 python package类文件夹:1.api2.scripts3.common directory类文件夹:1.data2.report 项目根目录文件:1.config.py2.pytest.ini 2.参数化步骤 1)api文件夹中封装登录.py #登录 import requests#导包 class Ihrm......
python习题#讲解12
weixin_68440128的博客
05-10 549
1.请使用pymysql完成以下需求: - 向 t_book 表插入一本书,书名为《Python从入门到放弃》,阅读量为50,评论量为0,发布日期为:2020-01-01 - 测试工程师发现一个bug,该书的评论数与实际不符,要求你把评论量修改为修正后的值:250 - 老板投资了Python,觉得这本书名太不吉利,需要下架,请删除这本书。 - 你删除后,心中不放心到底有没有删除,想确认是否真正删除了,你需要怎么做? import pymysql#导包pymysql class DBtools(ob
接口测试(五)—— PyMySQL增删改查、数据库工具类封装
此心光明事上练的博客
12-13 960
1. 导包 import pymysql 2. 创建连接。 3. 获取游标。 4. 执行 SQL。 cursor.execute( ”sql语句“ ) 查询语句 处理结果集(提取数据 fetch*) 增删改语句,成功:提交事务;失败:回滚事务。 5. 关闭游标。 6. 关闭连接。
【医疗健康项目】传智健康项目(一)
热门推荐
Chovy_pyc的博客
05-16 1万+
传智健康项目是一套医疗健康项目,包括前台预约系统以及后台管理系统,基于SSM实现,采用Dubbo+ZooKeeper部署。前台预约系统包括:用户登录、体检预约、短信验证、自动注册、套餐详情、预约成功展示等模块。后管理系统包括:用户登录、检查项管理、检查组管理、套餐管理、预约设置、会员数量统计、套餐占比统计、运营数据统计、运营数据统计导出Excel、PDF文件等模块。
黑马程序员Java就业项目:传智健康项目概述和环境搭建
黑马程序员官方博客
07-19 816
传智健康管理系统是一款应用于健康管理机构的业务系统,实现健康管理机构工作内容可视化、会员管理专业化、健康评估数字化、健康干预流程化、知识库集成化,从而提高健康管理师的工作效率,加强与会员间的互动,增强管理者对健康管理机构运营情况的了解。参见资料中的静态原型。软件开发一般会经历如下几个阶段,整个过程是顺序展开,所以通常称为瀑布模型。本项目采用maven分模块开发方式,即对整个项目拆分为几个maven工程,每个maven工程存放特定的一类代码,具体如下:各模块职责定位:health_parent:父工程,打包方
Day_01 传智健康项目-项目概述和环境搭建
lbw18的博客
06-18 636
ssm项目
JAVA中台化与常用组件设计
fegus的博客
06-01 2177
随着互联网公司的崛起,“中台”这个词也进入了人们的视线。BAT 等公司纷纷推出了自己的中台系统。那么,什么是中台系统?任何一个软件系统都是通过帮助客户解决问题来实现价值的。针对不同的需求会建立不同的软件项目。这些软件项目包含客户端的应用和后台管理配置的应用。久而久之就形成了固定的“前台”和“后台”系统,而且大家都在乐此不疲地开发着类似的业务系统。但是,时间一长大家就发现了,这些系统中有一些部分大同小异,在做第二个项目的时候并不用将所有的功能重写,可以把之前项目中那些共有的模块拿出来,稍作修改就可以在新项目中
Python常见基础面试题
行走在路上
02-18 5102
面试题 谈谈你对面向对象的理解 面对对象是一种编程思想,以类的眼光来来看待事物的一种方式。将有共同的属性和方法的事物封装到同一个类下面。 ​ 封装: 定义:将数据处理和业务实现逻辑放在某个对象内部,外界只能通过公开的接口访问该对象 好处:隐藏对象实现细节,便于维护和提高安全性 继承: 定义:类与类之间产生父子关系,多继承情况按照类的书写顺序查找 好处:子类可以获取父类的属性和方法; 实现方式:覆盖式继承、扩展式继承super() 多态:(python动态弱类型语
接口测试项目记录
qq_61355899的博客
06-26 474
接口测试记录
检测HM-JPEG隐写术:一种新型统计特性保持方法
"一种新型统计特性保持JPEG隐写术的检测,黄方军,HM-JPEG隐写术,JPEG系数,奇偶量化器" 在数字世界中,隐写术(Steganography)是一种至关重要的信息隐藏技术,它允许秘密信息被嵌入到常见的数字媒体中,如图像、...
写文章

热门文章

  • 弱网测试整理 6827
  • 零基础篇-03阶段:Django框架与项目-上 4156
  • plantUML语法学习(1)) 2612
  • C++基础了解-17-C++日期 & 时间 2363
  • C++基础了解-10-C++ 判断 2204

分类专栏

  • python_100天 24篇
  • 管理类联考
  • 测试技能学习整理-HM 10篇
  • 测试学习整理-UI自动化
  • 测试知识学习整理补充 9篇
  • 测试学习整理-APP测试专项 1篇
  • 测试学习整理-性能自动化整理 15篇
  • 测试学习整理-接口自动化 7篇
  • 测试学习整理-MySQL 3篇
  • 测试学习整理-Linux 6篇
  • 编程狮微课学习 7篇
  • C++/C# 43篇
  • C语言基础 30篇
  • python基础学习--教程自学 36篇

最新评论

  • 测试技能提升HM-金融类安全类练习

    SeaHaloa: 文章很赞,博主请问下,安享金融后台管理的admin的密码是不是改了? 123456错误

  • 零基础篇-python高级-11-mini-web框架

    weixin_51197295: sql数据的连接有吗

  • 测试技能提升HM-功能测试-测试辅助工具

    CSDN-Ada助手: 恭喜你这篇博客进入【CSDN每天值得看】榜单,全部的排名请看 https://bbs.csdn.net/topics/616897427。

  • 测试技能提升-接口测试11-dubbo接口测试

    CSDN-Ada助手: 恭喜你,获得了 2023 博客之星评选的入围资格,请看这个帖子 (https://bbs.csdn.net/topics/616395297?utm_source=blogger_star_comment)。 请在这里提供反馈: https://blogdev.blog.csdn.net/article/details/129986459?utm_source=blogger_star_comment。

  • 测试技能提升-接口测试10-git/jenkins

    CSDN-Ada助手: 恭喜你这篇博客进入【CSDN每天值得看】榜单,全部的排名请看 https://bbs.csdn.net/topics/616395337。

大家在看

  • 2024年研赛-华为杯数模竞赛C题论文首发+论文讲解+代码分享
  • 实战OpenCV之几何变换 436
  • 基于redis的HyperLogLog数据结构实现的布隆过滤器在信息流中历史数据的应用
  • 分治算法专题(一)——快速排序之【三路划分】/归并排序 926
  • 带你0到1之QT编程:十五、探索QSplitter和QDockWidget的简单应用技巧 252

最新文章

  • docsify搭建笔记博客--本地
  • Linux日志分析命令
  • MySQL必知必会读书笔记(上)【1-17章】
2023年263篇

目录

目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值

玻璃钢生产厂家玻璃钢花盆是怎么做的新疆玻璃钢造型雕塑通化玻璃钢雕塑加工唐山玻璃钢卡通雕塑在家自制玻璃钢花盆视频特色商场美陈销售厂家吉林户外玻璃钢雕塑哪家便宜桐乡玻璃钢门头雕塑郑州佛像玻璃钢雕塑供应商仙桃玻璃钢商场美陈南京城市玻璃钢雕塑制作外贸玻璃钢雕塑价格西安环保玻璃钢雕塑联系方式贵州省玻璃钢雕塑哪里有太原户内玻璃钢雕塑供应商精美玻璃钢卡通雕塑南宁奔技玻璃钢雕塑萍乡玻璃钢雕塑定做价格安阳花朵玻璃钢景观雕塑山东大型商场美陈生产厂家梅州主题玻璃钢雕塑定制价格上海大型商场美陈怎么样直径50玻璃钢花盆价格河北玻璃钢关公雕塑广东主题商场美陈供应河北玻璃钢动物雕塑哪家有商场气球美陈工作室河北多彩玻璃钢雕塑图片乐山玻璃钢人型雕塑许昌室外校园玻璃钢景观雕塑香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化