读后笔记 -- 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 re from requests import Session # request1: 创建 session 会话对象,用该对象发送后续请求,表示所有请求在同一个会话中完成 session = Session() session.get("http://localhost:1080/cgi-bin/welcome.pl?signOff=true") # request2: 前后接口请求存在依赖,先发送服务器会产生session 值并在响应中取出来 # 和上面的 session.get() 是不同的 url 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)] # 通过 regex 的方式查找 session 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' } # request 3: get_response = session.post("http://localhost:1080/cgi-bin/login.pl", data=data) print(get_response.text)
11.2 Mock 测试
1. 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_badCase1(self): # 创建 mock 对象;模拟 Cal 中 add() 返回值 mock_add = mock.Mock(return_value=6.0) # 模拟 Mobile get_add_int 对象。(如果模拟 get_add_int 方法,是假设 get_add_int 方法还未实现。实际是 get_add_int()有实现,但其内部调用 cal.add() 未实现) self.mobile.get_add_int = mock_add self.assertEqual(6, self.mobile.get_add_int(1.2, 5.2)) def test_get_add_int_badCase2(self): 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_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_badCase1(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_badCase2(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_badCase3(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_realWant(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,需要 二层mock 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()