读后笔记 -- Python 全栈测试开发 Chapter11:Python + Requests 实现接口测试
11.1 Requests 框架
11.1.1 requests 请求
1. reqeusts 库 安装
pip install requests
2. requests 库 GET 方法,参数通过 params 传入
import requests # get 请求 无参数
get_response = requests.get("http://127.0.0.1:30060/login")
print(get_response.text) # get 请求带参数,方式一,添加在 url 中
get_response1 = requests.get("http://127.0.0.1:30060/loginAction?username=admin&password=123456") # get 请求带参数,方式二,放在 params 中
data_userLogin = {
"username": "admin",
"password": "23456"
}
get_response2 = requests.get("http://127.0.0.1:30060/loginAction", params=data_userLogin)
3. requests 库 POST 方法,参数通过 data 传入
import requests data_addUser = {
"customer_name": "admin",
"customer_phone": "13312345678",
"customer_type": "C"
} get_response = requests.post("http://127.0.0.1:30060/addCustomer", data=data_addUser)
print(get_response.json())
4. requests 库的 request 方法
import requests def requests_request():
# get 请求,必须大写
print(requests.request("GET", "http://127.0.0.1:30060/login").status_code)
# post 请求,必须大写
data_addUser = {
"customer_name": "admin",
"customer_phone": "13312345678",
"customer_type": "C"
}
print(requests.request("POST", "http://127.0.0.1:30060/addCustomer", data=data_addUser).json())
11.1.2 requests 响应
- status
- encoding
- headers
- raw:原始内容
- text:字符串形式 -> html
- content:二进制形式 -> pic/video
- json:json 形式 -> json
11.1.4 Requests 处理 session
"""
该例子定义了三个请求,第一个请求与服务器建立连接 -> 第二个请求基于第一个请求,并在响应中提取 session 的 id 值提取出来
-> 作为第三个请求的参数
"""
import requests
import re
from requests import Session # 创建 session 会话对象,用该对象发送后续请求,表示所有请求在同一个会话中完成
session = Session()
session.get("http://localhost:1080/cgi-bin/welcome.pl?signOff=true") # 前后接口请求存在依赖,先发送服务器会产生session 值并在响应中取出来
get_first_response = session.get("http://localhost:1080/cgi-bin/nav.pl?in=home")
get_text = get_first_response.text # 提取 session 值,并使用字符串处理方式
get_index = get_text.find('userSession" value="') + len('userSession" value="')
get_session = get_text[get_index:get_text.find('"', get_index + 1)]
get_session1 = re.findall('userSession" value="(.+?)"', get_text) # 分析: userSession 的值是基于上一个请求服务器响应的,当前请求必须携带 session 值,否则服务器需要重新建立连接,会拒绝当前请求
data = {
'userSession': '%s' %get_session,
'username': 'admin',
'password': 'bean',
'login.x': '49',
'login.y': '15',
'JSFormSubmit': 'off'
}
get_response = session.post("http://localhost:1080/cgi-bin/login.pl", data=data)
print(get_response.text)
11.2 Mock 测试
1. Mock 目的:接口未开发完毕,影响接口测试进度。此时,可通过 mock 进行模拟数据;以及接口存在很多依赖的话,也可以解决依赖(实现解耦操作)。
2. Mock 技术主要分两类:
- 1)Mock 服务:实现 Mock 功能的服务。由于项目中很多应用第三方服务,联调和测试比较麻烦,常见的解决方案是:搭建和部署一个临时服务,来模拟第三方服务进行联调和测试
- 2)Mock 数据:mock 一个对象,通过它进行想要的测试,常见的有:EasyMock,Mockito,WireMock等,主要用于单元测试。
3. Mock 的基本原则是:
- 被测试函数里面所对应的方法,如果没实现,就需要 mock
- 无论对象有多少层,必须保证 mock 的对象是断言所调用的对象(同一对象)
------ Cal.py (该函数方法未实现)------
class Cal(object):
def add(self, a, b):
pass def minus(self, a, b):
pass
------- Mobile.py (该功能已实现) -----
from CH11_Requests.Mock_Tech.SourceDir.Cal import Cal class Mobile(object):
def __init__(self):
self.cal = Cal() def get_add_int(self, a, b):
return int(self.cal.add(a, b)) def get_minus_int(self, a, b):
return int(self.cal.minus(a, b)) # 不同于 cal 对象,此处传入 add 对象
def get_add_int_1(self, add, a, b):
return int(add(a, b))
2.1 一层 mock
---------- CalTest.py ---------
# @Description : mock 一层对象
import unittest
from unittest import mock
from CH11_Requests.Mock_Tech.SourceDir.Cal import Cal class CalTest(unittest.TestCase):
def setUp(self) -> None:
self.cal = Cal() # 假设 cal.minus() 方法输入参数 4,6, 输出 0
def test_minus(self):
get_mock = mock.Mock(return_value=0)
# 此时 self.cal.minus 表示的是对象,如果是 self.cal.minus() 则表示的是 方法
self.cal.minus = get_mock
self.assertEqual(self.cal.minus(4, 6), 0) if __name__ == '__main__':
unittest.main()
2.2 mock 剖析及 二层 mock
------- MobileTest.py (需要测试 Mobile.py 的函数功能)----------
# @Description : 1. mock 实现; 2. 设计原则:源代码和测试代码分开,即可以分开成 SourceDir 和 TestDir
import unittest
from unittest import mock
from CH11_Requests.Mock_Tech.SourceDir.Mobile import Mobile
from CH11_Requests.Mock_Tech.SourceDir.Cal import Cal class MobileTest(unittest.TestCase):
def setUp(self) -> None:
self.mobile = Mobile()
self.cal = Cal() def test_get_add_int_case1(self):
# 创建 mock 对象;模拟 Cal 中 add() 返回值
mock_add = mock.Mock(return_value=6.0)
# 模拟 Mobile get_add_int 对象。(如果模拟 get_add_int 方法,是假设 get_add_int 方法还未实现)
self.mobile.get_add_int = mock_add
self.assertEqual(6, self.mobile.get_add_int(1.2, 5.2)) def test_get_add_int_case2(self):
# 创建 mock 对象;模拟 Cal 中 add() 返回值
mock_add = mock.Mock(return_value=6.0)
# 将 mock 对象赋值给 python 对象。模拟 add 对象,但是没有测试 mobile 的方法 get_add_nit
self.cal.add = mock_add
self.assertEqual(6.0, self.cal.add(2.0, 3.0)) def test_get_add_int_realWant(self):
# 创建 mock 对象;模拟 Cal 中 add() 返回值
mock_add = mock.Mock(return_value=6.0)
# => 实际上 mock 的应该是 mobile cal 的 add 对象。这里指的是 SourceDir 里的 cal.add 对象
self.mobile.cal.add = mock_add
# => 并且测试 mobile 的方法 get_add_int
self.assertEqual(6, self.mobile.get_add_int(2.0, 3.0)) def test_get_add_int_1(self):
# 针对 Mobile 的 get_add_int_1 的形式,使用如下的 mock
# 将 add 以对象形式传入到 get_add_int_1 的方法中。此时,有2种实现:1)mock cal.add 或 2)mock mobile.cal.add
mock_add = mock.Mock(return_value=6.0)
# 2.1) 这个 cal.add 是本测试类的 cal.add 对。该 mock 一层对象(cal.add)
self.cal.add = mock_add
self.assertEqual(6, self.mobile.get_add_int_1(self.cal.add, 2.0, 3.0)) # 2.2) 这个 cal.add 是 SourceDir 的 cal.add 对象。这个是 mock 对象(mobile)里面的对象(cal.add),即 二层对象
self.mobile.cal.add = mock_add
self.assertEqual(6, self.mobile.get_add_int_1(self.mobile.cal.add, 2.0, 3.0)) if __name__ == '__main__':
unittest.main()
2.3 接口 mock
------ Login_Interface.py (该接口服务目前无法启动)--------
import requests def login_interface(data):
get_response = requests.get("http://127.0.0.1:30060/loginAction", params=data)
return get_response.json()
-------- LoginTest.py (需要测试接口功能)---------
# @Description : mock 接口对象
import unittest
import requests
from unittest import mock
from CH11_Requests.Mock_Tech.SourceDir.Login_InterFace import login_interface class LoginTest(unittest.TestCase):
def setUp(self) -> None:
pass def test_login_case1(self):
# mock 接口对象,但是 login_interface 没有调用 Login_Interface 的 login_interface 方法
data = {"username": "admin",
"password": "123456"
}
expect = {"errorcode": "0",
"message": "登录成功"
}
get_mock = mock.Mock(return_value=expect)
# login_interface 是一个对象,所以该处可以 mock 成功
login_interface = get_mock
print(login_interface(data))
# {'errorcode': '0', 'message': '登录成功'} def test_login_case2(self):
# 该例子中, self.get_response 是一个变量,实际运行中没有 mock 成功
data = {"username": "admin",
"password": "123456"
}
self.get_response = requests.get("http://127.0.0.1:30060/loginAction", params=data).json()
get_mock = mock.Mock(return_value=data)
# ConnectionRefusedError: [WinError 10061] 由于目标计算机积极拒绝,无法连接。此时 mock 对象是赋值给一个变量值,不是对象
self.get_response = get_mock
print(self.get_response) def test_login_case3(self):
# mock 传个 对象
data = {"username": "admin",
"password": "123456"
}
# 该例子中, get_response 实际上没有使用到
get_response = lambda: requests.get("http://127.0.0.1:30060/loginAction", params=data).json()
get_mock = mock.Mock(return_value=data)
get_response = get_mock
print(get_response())
# {'username': 'admin', 'password': '123456'} def test_login_real(self):
# mock 传个 对象。区别于 case3, 这里的 get_response 加上了 "self." 作为当期对象
data = {"username": "admin",
"password": "123456"
}
# lambda 生成对象,所以该列子中可以 mock 成功。区别于 case3,加上 self. 之后,self.get_response 对象有实际被使用
self.get_response = lambda: requests.get("http://127.0.0.1:30060/loginAction", params=data).json()
get_mock = mock.Mock(return_value=data)
self.get_response = get_mock
print(self.get_response())
# {'username': 'admin', 'password': '123456'} if __name__ == '__main__':
unittest.main()
11.2.4 重构封装 Mock 服务
1. side_effect 参数:当实际代码已经实现时,需要修改测试代码,让产生的返回值将 return_value 进行覆盖。
------- Cal.py --------
class Cal(object):
def add(self, a, b):
pass # 未实现的方法
def minus(self, a, b):
pass # 已经实现了的方法
def minus1(self, a, b):
return a - b + 2
-------- CalTest.py ---------
import unittest
from unittest import mock
from CH11_Requests.Mock_Tech.SourceDir.Cal import Cal class CalTest(unittest.TestCase):
def setUp(self) -> None:
self.cal = Cal() # 假设 cal.minus() 方法输入参数 4,6, 输出 0
def test_minus(self):
get_mock = mock.Mock(return_value=0)
# 此时 self.cal.minus 表示的是对象,如果是 self.cal.minus() 则表示的是 方法
self.cal.minus = get_mock
self.assertEqual(self.cal.minus(4, 6), 0) # 测试已经实现了的 minus1 方法
# side_effect 参数,将 实际产生的值覆盖 return_value
def test_minus1(self):
get_mock = mock.Mock(return_value=0, side_effect=self.cal.minus1)
self.cal.minus1 = get_mock
self.assertEqual(self.cal.minus1(4, 13), 0)
# 此时输入的参数,得到的结果就断言失败
self.assertEqual(self.cal.minus1(4, 13), -7)
# 该结果正确 if __name__ == '__main__':
unittest.main()
2. 一个用例涉及多个对象
----------- Mobile.py ------------
from CH11_Requests.Mock_Tech.SourceDir.Cal import Cal class Mobile(object):
def __init__(self):
self.cal = Cal() def get_add_int(self, a, b):
return int(self.cal.add(a, b)) def get_minus_int(self, a, b):
return int(self.cal.minus(a, b)) # 不同于 cal 对象,此处传入 add 对象
def get_add_int_1(self, add, a, b):
return int(add(a, b)) def get_add_minus_int(self, a, b):
return int(self.cal.add(a, b) - self.cal.minus(a, b))
--------- MobileTest2.py ---------
import unittest
from unittest import mock
from CH11_Requests.Mock_Tech.SourceDir.Mobile import Mobile
from CH11_Requests.Mock_Tech.SourceDir.Cal import Cal class MobileTest(unittest.TestCase):
def setUp(self) -> None:
self.mobile = Mobile()
self.cal = Cal() def test_get_add_minus_int(self):
# 实现一个测试用例涉及多个对象
mock_add = mock.Mock(return_value=10)
mock_minus = mock.Mock(return_value=0)
self.mobile.cal.add = mock_add # 绝对不能 self.cal.add = mock_add
self.mobile.cal.minus = mock_minus
self.assertEqual(self.mobile.get_add_minus_int(4, 6), 10) if __name__ == '__main__':
unittest.main()
3. mock + parameterized
----- MobileTest3.py --------
# -*- coding: utf-8 -*-
# @Time : 2023/1/8 15:02
# @Author : Bruce He
# @File : MobileTest3.py
# @Project Name: Lesson_FullStack_TD
# @Description : 测试 mock + parameterized
import unittest
from unittest import mock
from parameterized import parameterized
from CH11_Requests.Mock_Tech.SourceDir.Mobile import Mobile
from CH11_Requests.Mock_Tech.SourceDir.Cal import Cal class MobileTest(unittest.TestCase):
def setUp(self) -> None:
self.mobile = Mobile()
self.cal = Cal() @parameterized.expand([(16, 4, 9, 7, 12), (10, 0, 4, 6, 10)])
def test_parameterized_get_add_minus_int(self, mock1, mock2, input1, input2, expect):
# 实现一个测试用例涉及多个对象
mock_add = mock.Mock(return_value=mock1)
mock_minus = mock.Mock(return_value=mock2)
self.mobile.cal.add = mock_add # 此处的 self.mobile.cal.add 是 mock 二层对象
self.mobile.cal.minus = mock_minus
self.assertEqual(self.mobile.get_add_minus_int(input1, input2), expect) if __name__ == '__main__':
unittest.main()
4. mock 对象的主要参数:
- return_value:mock 的返回值
- side_effect:当实际程序代码已实现,不需要调用 mock 对象时,则会调用实际的程序结果覆盖 return_value 的值
5. mock 对象的常用属性:called, call_count, call_args, call_args_list
6. mock 对象的方法(用于断言): assert_called_once(), assert_called_wirh()
读后笔记 -- Python 全栈测试开发 Chapter11:Python + Requests 实现接口测试的更多相关文章
- 大数据全栈式开发语言 – Python
前段时间,ThoughtWorks在深圳举办一次社区活动上,有一个演讲主题叫做“Fullstack JavaScript”,是关于用JavaScript进行前端.服务器端,甚至数据库(MongoDB) ...
- 【Python全栈-后端开发】嵩天老师-Django
嵩天老师-Python云端系统开发入门教程(Django) 视频地址:https://www.bilibili.com/video/av19801429 课前知识储备: 一.课程介绍: 分久必合.合久 ...
- 【Python全栈-后端开发】Django入门基础
Django基础知识 一. 什么是web框架? 框架,即framework,特指为解决一个开放性问题而设计的具有一定约束性的支撑结构,使用框架可以帮你快速开发特定的系统,简单地说,就是你用别人搭建好的 ...
- 【Python全栈-后端开发】Django进阶2-Form表单
Django进阶2-Form表单 Django的Form主要具有一下几大功能: 生成HTML标签(可以保留上次输入内容) 验证用户数据(显示错误信息) HTML Form提交保留上次提交数据 初始化页 ...
- 【Python全栈-后端开发】数据库进阶
数据库进阶 python关于mysql的API---pymysql模块 pymsql是Python中操作MySQL的模块,其使用方法和py2的MySQLdb几乎相同. 模块安装 pip install ...
- 【Python全栈-后端开发】Django进阶之Model操作复习
Django进阶之Model操作复习 一.字段 AutoField(Field) - int自增列,必须填入参数 primary_key=True BigAutoField(AutoField) - ...
- 【Python全栈-后端开发】Django进阶1-分页
Django[进阶篇-1 ]分页 分页 一.Django内置分页 from django.core.paginator import Paginator, EmptyPage, PageNotAnIn ...
- 【Python全栈-后端开发】Django入门基础-2
Django入门基础知识-2 一 .模版 一.模版的组成 HTML代码+逻辑控制代码 二.逻辑控制代码的组成 1 变量(使用双大括号来引用变量) {{var_name}} 2 标签(tag)的使用 ...
- 【Python全栈-后端开发】MySQL数据库-练习题
MySQL数据库-练习题 一.表关系 请创建如下表,并创建相关约束 二.操作表 1.自行创建测试数据 2.查询“生物”课程比“物理”课程成绩高的所有学生的学号: 3.查询平均成绩大于60分的同学的学号 ...
- Python全栈之路----目录
Module1 Python基本语法 Python全栈之路----编程基本情况介绍 Python全栈之路----常用数据类型--集合 Module2 数据类型.字符编码.文件操作 Python全栈之路 ...
随机推荐
- TNF诱导的关节破坏由IL-1介导
TNF诱导的关节破坏由IL-1介导Zwerina J, et al. PNAS.2007;104:11742-7.TNF拮抗剂有效抑制人类类风湿关节炎(RA)的炎症和结构破坏.然而截至目前还不清楚TN ...
- 五十条常用的MySQL语句
1.查询"001"课程比"002"课程成绩高的所有学生的学号:select a.S# from (select s#,score from SC where C ...
- 使用vscode编辑markdown
目录 markdown在vscode中的使用 标题 一级标题 二级标题 三级标题 四级标题 五级标题 六级标题 列表 图片 表格 网址 代码 文本样式 引用 目录 vscode中使用的插件推荐 截图工 ...
- LeetCode-798 得分最高的最小论调 及差分和前缀和的学习
来源:力扣(LeetCode)链接:https://leetcode-cn.com/problems/smallest-rotation-with-highest-score 题目描述 给你一个数组 ...
- Python实战项目6-后端多方式登录接口/手机登录接口
为开源项目共享代码 步骤: 1先fork开源项目 2clone下来,修改代码,进行提交 3提交pr,等作者同意 Pycharm 使用Git 右键文件会列出Git命令 登录注册功能分析 多方式登录接口: ...
- ubuntu 启动脚本变化
ubuntu-16.10 开始不再使用initd管理系统,改用systemd- 快速看了 systemd 的使用方法,发现改动有点大, 包括用 systemctl 命令来替换了 service 和 c ...
- S-HR类加载器的区别
S-HR的addon和sp下的jar包是由不同的类加载器来加载的: 1.如果/root/kingdee/eas/server/lib/sp的包的路径引用的是/root/kingdee/eas/serv ...
- jssip3.9.1的demo,webphone网页电话
用的目前最新的3.9.1版本,全版本在这里: https://jssip.net/download/releases/ https://github.com/versatica/JsSIP 代码: & ...
- npm发布和修改详细教程
登录 登录npm账号(没有账号需要先前往npm注册 https://www.npmjs.com/) npm login npm notice Log in on https://registry.np ...
- Veeva_001常见问题总结
1.时间互动轴:其他代表与该医生的互动记录 2.Call的拜访流程 : 用户在Ipad端先制定计划拜访(已计划:青色)后,需手动同步到服务器上: 进入拜访详细,选择联系人和产品及相关拜访信息等,并'保 ...