读后笔记 -- 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()

 

posted on 2022-12-09 23:13  bruce_he  阅读(87)  评论(0编辑  收藏  举报