基于Requests + UnitTest + Excel + HTMLTestRunner搭建接口测试框架


前言

测试项目:likeshop商城

网址:http://101.43.8.10:6969/ (H5版)

1、登录接口:

2、商品详情接口:

3、添加到购物车接口:

4、生成订单接口:

5、订单详情接口:

接口测试,主要有单接口功能测试和多接口的流程测试。接下来首先以登录接口为介绍对象讲解单接口测试,之后再介绍多接口的流程测试。

单接口功能测试

简单实现

前面的章节已经介绍了登录接口代码,如下:

from common.PLRequests import PLRequests

PLRequests = PLRequests()

"""
1、登录接口:
- 接口文档:http://www.likeshop.cn/doc/2/173
- 登录接口:http://101.43.8.10:6969/api/account/login
"""
url = 'http://101.43.8.10:6969/api/account/login'
data = {
    'account': '17900000001',
    'password': 'a123456',
    'client': 2
}

res = PLRequests.pl_request(method='post', url=url, data=data)
print(res.json())

这里的代码,基本以流水账的形式,接下来一步一步的完善上面的代码,最终形成自己的接口测试框架。

框架的目录结构

组织测试框架,梳理文件结构目录如下:

  • common:存放公共方法
  • test_case:存放测试用例
  • test_data:存放测试数据
  • config:存放配置文件
  • lib:存放第三方库文件
  • run.py:框架主入口文件

封装requests

将封装后的requests存放到common目录中

pl_requests.py

import requests


class PLRequests:
    # 只要调用这个类,就会进入到init这个函数里面去
    def __init__(self):
        # 定义session对象
        self.session = requests.session()

    # 封装请求,这里只对GET、POST、PUT、DELETE四种请求方法做处理
    def pl_request(self, method, url, **kwargs):
        # 将接受到的method的值统一转为大写字母
        method = method.upper()

        if 'GET' == method:
            resp = self.session.request(method=method, url=url, **kwargs)
        elif 'POST' == method:
            resp = self.session.request(method=method, url=url, **kwargs)
        elif 'PUT' == method:
            resp = self.session.request(method=method, url=url, **kwargs)
        elif 'DELETE' == method:
            resp = self.session.request(method=method, url=url, **kwargs)
        else:
            raise "请求方法错误"
        return resp

    # 关闭session对象
    def close_session(self):
        self.session.close()

通过unittest设计登录用例

设计登录测试用例

import unittest
from common.pl_requests import PLRequests
from common.pl_util import json_format_output
import json

# 实例化LBRequests对象
PLRequests = PLRequests()

"""
1、登录接口:
- 接口文档:http://www.likeshop.cn/doc/2/173
- 登录接口:http://101.43.8.10:6969/api/account/login
"""

class TestLogin(unittest.TestCase):

    def test_login(self):
        url = 'http://101.43.8.10:6969/api/account/login'
        data = {
            'account': '17900000001',
            'password': 'a123456',
            'client': 2
        }

        response = PLRequests.pl_request(method='post', url=url, data=data)
        self.assertEqual(200, response.status_code, '请求返回非200')
        self.assertEqual('登录成功', response.json()['msg'], '登录失败')

丰富测试用例,包括用户名错误、密码错误、输入为空等情况:

import unittest
from common.pl_requests import PLRequests

# 实例化LBRequests对象
PLRequests = PLRequests()

"""
1、登录接口:
- 接口文档:http://www.likeshop.cn/doc/2/173
- 登录接口:http://101.43.8.10:6969/api/account/login
"""

class TestLogin(unittest.TestCase):

    def test_01_login_success(self):
        url = 'http://101.43.8.10:6969/api/account/login'
        data = {
            'account': '17900000001',
            'password': 'a123456',
            'client': 2
        }

        response = PLRequests.pl_request(method='post', url=url, data=data)
        self.assertEqual(200, response.status_code, '请求返回非200')
        self.assertEqual('登录成功', response.json()['msg'], '登录失败')

    def test_02_login_wrong_password(self):
        url = 'http://101.43.8.10:6969/api/account/login'
        data = {
            'account': '17900000001',
            'password': 'a123456a',
            'client': 2
        }
        response = PLRequests.pl_request(method='post', url=url, data=data)
        self.assertEqual(200, response.status_code, '请求返回非200')
        self.assertEqual('密码错误', response.json()['msg'])

    def test_03_login_wrong_username(self):
        url = 'http://101.43.8.10:6969/api/account/login'
        data = {
            'account': '17800000001',
            'password': 'a123456',
            'client': 2
        }
        response = PLRequests.pl_request(method='post', url=url, data=data)
        self.assertEqual(200, response.status_code, '请求返回非200')
        self.assertEqual('密码错误', response.json()['msg'])

    def test_04_login_no_username(self):
        url = 'http://101.43.8.10:6969/api/account/login'
        data = {
            'account': None,
            'password': 'a123456',
            'client': 2
        }
        response = PLRequests.pl_request(method='post', url=url, data=data)
        self.assertEqual(200, response.status_code, '请求返回非200')
        self.assertEqual('请输入账号或手机号', response.json()['msg'])

    def test_05_login_no_password(self):
        url = 'http://101.43.8.10:6969/api/account/login'
        data = {
            'account': '17900000001',
            'password': None,
            'client': 2
        }
        response = PLRequests.pl_request(method='post', url=url, data=data)
        self.assertEqual(200, response.status_code, '请求返回非200')
        self.assertEqual('请输入密码', response.json()['msg'])

    def test_06_login_wrong_client(self):
        url = 'http://101.43.8.10:6969/api/account/login'
        data = {
            'account': '17900000001',
            'password': 'a123456',
            'client': 1
        }
        response = PLRequests.pl_request(method='post', url=url, data=data)
        self.assertEqual(200, response.status_code, '请求返回非200')
        self.assertEqual('当前只支持h5和app登录', response.json()['msg'])

可以看到上面的程序虽然已经实现登录的测试用例的执行及校验,但url和测试数据等不仅重复冗余,并且都写死在代码中了,接下来会一步一步的优化完善。

通过DDT进行数据驱动

安装ddt

$ pip install ddt

通过ddt进行数据驱动

import unittest
from common.pl_requests import PLRequests
from ddt import ddt, data, unpack

# 实例化LBRequests对象
PLRequests = PLRequests()

"""
1、登录接口:
- 接口文档:http://www.likeshop.cn/doc/2/173
- 登录接口:http://101.43.8.10:6969/api/account/login
"""


@ddt
class TestLogin(unittest.TestCase):

    @data(('17900000001', 'a123456', 2, "登录成功"), ('17900000001', 'a123456a', 2, "密码错误"), ('17800000001', 'a123456', 2, "密码错误"), (None, 'a123456', 2, "请输入账号或手机号"), ('17900000001', None, 2, "请输入密码"), ('17900000001', 'a123456', 1, "当前只支持h5和app登录"))
    @unpack
    def test_01_login(self, account, password, client, result):
        url = 'http://101.43.8.10:6969/api/account/login'
        data = {
            'account': account,
            'password': password,
            'client': client
        }

        response = PLRequests.pl_request(method='post', url=url, data=data)
        self.assertEqual(200, response.status_code)
        self.assertEqual(result, response.json()['msg'])

执行测试:

 $ python -m unittest test_case/test_login02.py -v
test_01_login_success_1___17900000001____a123456___2___登录成功__ (test_case.test_login02.TestLogin) ... ok
test_01_login_success_2___17900000001____a123456a___2___密码错误__ (test_case.test_login02.TestLogin) ... ok
test_01_login_success_3___17800000001____a123456___2___密码错误__ (test_case.test_login02.TestLogin) ... ok
test_01_login_success_4__None___a123456___2___请输入账号或手机号__ (test_case.test_login02.TestLogin) ... ok
test_01_login_success_5___17900000001___None__2___请输入密码__ (test_case.test_login02.TestLogin) ... ok
test_01_login_success_6___17900000001____a123456___1___当前只支持h5和app登录__ (test_case.test_login02.TestLogin) ... ok

----------------------------------------------------------------------
Ran 6 tests in 0.735s

OK

可以看到@data()中的测试数据过多,接下来会将测试数据提取到文件中进行存取。

通过Yaml文件传DDT数据

安装PyYAML

$ pip install PyYAML

在test_data目录下新建login.yaml文件,设计用例如下:

-
  account: 17900000001
  password: a123456
  client: 2
  result: '登录成功'
-
  account: 17900000001
  password: a123456a
  client: 2
  result: '密码错误'
-
  account: 17800000001
  password: a123456
  client: 2
  result: '密码错误'
-
  account:
  password: a123456
  client: 2
  result: '请输入账号或手机号'
-
  account: 17900000001
  password:
  client: 2
  result: '请输入密码'
-
  account: 17900000001
  password: a123456
  client: 1
  result: '当前只支持h5和app登录'

测试用例文件test_login.py修改如下:

import unittest
from common.pl_requests import PLRequests
from ddt import ddt, data, unpack, file_data

# 实例化LBRequests对象
PLRequests = PLRequests()

"""
1、登录接口:
- 接口文档:http://www.likeshop.cn/doc/2/173
- 登录接口:http://101.43.8.10:6969/api/account/login
"""

@ddt
class TestLogin(unittest.TestCase):

    @file_data('/Users/laobai/workspaces/testdemo03/test_data/login.yaml')
    def test_01_login(self, account, password, client, result):
        url = 'http://101.43.8.10:6969/api/account/login'
        data = {
            'account': account,
            'password': password,
            'client': client
        }

        response = PLRequests.pl_request(method='post', url=url, data=data)
        self.assertEqual(200, response.status_code)
        self.assertEqual(result, response.json()['msg'])

通过Excel文件传DDT数据

安装操作excel的第三方库xlrd

$ pip install xlrd==1.2.0

封装操作excle的方法

import os
import xlrd


# 验证文件是否存在,存在读取,不存在报错
class ExcelReader:
    def __init__(self, excel_file, sheet_by):
        if os.path.exists(excel_file):
            self.excel_file = excel_file
            self.sheet_by = sheet_by
            self.data_list = list()
        else:
            raise FileNotFoundError('文件不存在')

    # 读取sheet方式,名称or索引
    def read_data(self):
        data = None
        workbook = xlrd.open_workbook(self.excel_file)
        if type(self.sheet_by) not in [str, int]:
            raise '请输入正确的sheet索引或sheet名称'
        elif type(self.sheet_by) == int:
            data = workbook.sheet_by_index(self.sheet_by)
        elif type(self.sheet_by) == str:
            data = workbook.sheet_by_name(self.sheet_by)
        # 读取sheet内容,返回list,元素:字典
        # 获取首行的信息
        title = data.row_values(0)
        # 遍历测试行,与首行组成dict,放在list
        for col in range(1, data.nrows):
            col_value = data.row_values(col)
            # 与首行组成字典,放在list
            self.data_list.append(dict(zip(title, col_value)))

        return self.data_list

也可以使用openpyxl库进行操作,二者选一即可。

$ pip install openpyxl

代码如下:

import os
from openpyxl import load_workbook


class ExcelReader:

    def __init__(self, excel_file, sheet_name):
        if os.path.exists(excel_file):
            self.wb = load_workbook(excel_file)
            self.sheet = self.wb[sheet_name]
            self.data_list = list()
        else:
            raise FileNotFoundError('文件不存在')

    def read_data(self):
        data = list(self.sheet.values)
        # 获取首行的信息
        title = data[0]
        # 遍历测试行,与首行组成dict,放在list
        for row_value in data[1:]:
            # 与首行组成字典,放在list
            self.data_list.append(dict(zip(title, row_value)))

        return self.data_list

用excle设计login模块的测试用例,存放在test_data目录下

改写接口测试用例文件test_login.py如下:

import unittest
import ddt
from common.pl_requests import PLRequests
from common.excel_util import ExcelReader

# 实例化LBRequests对象
PLRequests = PLRequests()

"""
1、登录接口:
- 接口文档:http://www.likeshop.cn/doc/2/173
- 登录接口:http://101.43.8.10:6969/api/account/login
"""


@ddt.ddt
class TestLogin(unittest.TestCase):
    reader = ExcelReader('/Users/laobai/workspaces/testdemo03/test_data/login.xlsx', "login")
    cases = reader.read_data()

    @ddt.data(*cases)
    def test_01_login(self, cases):
        print(cases['account'])
        url = 'http://101.43.8.10:6969/api/account/login'
        data = {
            'account': cases['account'],
            'password': cases['password'],
            'client': cases['client']
        }

        response = PLRequests.pl_request(method='post', url=url, data=data)
        self.assertEqual(200, response.status_code)
        self.assertEqual(cases['expected_result'], response.json()['msg'])

上面的代码中,文件路径及url都写死了,接下来继续优化

封装获取文件及目录方法

在common目录下新建path_util.py,代码如下:

import os


# 获取项目的根目录
def get_root_dir():
    # 获取当前文件所在目录
    current_dir = os.path.abspath(__file__)
    # 获取当前文件所在的项目根目录
    root_dir = os.path.dirname(os.path.dirname(current_dir))
    return root_dir


# 获取配置文件的路径
def get_config_path():
    config_path = get_root_dir() + os.sep + "config"
    return config_path


# 获取测试数据Data文件的路径(Excel或yaml)
def get_data_path():
    data_path = get_root_dir() + os.sep + "test_data"
    return data_path

修改test_login.py后如下:

import unittest
import ddt
import os
from common.pl_requests import PLRequests
from common.excel_util import ExcelReader
from common.path_util import get_data_path

# 实例化LBRequests对象
PLRequests = PLRequests()

"""
1、登录接口:
- 接口文档:http://www.likeshop.cn/doc/2/173
- 登录接口:http://101.43.8.10:6969/api/account/login
"""


@ddt.ddt
class TestLogin(unittest.TestCase):
    reader = ExcelReader(get_data_path() + os.sep + 'login.xlsx', "login")
    cases = reader.read_data()

    @ddt.data(*cases)
    def test_01_login(self, cases):
        url = 'http://101.43.8.10:6969/api/account/login'
        data = {
            'account': cases['account'],
            'password': cases['password'],
            'client': cases['client']
        }

        response = PLRequests.pl_request(method='post', url=url, data=data)
        self.assertEqual(200, response.status_code)
        self.assertEqual(cases['expected_result'], response.json()['msg'])

在config目录下创建config.yaml,在这个文件里,添加维护模块与测试数据xlsx文件及sheet页的对应关系,对应上面的login模块,内容如下:

login:
  file_name: "login.xlsx"
  sheet_name: "login"

在path_util.py中增加获取配置文件config.yaml的方法

import os


# 获取项目的根目录
def get_root_Dir():
    # 获取当前文件所在目录
    current_dir = os.path.abspath(__file__)
    # 获取当前文件所在的项目根目录
    Root_Dir = os.path.dirname(os.path.dirname(current_dir))
    return Root_Dir


# 获取配置文件的路径
def get_config_path():
    config_path = get_root_Dir() + os.sep + "config"
    return config_path


# 获取配置文件
def get_config_file():
    config_file = get_config_path() + os.sep + "config.yaml"
    return config_file


# 获取测试数据Data文件的路径(Excel或yaml)
def get_data_path():
    data_path = get_root_Dir() + os.sep + "test_data"
    return data_path

在common目录下,创建config_util.py,用以读取

from common.path_util import get_config_file
from common.yaml_util import YamlReader


class ConfigYaml:
    # 初始化yaml读取配置文件
    def __init__(self):
        self.config = YamlReader(get_config_file()).read_data()

    # 从配置文件中获取相应模块的文件名
    def get_file_name(self, module):
        return self.config[module]['file_name']

    # 从配置文件中获取相应模块的文件名中对应的页签
    def get_sheet_name(self, module):
        return self.config[module]['sheet_name']

修改test_login.py为如下:

import unittest
import ddt
import os
from common.pl_requests import PLRequests
from common.excel_util import ExcelReader
from common.path_util import get_data_path
from common.config_util import ConfigYaml

# 实例化LBRequests对象
PLRequests = PLRequests()
conf_y = ConfigYaml()


""" 
1、登录接口:
- 接口文档:http://www.likeshop.cn/doc/2/173
- 登录接口:http://101.43.8.10:6969/api/account/login
"""


@ddt.ddt
class TestLogin(unittest.TestCase):
    reader = ExcelReader(get_data_path() + os.sep + conf_y.get_file_name('login'), conf_y.get_sheet_name('login'))
    cases = reader.read_data()

    @ddt.data(*cases)
    def test_01_login(self, cases):
        url = 'http://101.43.8.10:6969/api/account/login'
        data = {
            'account': cases['account'],
            'password': cases['password'],
            'client': cases['client']
        }

        response = PLRequests.pl_request(method='post', url=url, data=data)
        self.assertEqual(200, response.status_code)
        self.assertEqual(cases['expected_result'], response.json()['msg'])

执行结果:

$ python -m unittest test_case/test_login06.py -v
test_01_login_success_1 (test_case.test_login06.TestLogin) ... 17900000001
ok
test_01_login_success_2 (test_case.test_login06.TestLogin) ... 17900000001
ok
test_01_login_success_3 (test_case.test_login06.TestLogin) ... 17800000001
ok
test_01_login_success_4 (test_case.test_login06.TestLogin) ... 
ok
test_01_login_success_5 (test_case.test_login06.TestLogin) ... 17900000001
ok
test_01_login_success_6 (test_case.test_login06.TestLogin) ... 17900000001
ok

----------------------------------------------------------------------
Ran 6 tests in 0.838s

OK

上面的代码中,url还是写死的,继续优化。

封装api url地址获取方法

将api的url区分成host + api_url两部分,将host写入config.yaml。api_url,写在测试用例的excel中。

Base:
  test:
    host: "http://101.43.8.10:6969/api/"
  www:
    host: "http://www.plscript.com:6969/api/"
login:
  file_name: "login.xlsx"
  sheet_name: "login"

在config_util.py中增加获取host的方法

from common.path_util import get_config_file
from common.yaml_util import YamlReader


class ConfigYaml:
    # 初始化yaml读取配置文件
    def __init__(self):
        self.config = YamlReader(get_config_file()).read_data()

    # 从配置文件中获取相应模块的文件名
    def get_file_name(self, module):
        return self.config[module]['file_name']

    # 从配置文件中获取相应模块的文件名中对应的页签
    def get_sheet_name(self, module):
        return self.config[module]['sheet_name']

    # 获取测试用host
    def get_host(self):
        return self.config['BASE']['test']['host']

改写test_login.py如下:

import unittest
import ddt
import os
from common.pl_requests import PLRequests
from common.excel_util import ExcelReader
from common.path_util import get_data_path
from common.config_util import ConfigYaml

# 实例化LBRequests对象
PLRequests = PLRequests()
conf_y = ConfigYaml()


""" 
1、登录接口:
- 接口文档:http://www.likeshop.cn/doc/2/173
- 登录接口:http://101.43.8.10:6969/api/account/login
"""


@ddt.ddt
class TestLogin(unittest.TestCase):
    reader = ExcelReader(get_data_path() + os.sep + conf_y.get_file_name('login'), conf_y.get_sheet_name('login'))
    cases = reader.read_data()

    @ddt.data(*cases)
    def test_01_login(self, cases):
        url = conf_y.get_host() + 'account/login'
        data = {
            'account': cases['account'],
            'password': cases['password'],
            'client': cases['client']
        }

        response = PLRequests.pl_request(method='post', url=url, data=data)
        self.assertEqual(200, response.status_code)
        self.assertEqual(cases['expected_result'], response.json()['msg'])

可以看到上面的api地址及data数据,都可以写在测试数据文件的excel中。(这里只是登录接口,其他接口的请求数据不尽相同,在excle中维护更加合理方便)

梳理excel测试数据

将测试数据login.xlsx,改名为likeshop_h5_testcase.xlsx。将测试用例代码中的内容抽取到excle中进行维护,修改为如下图所示:

更新config.yaml的login-file_name

BASE:
  test:
    host: "http://101.43.8.10:6969/api/"
  www:
    host: "http://www.plscript.com:6969/api/"
login:
  file_name: "likeshop_h5_testcase.xlsx"
  sheet_name: "login"

修改test_login.py

import unittest
import json
import ddt
import os
from common.pl_requests import PLRequests
from common.excel_util import ExcelReader
from common.path_util import get_data_path
from common.config_util import ConfigYaml

# 实例化LBRequests对象
PLRequests = PLRequests()
conf_y = ConfigYaml()

""" 
1、登录接口:
- 接口文档:http://www.likeshop.cn/doc/2/173
- 登录接口:http://101.43.8.10:6969/api/account/login
"""


@ddt.ddt
class TestLogin(unittest.TestCase):
    reader = ExcelReader(get_data_path() + os.sep + conf_y.get_file_name('login'), conf_y.get_sheet_name('login'))
    cases = reader.read_data()

    @ddt.data(*cases)
    def test_01_login(self, cases):
        url = conf_y.get_host() + cases['请求URL']
        data = json.loads(cases["请求参数"])
        response = PLRequests.pl_request(cases["请求方法"], url=url, data=data)
        self.assertEqual(200, response.status_code)
        self.assertEqual(cases['期望结果断言'], response.json()['msg'])

测试用例的代码进一步精简,而测试用例的更多细节内容都在excel中进行维护。

但,上面断言的预期结果被简单粗暴的写了msg,这并不合理,下面继续优化。

这里仅是登录接口,请求其他接口时需要携带登录token参数,而token的值是动态生成的,而不能直接写死在表格里,所以请求参数列的内容需要调整。这个调整会在多接口流程测试中进行介绍。

断言优化

上面实现的测试数据表格中,预期结果列中固定写成了msg的验证,但不是所有接口请求都会返回msg,例如首页接口,msg的返回值是空。所以需要优化测试数据表格中预期结果的内容。

接口请求发出后,会返回response,是json格式,而在前面的博客中介绍过一个可以解析json的第三方库jsonpath

可以将测试数据表格中,预期结果列表的内容设计成[{jsonpath提取表达式,预期结果,对比方法(相等、包含等)}]

示例:

[{"expr":"$.data.username","expected":"laobai","type":"eq"},{"expr":"$.data.goods_name","expected":"钢笔","type":"eq"}]

然后将断言单独封装起来,在断言模块中实现从excel中获取预期结果列及实际结果和预期结果的断言操作。

按照[{"expr": "$.msg", "expected": "登录成功", "type": "eq"}, {"expr": "$.code", "expected": "1", "type": "eq"}]的格式,修改likeshop_h5_testcase.xlsx中期望结果断言列的内容如下图

封装断言方法assert_util.py

import ast
import jsonpath


def pl_assert(check_str, response_dict):
    """
    :param check_str: 从excel中读取出来的断言列,是一个列表形式的字符串,里面的成员是一个断言
    :param response_dict: 接口请求之后的响应数据,是字典类型。
    :return:
    """
    # 所有断言比对结果列表
    check_res = []
    # 把字符串转换成python列表
    check_list = ast.literal_eval(check_str)

    for check in check_list:
        # 通过jsonpath表达式,从响应结果当中拿到实际结果
        actual = jsonpath.jsonpath(response_dict, check["expr"])
        if isinstance(actual, list):
            actual = str(actual[0])
        # 与实际结果做比对,eq等于,in包含
        if check["type"] == "eq":
            check_res.append(actual == check['expected'])
            print(actual)
            print(check['expected'])
        elif check["type"] == "in":
            check_res.append(check['expected'] in actual)

    if False in check_res:
        raise AssertionError
    else:
        print("断言成功!")

修改test_login.py中断言的方法

import unittest
import json
import ddt
import os
from common.pl_requests import PLRequests
from common.excel_util import ExcelReader
from common.path_util import get_data_path
from common.config_util import ConfigYaml
from common.assert_util import pl_assert

# 实例化LBRequests对象
PLRequests = PLRequests()
conf_y = ConfigYaml()

""" 
1、登录接口:
- 接口文档:http://www.likeshop.cn/doc/2/173
- 登录接口:http://101.43.8.10:6969/api/account/login
"""


@ddt.ddt
class TestLogin(unittest.TestCase):
    reader = ExcelReader(get_data_path() + os.sep + conf_y.get_file_name('login'), conf_y.get_sheet_name('login'))
    cases = reader.read_data()

    @ddt.data(*cases)
    def test_01_login(self, cases):
        url = conf_y.get_host() + cases['请求URL']
        data = json.loads(cases["请求参数"])
        response = PLRequests.pl_request(cases["请求方法"], url=url, data=data)
        pl_assert(cases["期望结果断言"], response.json())

执行结果:

$ python -m unittest test_case/test_login07.py -v
test_01_login_success_1 (test_case.test_login07.TestLogin) ... 断言成功!
ok
test_01_login_success_2 (test_case.test_login07.TestLogin) ... 断言成功!
ok
test_01_login_success_3 (test_case.test_login07.TestLogin) ... 断言成功!
ok
test_01_login_success_4 (test_case.test_login07.TestLogin) ... 断言成功!
ok
test_01_login_success_5 (test_case.test_login07.TestLogin) ... 断言成功!
ok
test_01_login_success_6 (test_case.test_login07.TestLogin) ... 断言成功!
ok

----------------------------------------------------------------------
Ran 6 tests in 0.740s

OK

增加日志

为了方便查看并调试脚本的运行情况,为框架增加日志记录功能。

在pl_util.py中增加日志级别定义函数和获取log文件存放地址的文件名的方法。(由于相互引用的问题,暂时未存放在path_util.py中)

import datetime
import json
import logging
import os
from common.config_util import ConfigYaml
from common.path_util import get_log_path


# 获取当前日期格式
def get_now_date_str():
    return datetime.datetime.now().strftime("%Y-%m-%d")


# 从config.yaml中获取log级别
def get_log_level():
    # 日志文件级别
    loglevel = ConfigYaml().get_log_level()
    # 日志对应
    log_l = {
        "info": logging.INFO,
        "debug": logging.DEBUG,
        "warning": logging.WARNING,
        "error": logging.ERROR,
        "critical": logging.CRITICAL
    }
    return log_l[loglevel]


# 获取log文件
def get_log_file():
    logfile = get_log_path() + os.sep + get_now_date_str() + ".log"
    return logfile

在config.yaml中设置默认的日志输出级别

BASE:
  log_level: "debug"
  test:
    host: "http://101.43.8.10:6969/api/"
  www:
    host: "http://www.plscript.com:6969/api/"
login:
  file_name: "likeshop_h5_testcase_bak.xlsx"
  sheet_name: "login"

在config_util.py中增加获取日志级别的方法

from common.path_util import get_config_file
from common.yaml_util import YamlReader


class ConfigYaml:
    # 初始化yaml读取配置文件
    def __init__(self):
        self.config = YamlReader(get_config_file()).read_data()

    # 从配置文件中获取相应模块的文件名
    def get_file_name(self, module):
        return self.config[module]['file_name']

    # 从配置文件中获取相应模块的文件名中对应的页签
    def get_sheet_name(self, module):
        return self.config[module]['sheet_name']

    # 获取测试用host
    def get_host(self):
        return self.config['BASE']['test']['host']

    # 获取log级别
    def get_log_level(self):
        return self.config["BASE"]["log_level"]

引入封装好的log_util.py

import logging
from logging import Logger
from common.pl_util import get_log_file, get_log_level


class PLLogger(Logger):

    def __init__(self, name, level=logging.INFO, file=None):
        # 调用父类的初始化方法
        super().__init__(name, level)
        # 定义日志输出格式
        fmt_str = "%(asctime)s %(name)s %(levelname)s %(filename)s [%(lineno)d] %(message)s "
        # 实例化一个日志格式类
        formatter = logging.Formatter(fmt_str)

        # log输出到终端控制台
        stream_handle = logging.StreamHandler()
        stream_handle.setFormatter(formatter)
        # 将输出渠道与日志收集器绑定
        self.addHandler(stream_handle)

        if file:
            # log写入到文件
            file_handle = logging.FileHandler(file, encoding="utf-8")
            file_handle.setFormatter(formatter)
            # 将输出渠道与日志收集器绑定
            self.addHandler(file_handle)


pl_log = PLLogger(name='PLS自动化测试', level=get_log_level(), file=get_log_file())

在pl_requests.py中增加日志记录

import requests
from common.log_util import pl_log
from common.pl_util import json_format_output


class PLRequests:
    # 只要调用这个类,就会进入到init这个函数里面去
    def __init__(self):
        # 定义session对象
        self.session = requests.session()

    # 封装请求,这里只对GET、POST、PUT、DELETE四种请求方法做处理
    def pl_request(self, method, url, **kwargs):
        # 将接受到的method的值统一转为大写字母
        method = method.upper()

        if 'GET' == method:
            pl_log.info("发送get请求")
            resp = self.session.request(method=method, url=url, **kwargs)
        elif 'POST' == method:
            pl_log.info("发送post请求")
            resp = self.session.request(method=method, url=url, **kwargs)
        elif 'PUT' == method:
            pl_log.info("发送put请求")
            resp = self.session.request(method=method, url=url, **kwargs)
        elif 'DELETE' == method:
            pl_log.info("发送delete请求")
            resp = self.session.request(method=method, url=url, **kwargs)
        else:
            raise "请求方法错误"
        pl_log.info("响应结果:\n{}".format(json_format_output(resp)))
        return resp

    # 关闭session对象
    def close_session(self):
        self.session.close()

在assert_util.py增加日志记录

import ast
import jsonpath
from common.log_util import pl_log


def pl_assert(check_str, response_dict):

    # 所有断言比对结果列表
    check_res = []
    # 把字符串转换成python列表
    check_list = ast.literal_eval(check_str)
    pl_log.info("从Excel中提取的断言字段:{}".format(check_list))

    for check in check_list:
        pl_log.info("要断言的内容为:{}".format(check))
        # 通过jsonpath表达式,从响应结果当中拿到实际结果
        actual = jsonpath.jsonpath(response_dict, check["expr"])
        if isinstance(actual, list):
            actual = str(actual[0])
        pl_log.info("从响应结果中提取到的值为:{}".format(actual))
        pl_log.info("期望结果为:{}".format(check['expected']))
        # 与实际结果做比对,eq等于,in包含
        if check["type"] == "eq":
            pl_log.info("提取值与期望结果的断言结果为:{}".format(actual == check['expected']))
            check_res.append(actual == check['expected'])
        elif check["type"] == "in":
            pl_log.info("提取值与期望结果的断言结果为:{}".format(check['expected'] in actual))
            check_res.append(check['expected'] in actual)

    if False in check_res:
        pl_log.error("断言失败,可通过查看日志来判断失败位置!")
        raise AssertionError
    else:
        pl_log.info("断言成功!")

在test_login.py中增加日志记录

import unittest
import json
import ddt
import os
from common.pl_requests import PLRequests
from common.excel_util import ExcelReader
from common.path_util import get_data_path
from common.config_util import ConfigYaml
from common.assert_util import pl_assert
from common.log_util import pl_log

# 实例化LBRequests对象
PLRequests = PLRequests()
conf_y = ConfigYaml()

""" 
1、登录接口:
- 接口文档:http://www.likeshop.cn/doc/2/173
- 登录接口:http://101.43.8.10:6969/api/account/login
"""


@ddt.ddt
class TestLogin(unittest.TestCase):
    reader = ExcelReader(get_data_path() + os.sep + conf_y.get_file_name('login'), conf_y.get_sheet_name('login'))
    cases = reader.read_data()

    @ddt.data(*cases)
    def test_01_login(self, cases):
        url = conf_y.get_host() + cases['请求URL']
        data = json.loads(cases["请求参数"])
        pl_log.info("请求方法:{}".format(cases["请求方法"]))
        pl_log.info("请求url:{}".format(url))
        pl_log.info("请求数据:{}".format(data))
        response = PLRequests.pl_request(cases["请求方法"], url=url, data=data)
        pl_assert(cases["期望结果断言"], response.json())

执行结果(由于篇幅问题,只执行了登录成功的用例):

$ python -m unittest test_case/test_login08.py
2023-01-10 19:42:18,681 PLS自动化测试 INFO test_login08.py [36] 请求方法:POST 
2023-01-10 19:42:18,682 PLS自动化测试 INFO test_login08.py [37] 请求url:http://101.43.8.10:6969/api/account/login 
2023-01-10 19:42:18,682 PLS自动化测试 INFO test_login08.py [38] 请求数据:{'account': '17900000001', 'password': 'a123456', 'client': '2'} 
2023-01-10 19:42:18,682 PLS自动化测试 INFO pl_requests.py [25] 发送post请求 
2023-01-10 19:42:18,874 PLS自动化测试 INFO pl_requests.py [35] 响应结果:
{
    "code": 1,
    "msg": "登录成功",
    "data": {
        "id": 1,
        "nickname": "测试账号一",
        "avatar": "http://101.43.8.10:6969/static/common/image/default/user.png",
        "level": 1,
        "disable": 0,
        "distribution_code": "TVHYKM",
        "token": "ae8d0d7197b612b598efcbc6ca3bdf7b"
    },
    "show": 0,
    "time": "0.093085"
} 
2023-01-10 19:42:18,875 PLS自动化测试 INFO assert_util.py [20] 从Excel中提取的断言字段:[{'expr': '$.msg', 'expected': '登录成功', 'type': 'eq'}, {'expr': '$.cod, 'type': 'eq'}] 
2023-01-10 19:42:18,875 PLS自动化测试 INFO assert_util.py [23] 要断言的内容为:{'expr': '$.msg', 'expected': '登录成功', 'type': 'eq'} 
2023-01-10 19:42:18,876 PLS自动化测试 INFO assert_util.py [28] 从响应结果中提取到的值为:登录成功 
2023-01-10 19:42:18,876 PLS自动化测试 INFO assert_util.py [29] 期望结果为:登录成功 
2023-01-10 19:42:18,876 PLS自动化测试 INFO assert_util.py [32] 提取值与期望结果的断言结果为:True 
2023-01-10 19:42:18,876 PLS自动化测试 INFO assert_util.py [23] 要断言的内容为:{'expr': '$.code', 'expected': '1', 'type': 'eq'} 
2023-01-10 19:42:18,877 PLS自动化测试 INFO assert_util.py [28] 从响应结果中提取到的值为:1 
2023-01-10 19:42:18,877 PLS自动化测试 INFO assert_util.py [29] 期望结果为:1 
2023-01-10 19:42:18,877 PLS自动化测试 INFO assert_util.py [32] 提取值与期望结果的断言结果为:True 
2023-01-10 19:42:18,877 PLS自动化测试 INFO assert_util.py [42] 断言成功! 
.
----------------------------------------------------------------------
Ran 1 test in 0.197s

OK

生成报告

测试报告,这里使用unittestreport

安装

$ pip install unittestreport

在项目根目录中新建run.py

import unittest
import unittestreport
from common.path_util import get_report_path
from test_case.test_login08 import TestLogin

if __name__ == '__main__':
    suite_1 = unittest.TestLoader().loadTestsFromTestCase(TestLogin)
    suites = unittest.TestSuite([suite_1])
    runner = unittestreport.TestRunner(suites,
                                       tester='老百',
                                       filename="report.html",
                                       report_dir=get_report_path(),
                                       title='PLS自动化测试报告',
                                       desc='用例执行情况',
                                       templates=1
                                       )
    runner.run()

动态获取用例描述

修改ddt.py源码(383行(版本不同代码位置可能会略有偏差))

test_data_docstring = _get_test_data_docstring(func, v)

替换成

try:
    test_data_docstring = v['接口名称']
except KeyError:
    test_data_docstring = _get_test_data_docstring(func, v)

其中”接口名称”,需要根自己excel中测试数据列名保持一致。

执行run.py

发送报告到指定邮箱

上面的测试报告库unittestreport自带了发送邮件的功能

import unittest
import unittestreport
from common.path_util import get_report_path
from test_case.test_login08 import TestLogin

if __name__ == '__main__':
    suite_1 = unittest.TestLoader().loadTestsFromTestCase(TestLogin)
    suites = unittest.TestSuite([suite_1])
    runner = unittestreport.TestRunner(suites,
                                       tester='老百',
                                       filename="report.html",
                                       report_dir=get_report_path(),
                                       title='PLS自动化测试报告',
                                       desc='用例执行情况',
                                       templates=1
                                       )
    runner.run()
    runner.send_email(host="smtp.qiye.aliyun.com",
                      port=465,
                      user="123412341234@plscript.cn",
                      password="123412341234",
                      to_addrs="123412341234@qq.com")

执行测试后会发送测试报告到指定邮箱

可以将send_mail()函数对应的参数都写在config.yaml中,方便维护。具体步骤略。

import unittest
import unittestreport
from common.path_util import get_report_path
from common.config_util import ConfigYaml
from common.path_util import get_testcase_path

if __name__ == '__main__':
    conf_y = ConfigYaml()
    suites = unittest.defaultTestLoader.discover(get_testcase_path())
    # TesterRunner()的参数也可以提取到配置文件中,这里暂时略
    runner = unittestreport.TestRunner(suites,
                                       tester='老百',
                                       filename="report.html",
                                       report_dir=get_report_path(),
                                       title='PLS自动化测试报告',
                                       desc='用例执行情况',
                                       templates=1
                                       )
    runner.run()
    runner.send_email(host=conf_y.get_email_info()['smtpserver'],
                      port=conf_y.get_email_info()['port'],
                      user=conf_y.get_email_info()['username'],
                      password=conf_y.get_email_info()['password'],
                      to_addrs=conf_y.get_email_info()['receiver'])

在unnttestreport源码中增加log日志收集。执行测试

$ python run.py
2023-01-12 17:39:42,061 PLS自动化测试 INFO test_login08.py [37] 请求方法:POST 
2023-01-12 17:39:42,061 PLS自动化测试 INFO test_login08.py [38] 请求url:http://101.43.8.10:6969/api/account/login 
2023-01-12 17:39:42,061 PLS自动化测试 INFO test_login08.py [39] 请求数据:{'account': '17900000001', 'password': 'a123456', 'client': '2'} 
2023-01-12 17:39:42,061 PLS自动化测试 INFO pl_requests.py [25] 发送post请求 
2023-01-12 17:39:42,256 PLS自动化测试 INFO pl_requests.py [35] 响应结果:
{
    "code": 1,
    "msg": "登录成功",
    "data": {
        "id": 1,
        "nickname": "测试账号一",
        "avatar": "http://101.43.8.10:6969/static/common/image/default/user.png",
        "level": 1,
        "disable": 0,
        "distribution_code": "TVHYKM",
        "token": "8c822a8542134a763208ac5bb3b45fd1"
    },
    "show": 0,
    "time": "0.088353"
} 
2023-01-12 17:39:42,258 PLS自动化测试 INFO assert_util.py [20] 从Excel中提取的断言字段:[{'expr': '$.msg', 'expected': '登录成功', 'type': 'eq'}, {'expr': '$.code', 'expected': '1', 'type': 'eq'}] 
2023-01-12 17:39:42,258 PLS自动化测试 INFO assert_util.py [23] 要断言的内容为:{'expr': '$.msg', 'expected': '登录成功', 'type': 'eq'} 
2023-01-12 17:39:42,259 PLS自动化测试 INFO assert_util.py [28] 从响应结果中提取到的值为:登录成功 
2023-01-12 17:39:42,259 PLS自动化测试 INFO assert_util.py [29] 期望结果为:登录成功 
2023-01-12 17:39:42,259 PLS自动化测试 INFO assert_util.py [32] 提取值与期望结果的断言结果为:True 
2023-01-12 17:39:42,259 PLS自动化测试 INFO assert_util.py [23] 要断言的内容为:{'expr': '$.code', 'expected': '1', 'type': 'eq'} 
2023-01-12 17:39:42,259 PLS自动化测试 INFO assert_util.py [28] 从响应结果中提取到的值为:1 
2023-01-12 17:39:42,259 PLS自动化测试 INFO assert_util.py [29] 期望结果为:1 
2023-01-12 17:39:42,259 PLS自动化测试 INFO assert_util.py [32] 提取值与期望结果的断言结果为:True 
2023-01-12 17:39:42,259 PLS自动化测试 INFO assert_util.py [42] 断言成功! 
2023-01-12 17:39:42,260 PLS自动化测试 INFO testResult.py [107] test_01_login_1 (test_case.test_login08.TestLogin)执行——>【通过】 
2023-01-12 17:39:42,260 PLS自动化测试 INFO testRunner.py [74] 所有用例执行完毕,正在生成测试报告中...... 
2023-01-12 17:39:42,278 PLS自动化测试 INFO testRunner.py [120] 测试报告已经生成,报告路径为:/Users/laobai/workspaces/testdemo03/report/report.html 
2023-01-12 17:39:43,098 PLS自动化测试 INFO resultPush.py [79] 测试报告邮件发送成功! 

多接口流程测试

基本实现

前面的章节已经介绍了订单流程多接口测试的代码,如下:

from common.pl_requests import PLRequests

PLRequests = PLRequests()

"""
1、登录接口:
- 接口文档:http://www.likeshop.cn/doc/2/173
- 登录接口:http://101.43.8.10:6969/api/account/login
"""
url1 = 'http://101.43.8.10:6969/api/account/login'
data1 = {
    'account': '17900000001',
    'password': 'a123456',
    'client': 2
}

res1 = PLRequests.pl_request(method='post', url=url1, data=data1)
print(res1.json())


"""
2、商品详情接口:
- 接口文档:http://www.likeshop.cn/doc/2/209
- 登录接口:http://101.43.8.10:6969/api/goods/getGoodsDetail
"""
url2 = 'http://101.43.8.10:6969/api/goods/getGoodsDetail'
params = {
    'id': 4
}

res2 = PLRequests.pl_request(method='get', url=url2, params=params)
print(res2.json())


"""
3、添加到购物车接口:

- 接口文档:http://www.likeshop.cn/doc/2/183
- 登录接口:http://101.43.8.10:6969/api/cart/add
"""
url3 = 'http://101.43.8.10:6969/api/cart/add'
data3 = {
    'item_id': 1,
    'goods_num': 1
}
headers = {
    'token': res1.json()['data']['token']
}
res3 = PLRequests.pl_request(method='post', url=url3, data=data3, headers=headers)
print(res3.json())


"""
4、生成订单接口:
- 接口文档:http://www.likeshop.cn/doc/2/272
- 登录接口:http://101.43.8.10:6969/api/order/buy
"""
url4 = 'http://101.43.8.10:6969/api/order/buy'
json = {
    "action": "submit",
    "goods": [{
        "item_id": 1,
        "num": 1
    }],
    "pay_way": 3,
    "use_integral": 0,
    "type": "cart"
}

res4 = PLRequests.pl_request(method='post', url=url4, json=json, headers=headers)
print(res4.json())


"""
5、订单详情接口:

- 接口文档:http://www.likeshop.cn/doc/2/273
- 登录接口:http://101.43.8.10:6969/api/order/detail
"""
url5 = 'http://101.43.8.10:6969/api/order/detail'
params5 = {
    "id": res4.json()['data']['order_id']
}

res5 = PLRequests.pl_request(method='get', url=url5, params=params5, headers=headers)
print(res5.json())

这里的代码,基本以流水账的形式,接下来会根据上面介绍的步骤,一步一步的完善上面的代码,完善框架。

用unittest设计流程用例

登录已经在上面的测试用例测试过了,当前用例的重点是走订单流程,所以这里将流程用例中的登录写在setUpClass()中。(这里的登录,仅仅是为了提供后续操作的token,后续会将生成token单独封装)

import unittest
from common.pl_requests import PLRequests

PLRequests = PLRequests()


class TestOrderFlow(unittest.TestCase):
    order_id = None

    @classmethod
    def setUpClass(cls) -> None:
        url1 = 'http://101.43.8.10:6969/api/account/login'
        data1 = {
            'account': '17900000001',
            'password': 'a123456',
            'client': 2
        }

        response = PLRequests.pl_request(method='post', url=url1, data=data1)
        cls.token = response.json()['data']['token']

    def test_goods_detail(self):
        url2 = 'http://101.43.8.10:6969/api/goods/getGoodsDetail'
        params = {
            'id': 4
        }

        response = PLRequests.pl_request(method='get', url=url2, params=params)
        self.assertEqual(200, response.status_code)

    def test_add_cart(self):
        url3 = 'http://101.43.8.10:6969/api/cart/add'
        data3 = {
            'item_id': 1,
            'goods_num': 1
        }
        headers = {
            'token': self.token
        }
        response = PLRequests.pl_request(method='post', url=url3, data=data3, headers=headers)
        self.assertEqual(200, response.status_code)

    def test_buy(self):
        url4 = 'http://101.43.8.10:6969/api/order/buy'
        json = {
            "action": "submit",
            "goods": [{
                "item_id": 1,
                "num": 1
            }],
            "pay_way": 3,
            "use_integral": 0,
            "type": "cart"
        }
        headers = {
            'token': self.token
        }

        response = PLRequests.pl_request(method='post', url=url4, json=json, headers=headers)
        self.order_id = response.json()['data']['order_id']
        # print(response.json())
        self.assertEqual(200, response.status_code)

    def test_order_detail(self):
        url5 = 'http://101.43.8.10:6969/api/order/detail'
        params5 = {
            "id": self.order_id
        }
        headers = {
            'token': self.token
        }

        response = PLRequests.pl_request(method='get', url=url5, params=params5, headers=headers)
        # print(response.json())
        self.assertEqual(200, response.status_code)

可以看到上面的代码中,有两个动态参数:tokenorder_id,从前一个接口的返回值中获取,传给下一个接口做为参数。接下来将测试用到的数据都整理到excel中,并实现参数的传值替换。

在excel中设计测试数据

从订单详情接口的请求数据可以看到:

def test_order_detail(self):
       url5 = 'http://101.43.8.10:6969/api/order/detail'
       params5 = {
           "id": self.order_id
       }
       headers = {
           'token': self.token
       }

       response = PLRequests.pl_request(method='get', url=url5, params=params5, headers=headers)
       self.assertEqual(200, response.status_code)

订单id(order_id)和token是动态变化的,需要从前面请求的接口获取到值,所以要将其设置成变量,在程序代码中进行替换。可以参考譬如postman中,变量用两个大括号括起来,这里使用“#token#”的格式。将上面请求体中的变量替换如下:

params5 = {
    "id": #order_id#
}
headers = {
    'token': #token#
}

在excel中新增extract列,用于提取当前行请求结果中提取需要传递的参数,必填tokenorder_id等。

另由于当前示例项目的原因,需要根据请求类型,判断请求数据,已经是否携带headers等,最终excel用例表格设计如下:

实现请求数据的替换

从接口请求返回的response中提取参数,比如token,需要临时存放在一个位置,postman等工具是存在一个全局变量的空间中,这里定义一个空的Data类,只存放临时参数数据。

data_util.py

class Data:
    # 存储全局变量 - 接口返回值中提取的占位符变量
    pass

在common目录下新建一个extract_util.py,实现从response中提取需要的参数的过程

import jsonpath
from common.data_util import Data
from common.log_util import pl_log


def extract_data_from_response(extract_epr, response):
    """
    从响应结果当中提取值,并设置为Data类的属性
    :param extract_epr: excel中extract列中的提取表达式,是一个字典形式的字符串
                        其中key是全局变量名,value是jsonpath表达式
    :param response: 前置条件的请求响应结果,字典类型
    :return: None
    """
    # 从excel中读取的提取表达式,转成字典对象
    extract_dict = eval(extract_epr)
    # 遍历上面字典中的key,value值,其中key是全局变量名,value是jsonpath表达式
    for key, value in extract_dict.items():
        pl_log.info("提取的变量名是:{},提取的jsonpath表达式是:{}".format(key, value))
        # 根据jsonpath从响应结果中提取真正的值
        res = jsonpath.jsonpath(response, value)
        pl_log.info("jsonpath提取之后的值为:{}".format(res))
        # 如果提取到了值,就将它设置为Data类的属性。key是全局变量名,res[0]就是提取后的值
        if res:
            setattr(Data, key, str(res[0]))
            pl_log.info("提取的变量名是:{},提取到的值是:{},并设置为Data类的属性和值".format(key, str(res[0])))

这里在data_util.py中继续实现根据提取到的替换值和excel中对应的变量值#mark#进行替换。也可以新建一个工具模块进行实现。

import json
import re
from common.log_util import pl_log


class Data:
    # 存储全局变量 - 接口返回值中提取的占位符变量
    pass

def replace_case_with_re(case_dict):
    """
    :param case_dict: 从excel当中读取出来的一行测试数据,字典形式
    """
    cases_str = str(case_dict)
			。。。
    return new_case_dist

改写封装后的pl_requests

请求参数中增加了对headers数据的处理,比如增加token,这里将get请求和post请求做一下修改

import requests
import json as jsonlib
from common.log_util import pl_log
from common.pl_util_plus import json_format_output


class PLRequests:
    # 只要调用这个类,就会进入到init这个函数里面去
    def __init__(self):
        # 定义session对象
        self.session = requests.session()

    # 封装请求,这里只对GET、POST、PUT、DELETE四种请求方法做处理
    def pl_request(self, method, url, headers=None, params=None, data=None, json=None, **kwargs):
        # 将接受到的method的值统一转为大写字母
        method = method.upper()

        pl_log.info("请求方法:{}".format(method))
        pl_log.info("请求url:{}".format(url))
        pl_log.info("请求headers:{}".format(headers))
        pl_log.info("请求参数(params):{}".format(params))
        pl_log.info("请求数据(data):{}".format(data))

        if 'GET' == method:
            pl_log.info("发送get请求")
            response = self.session.request(method=method, url=url, headers=headers, params=params, **kwargs)
        elif 'POST' == method:
            pl_log.info("发送post请求")
            response = self.session.request(method=method, url=url, headers=headers, data=data, **kwargs)
        elif 'PUT' == method:
            pl_log.info("发送put请求")
            response = self.session.request(method=method, url=url, **kwargs)
        elif 'DELETE' == method:
            pl_log.info("发送delete请求")
            response = self.session.request(method=method, url=url, **kwargs)
        else:
            raise "请求方法错误"
        pl_log.info("响应结果:\n{}".format(json_format_output(response)))
        return response

    # 关闭session对象
    def close_session(self):
        self.session.close()

设计测试用例代码

import unittest
import json
import ddt
import os
from common.pl_requests import PLRequests
from common.excel_util import ExcelReader
from common.path_util import get_data_path
from common.config_util import ConfigYaml
from common.assert_util import pl_assert
from common.extract_util import extract_data_from_response
from common.data_util import replace_case_with_re
from common.log_util import pl_log

PLRequests = PLRequests()
conf_y = ConfigYaml()


@ddt.ddt
class TestOrderFlow(unittest.TestCase):
    reader = ExcelReader(get_data_path() + os.sep + conf_y.get_file_name('order_flow'),
                         conf_y.get_sheet_name('order_flow'))
    cases = reader.read_data()

    @ddt.data(*cases)
    def test_order_flow(self, cases):
        new_cases = replace_case_with_re(cases)
        headers = None
        if new_cases['Headers'] is not None:
            headers = json.loads(new_cases['Headers'])
        url = conf_y.get_host() + str(new_cases['请求URL'])
        if new_cases['请求数据类型'] == 'params':
            params = json.loads(new_cases['请求数据'])
            response = PLRequests.pl_request(new_cases["请求方法"], url=url, params=params, headers=headers)
        elif new_cases['请求数据类型'] == 'data':
            data = json.loads(new_cases['请求数据'])
            response = PLRequests.pl_request(new_cases["请求方法"], url=url, data=data, headers=headers)
        elif new_cases['请求数据类型'] == 'json':
            json_str = new_cases['请求数据']
            response = PLRequests.pl_request(new_cases["请求方法"], url=url, data=json_str, headers=headers)
        else:
            response = PLRequests.pl_request(new_cases["请求方法"], url=url)
        pl_assert(new_cases["期望结果断言"], response.json())

        # 在有extract列的请求的响应结果中提取数据,并设置为全局变量
        if new_cases["extract"]:
            extract_data_from_response(new_cases["extract"], response.json())
            pl_log.info("从接口请求中获取全局变量提取表达式为:{}".format(new_cases["extract"]))

        if new_cases["期望结果断言"]:
            pl_assert(new_cases["期望结果断言"], response.json())

上面的代码对请求数据的判断处理,在其他用例中也会用到,可以单独封装起来,方便调用,并简化测试用例的代码。这里不再实现。由于后续的所有项目都会基于pytest,而非unittest,会在pytest实例中进行优化。

上面的代码执行后报告如下:


文章作者: 老百
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 老百 !
  目录