Request+Unittest接口测试框架基础搭建

request库基本使用

发送get请求

# 1、GET请求
r = requests.get('https://httpbin.org/ip')
print(r.text)

# 1.1 发送GET请求,带参数
#等同于直接访问https://httpbin.org/get?name=mikezhou
r = requests.get('https://httpbin.org/get', params={'name': 'mikezhou','age':18})
print(r.text)

# 1.2 定制请求头
header = {'user-agent': 'my-app/0.0.1'}
r = requests.get('https://httpbin.org/get', headers=header)
print(r.text)

# 1.3 发送get请求, 加proxy
proxies = {'http': 'http://127.0.0.1:8080',
           'https': 'http://127.0.0.1:8080'}
r=requests.get('https://httpbin.org/get', proxies=proxies)
print(r.text)

# 1.4 发送get请求,加鉴权 -- Basic Auth
from requests.auth import HTTPBasicAuth
r = requests.get('https://api.github.com/user', auth=HTTPBasicAuth('user', 'password'))
print(r.text)

发送post请求

# 通常,你想要发送一些编码为表单形式的数据——非常像一个 HTML 表单。
# 要实现这个,只需简单地传递一个字典给 data 参数, 数据字典在发出请求时会自动编码为表单形式
payload = {'key1': 'value1', 'key2': 'value2'}
r = requests.post('http://httpbin.org/post', data = payload)
print(r.text)

# 2.1 很多时候你想要发送的数据并非编码为表单形式的。
# 如果你传递一个 string 而不是一个 dict。
import json
payload = {'key1': 'value1', 'key2': 'value2'}
r = requests.post('https://api.github.com/some/endpoint', data=json.dumps(payload))
print(r.text)

# 2.2 此处除了可以自行对 dict 进行编码,
# 你还可以使用 json 参数直接传递,然后它就会被自动编码,下述和上述代码等价
r = requests.post('https://api.github.com/some/endpoint', json=payload)
print(r.text)

发送PUT请求

r = requests.put('https://httpbin.org/put', data={'name': 'mikezhou'})
print(r.text)

发送DELETE请求

r = requests.delete('https://httpbin.org/anything', data={'name': 'mikezhou'})
print(r.text)

获取接口返回值

r = requests.post('https://httpbin.org/anything', data={'hello': 'mikezhou'})

# 返回文本型response
print(r.text)
# 获取二进制返回值
print(r.content)

# 返回JSON串
print(r.json())

# 获取请求返回码
print(r.status_code)
#
# # 获取response的headers
print(r.headers)
#
# # 获取response的cookie
print(r.cookies.get_dict())

request保存session

# 初始化一个session对象
s = requests.Session()

# httpbin这个网站允许我们通过如下方式设置,在set后写你需要的值即可
s.get('https://httpbin.org/cookies/set/sessioncookie/mikezhou')
r = s.get('https://httpbin.org/cookies')
print(r.text)

postman代码转换为Request代码

Request+Unittest接口测试框架基础搭建

开始打造一个测试框架

创建框架目录结构

Request+Unittest接口测试框架基础搭建
◆ test_case:存放测试用例
◆ test_data:存放测试数据
◆ report:存放测试报告
◆ common:存放公共方法
◆ lib:存放第三方库
◆ config: 存放环境配置信息
◆ main:框架主入口

使用request封装http请求类

Request+Unittest接口测试框架基础搭建

创建测试用例

第一个例子

Request+Unittest接口测试框架基础搭建

import random
import unittest
from test_project.common.http_requests import HttpRequests

class TestBattal(unittest.TestCase):

    @classmethod
    def setUpClass(cls) -> None:
        cls.url = 'http://127.0.0.1:12356/'
        cls.http = HttpRequests(cls.url)

    def setUp(self) -> None:
        pass

    def tearDown(self) -> None:
        pass

    def test_001_index(self):
        '''测试访问首页'''
        response = TestBattal.http.get()
        self.assertEqual(response.status_code, 200, '请求返回非200')

    def test_002_login(self):
        '''测试登录'''
        payload = {'username': 'mikezhou', 'password': 123456}
        response = TestBattal.http.post('login',params=payload)
        # # 获取登录返回的内容
        globals()["text"] = response.text.split('\n')[1:]  # global()会返回一个字典,这个字典后面的测试方法运行时也可以访问到,用来存放全局数据
        print(globals()["text"])
        self.assertEqual(200, response.status_code, '请求返回非200')
        self.assertIn('10001', response.text, '响应不包含10001')

    def test_003_select(self):
        '''测试选择装备'''

        # 随机选择装备

        globals()["equipmentid"] = random.choice(globals()["text"]).split(':')[0]
        payload = {'equipmentid': globals()["equipmentid"]}
        response = TestBattal.http.post('selectEq',data=payload)
        self.assertEqual(200, response.status_code, '请求返回非200')
        self.assertIn('equipmentid', response.text, '响应不包含equipmentid')

    def test_004_kill(self):
        '''测试杀敌'''
        payload = {'equipmentid': globals()["equipmentid"], 'enemyid': '20001'}
        response = TestBattal.http.post('kill',data=payload)
        self.assertEqual(200, response.status_code, '请求返回非200')
        self.assertIn('win', response.text, '响应不包含win')

if __name__ == '__main__':
    unittest.main()

第二个例子

Request+Unittest接口测试框架基础搭建

import random
import hmac
import hashlib
import json
import unittest
from test_project.common.http_requests import HttpRequests

class TestUserApi(unittest.TestCase):

    @classmethod
    def setUpClass(cls) -> None:
        cls.url = 'http://127.0.0.1:5000/'
        cls.http = HttpRequests(cls.url)
        cls.device_sn = '123456789'
        cls.os_platform = 'ios'
        cls.app_version = '1.0'
        cls.SECRET_KEY = "mikezhou"
        cls.user_id = random.randint(10, 100)  # 随机生成id
        print(cls.user_id)

    @staticmethod
    def get_token():
        '''获取token'''
        uri = '/api/get-token'
        headers = {'device_sn': TestUserApi.device_sn,
                   'os_platform': TestUserApi.os_platform,
                   'app_version': TestUserApi.app_version,
                   'Content-Type': 'application/json'}

        args = (TestUserApi.device_sn, TestUserApi.os_platform, TestUserApi.app_version)
        content = ''.join(args).encode('ascii')
        sign_key = TestUserApi.SECRET_KEY.encode('ascii')
        sign = hmac.new(sign_key, content, hashlib.sha1).hexdigest()
        data = {'sign': sign}
        response = TestUserApi.http.post(uri, data=json.dumps(data), headers=headers)
        print(response.text)
        token = response.json().get('token')
        print(token)
        return token

    def setUp(self) -> None:
        self.headers = {'device_sn': TestUserApi.device_sn,
                   'token': TestUserApi.get_token(),  # 调用静态方法获取token
                   'Content-Type': 'application/json'}
        self.playload = {'name': 'mikezhou'}

    def test_001_createUser(self):
        '''测试创建用户'''
        uri = '/api/users/{}'.format(TestUserApi.user_id) # format方法组装数据
        response = TestUserApi.http.post(uri, data=json.dumps(self.playload), headers=self.headers)
        print(response.text)
        self.assertEqual(response.status_code, 201, '请求返回非201')

    def test_002_query_users(self):
        '''测试查询用户'''
        uri = '/api/users/{}'.format(TestUserApi.user_id)
        response = TestUserApi.http.get(uri, data=json.dumps(self.playload), headers=self.headers)
        print(response.text)
        self.assertEqual(response.status_code, 200, '请求返回非200')
        self.assertIn(json.dumps(self.playload),response.text)

    def test_003_query_all_users(self):
        '''测试查询所有用户'''
        uri = '/api/users'
        response = TestUserApi.http.get(uri, data=json.dumps(self.playload), headers=self.headers)
        print(response.text)
        count = response.json().get('count')
        items = response.json().get('items')
        self.assertEqual(response.status_code, 200, '请求返回非200')
        self.assertEqual(count,len(items))  # 判断数量正确

    def test_004_update_users(self):
        '''测试更新用户'''
        uri = '/api/users/{}'.format(TestUserApi.user_id)
        self.playload = {'name': 'mikezhou_{}'.format(random.randint(1,10))}
        response = TestUserApi.http.put(uri, data=json.dumps(self.playload), headers=self.headers)
        print(response.text)
        self.assertEqual(response.status_code, 200, '请求返回非200')
        self.assertIn(json.dumps(self.playload),response.text)

    def test_005_delete_users(self):
        '''测试删除用户'''
        uri = '/api/users/{}'.format(TestUserApi.user_id)
        self.playload = {'name': 'mikezhou_{}'.format(random.randint(1,10))}
        response = TestUserApi.http.delete(uri, data=json.dumps(self.playload), headers=self.headers)
        print(response.text)
        self.assertEqual(response.status_code, 200, '请求返回非200')

if __name__ == '__main__':
    unittest.main()

创建按类执行测试用例的执行策略

Request+Unittest接口测试框架基础搭建

'''
按指定类运行测试用例
'''

import unittest
from test_project.test_case.test_battal import TestBattal

if __name__ == '__main__':

    # 根据给定的测试类,获取其中所有以test开头的测试方法,并返回一个测试套件
    suite1 = unittest.TestLoader().loadTestsFromTestCase(TestBattal)

    # 将多个测试类加载到测试套件中
    suite = unittest.TestSuite([suite1])

    # 设置verbosity = 2,可以打印出更详细的执行信息
    unittest.TextTestRunner(verbosity=2).run(suite)

输出测试报告

下载类库

如果想要生成 HTML 格式的报告,那么就需要额外借助第三方库(如 HtmlTestRunner)来操作。HTMLTestRunner是一个第三方的unittest HTML报告库,首先我们下载HTMLTestRunner.py,并放到对应目录下。

官方原版:http://tungwaiyip.info/software/HTMLTestRunner.html
GitHub地址:https://github.com/SeldomQA/HTMLTestRunner

Request+Unittest接口测试框架基础搭建

整合到自己的框架中

放到lib目录下
Request+Unittest接口测试框架基础搭建

创建一个新的执行策略
Request+Unittest接口测试框架基础搭建
HTMLTestRunner类说明:
◆ stream : 指定报告的路径
◆ title : 报告的标题
◆ description : 报告的描述

run()方法说明:
◆ suit : 运行的测试套件
◆ rerun :重跑次数
◆ save_last_run :是否保存最后一个结果
Request+Unittest接口测试框架基础搭建

测试框架支持ws协议

首先要安装websocket类库,在common目录下新建ws_websocket.py类
Request+Unittest接口测试框架基础搭建
Request+Unittest接口测试框架基础搭建

ddt数据驱动

参数化测试是一种“数据驱动测试”(Data-Driven Test),在同一个方法上测试不同的参数,以覆盖所有可能的预期分支的结果。它的测试数据可以与测试行为分离,被放入到文件、数据库或者外部介质中,再由测试程序读取。

Python 标准库中的unittest 自身不支持参数化测试,为了解决这个问题,有人专门开发了两个库:一个是ddt ,一个是parameterized 。

Python可以使用xlrd库来读取Excel文件(对xls,xlsx格式文件都可以读取),使用xlwt库来生成Excel文件(只支持到excel 2003,即xls文件),另外openpyxl库同时支持读、写excel文件,且主要针对Excel2007之
后的版本(.xlsx)。

ddt例子

import ddt
import unittest

@ddt.ddt
class MyTest(unittest.TestCase):
    def setUp(self):
        pass

    def tearDown(self):
        pass

    @ddt.data([1, 2, 3, 6], [2, 3, 4, 9], [3, 4, 5, 12])
    # @ddt.data([1,2,3,6])
    @ddt.unpack
    def test_add(self, testdata1, testdata2, testdate3, exceptdata):
        sum = testdata1 + testdata2 + testdate3
        self.assertEqual(sum, exceptdata)

if __name__ == '__main__':
    unittest.main()

ddt数据文件例子

import ddt
import unittest


@ddt.ddt
class MyTest(unittest.TestCase):
    @classmethod
    def setUpClass(self):
        pass

    @classmethod
    def tearDownClass(self):
        pass

    def setUp(self):
        pass

    def tearDown(self):
        pass

    @ddt.file_data('data.json')
    @ddt.unpack
    def test_sum(self, value):
        a, b, sum = value.split('||')
        print(a, b, sum)
        result = int(a) + int(b)
        self.assertEqual(result, int(sum))

if __name__ == '__main__':
    unittest.main()

data.json

[
  "1||2||3",
  "3||2||5",
  "3||5||8"
]

在框架中集成数据驱动能力

Request+Unittest接口测试框架基础搭建

在common中创建parse_excel.py类,此类可以从excel中提取数据

from openpyxl import load_workbook

class ParseExcel(object):
    def __init__(self, excelPath, sheetName):
        print(excelPath,sheetName)
        self.wb = load_workbook(excelPath)
        self.sheet = self.wb[sheetName]
        self.maxRowNum = self.sheet.max_row

    def getDatasFromSheet(self):
        dataList = []
        for line in self.sheet.rows:
            tmpList=[]
            tmpList.append(line[0].value)
            tmpList.append(line[1].value)
            dataList.append(tmpList)
        return dataList[2:]

在test_data目录下存放测试数据的excel文件
Request+Unittest接口测试框架基础搭建
使用ddt实现数据驱动测试

import os
import random
import hmac
import hashlib
import json
import unittest
import ddt
from test_project.common.http_requests import HttpRequests
from test_project.common.parse_excel import ParseExcel


def get_test_data():
    '''
    从外部获取参数数据
    :return:
    '''
    path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'test_data')
    excelPath = os.path.join(path, 'test_user_api_data.xlsx')
    print(excelPath)
    sheetName = '用户参数表'
    return ParseExcel(excelPath, sheetName)

@ddt.ddt
class TestUserApiByTDD(unittest.TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        cls.url = 'http://127.0.0.1:5000/'
        cls.http = HttpRequests(cls.url)
        cls.device_sn = '123456789'
        cls.os_platform = 'ios'
        cls.app_version = '1.0'
        cls.SECRET_KEY = "mikezhou"
        cls.user_id = random.randint(10, 100)
        print(cls.user_id)

	
    @ddt.data(*get_test_data().getDatasFromSheet())
    def test_001_createUser(self, data):
        '''测试创建用户'''
        users, exp = tuple(data)
        print(users,exp)
        uri = '/api/users/{}'.format(users)
        response = TestUserApiByTDD.http.post(uri, data=json.dumps(self.playload), headers=self.headers)
        print(response.text)
        self.assertEqual(response.status_code, 201, '请求返回非201')
        self.assertIn(exp, response.text)

if __name__ == '__main__':
    unittest.main()

mock

mock简单例子

  • 例子1

import requests
from unittest import mock

def send_url():
    url = 'http://127.0.0.1:51000/'
    return requests.get(url)

# response = send_url()
# print(response)

sendUrl = mock.Mock(return_value="hello world")
response = sendUrl()
print(response)

sendUrl = mock.Mock(return_value={"code": 0, "msg": "登陆成功"})
response = sendUrl()
print(response)

sendUrl = mock.Mock(side_effect=ConnectionError('URL地址不通'))
response = sendUrl()
  • 例子2
from unittest import mock
import unittest
from count import Count

class MockDemo(unittest.TestCase):

    def test_001_add(self):
        count = Count()
        count.add = mock.Mock(return_value=13)
        result = count.add(8, 8)
        print(result)
        self.assertEqual(result, 16)

    def test_002_add(self):
        count = Count()
        # 如果return_value和side_effect同时存在,则side_effect会覆盖return_value
        count.add = mock.Mock(return_value=13,side_effect=count.add)
        result = count.add(8, 8)
        print(result)
        self.assertEqual(result, 16)

if __name__ == '__main__':
    unittest.main()
class Count():
    def add(self, a, b):
        return a + b
  • 例子3, mock patch使用示例
    通过装饰器mock方法,通过装饰器mock比前面直接创建mock类更常用。
# pay.py
def zhifu():
    '''假设这里是一个支付的功能,未开发完
    支付成功返回:{"result": "success", "msg":"支付成功"}
    支付失败返回:{"result": "fail", "msg":"余额不足"}
    '''
    pass

def zhifu_statues():
    '''根据支付的结果success or fail,判断跳转到对应页面'''
    result = zhifu()
    try:
        if result["result"] == "success":
            return "支付成功"
        elif result["result"] == "fail":
            return "支付失败"
        else:
            return "未知错误异常"
    except:
        return "Error, 服务端返回异常!"
import unittest
from unittest import mock
import pay

class TestZhifuStatues(unittest.TestCase):
    '''单元测试用例'''

    @mock.patch("pay.zhifu")
    def test_01(self, mock_zhifu):
        '''测试支付成功场景'''
        # 方法一:mock一个支付成功的数据
        pay.zhifu = mock.Mock(return_value={"result": "success", "msg":"支付成功"})
        print(pay.zhifu())

        # 方法二:mock.patch装饰器模拟返回结果
        mock_zhifu.return_value = {"result": "success", "msg":"支付成功"}

        # # 根据支付结果测试页面跳转
        statues = pay.zhifu_statues()
        print(statues)
        self.assertEqual(statues, "支付成功")

if __name__ == "__main__":
    unittest.main()
  • mock 类中的方法
    前面是直接mock模块中的方法,现在讲下如果mock模块中的类的方法
# pay_class.py
class Zhifu():
    def zhifu(self):
        '''假设这里是一个支付的功能,未开发完
        支付成功返回:{"result": "success", "reason":"null"}
        支付失败返回:{"result": "fail", "reason":"余额不足"}
        reason返回失败原因
        '''
        pass

class Statues():
    def zhifu_statues(self):
        '''根据支付的结果success or fail,判断跳转到对应页面'''
        result = Zhifu().zhifu()
        try:
            if result["result"] == "success":
                return "支付成功"
            elif result["result"] == "fail":
                return "支付失败"
            else:
                return "未知错误异常"
        except:
            return "Error, 服务端返回异常!"
import unittest
from unittest import mock
from pay_class import Zhifu,Statues

class Test_zhifu_statues(unittest.TestCase):
    '''单元测试用例'''

    @mock.patch("pay_class.Zhifu")
    def test_01(self, mock_Zhifu):
        '''测试支付成功场景'''
        # 先模拟类,再模拟类的中方法
        pay_class = mock_Zhifu.return_value  # 先返回实例,对类名称替换
        # 通过实例调用方法,再对方法的返回值替换
        pay_class.zhifu.return_value = {"result": "success", "msg":"支付成功"}
        print(pay_class.zhifu())
        # 根据支付结果测试页面跳转
        statues = Statues().zhifu_statues()
        print(statues)
        self.assertEqual(statues, "支付成功")

    @mock.patch("pay_class.Zhifu.zhifu")
    def test_02(self, mock_zhifu):
        '''测试支付失败场景'''
        # 直接模拟类中的方法
        mock_zhifu.return_value = {"result": "fail", "msg": "余额不足"}
        print(mock_zhifu())
        # 根据支付结果测试页面跳转
        statues = Statues().zhifu_statues()
        print(statues)
        self.assertEqual(statues, "支付失败")

    @unittest.mock.patch.object(Zhifu, 'zhifu')
    def test_03(self, mock_obj):
        '''测试支付成功场景,另外一种Mock方式'''
        mock_obj.return_value = {"result": "success", "reason": "Mock成功了,欢呼吧!"}
        statues = Statues().zhifu_statues()
        print(statues)
        self.assertEqual(statues, "支付成功")

    def test_04(self):
        '''测试支付成功场景,最后一种Mock写法'''
        with unittest.mock.patch.object(Zhifu, 'zhifu') as mock_obj:
            mock_obj.return_value = {"result": "success", "reason": "Mock成功了,欢呼吧!"}
            statues = Statues().zhifu_statues()
            print(statues)
            self.assertEqual(statues, "支付成功")

if __name__ == "__main__":
    unittest.main()

mock side_effect使用示例

import unittest
import unittest.mock

class MyTest(unittest.TestCase):

    def test_except(self):
        # 1. 创建Mock()对象,传递异常对象
        mock_obj = unittest.mock.Mock(side_effect=BaseException('自定义异常'))
        # mock_obj是对象,可调用对象,用法和函数一样
        mock_obj()

    def test_list(self):
        # 1. 创建Mock()对象,传递list
        mock_obj = unittest.mock.Mock(side_effect=[1,2,3])
        # mock_obj是对象,可调用对象,用法和函数一样
        print(mock_obj())
        print(mock_obj())
        print(mock_obj())

    def test_func(self):
        def func(a, b):
            return a+b

        # 1. 创建Mock()对象,传递函数名
        mock_obj = unittest.mock.Mock(side_effect=func)
        # mock_obj是对象,可调用对象,用法和函数一样
        print(mock_obj(2, 3))

if __name__ == '__main__':
    unittest.main()
上一篇:uinttest单元测试框架


下一篇:unittest加载自动化测试用例的几种方式