接口自动化

接口自动化的框架开发:

  • 用到的知识点:

    • pytest

    • allure

    • 参数化

    • Excel操作,不会,用xlrd

    • 日志操作,学过,不太会

    • 邮件,会

    • 文件操作,文件压缩, 没讲,但你要会的,zipfile

    • 执行终端命令,os.system, subprocess:cell, popen

      • 如何使用python查看当前目录下的所有文件或者目录?

  • 实现的个功能:

    • 将各个功能拆分为多个目录

    • 使用参数化读取Excel中的用例

      • 发请求

      • 获取请求结果

      • 校验/断言

    • 使用allure生成测试报告

    • 将allure测试报告所在的目录打包

    • 将打包的zip文件使用邮件发送到1206180814@qq.com

    • 在重点位置加日志

实现思路:

  1. 读取Excel,每一行数据都是一个用例,你在读出来之后,把这个一行用例封装成一个对象,字典,列表。

  2. 使用参数化每次讲一个用例对象传进去。

  3. 使用requests获取用例对象中的相关参数发请求。

  4. 然后将请求结果与预期值(用例对象)做断言

  5. 此时,allure所需的json数据已经有了。

  6. 使用allure命名读取josn数据生成测试报告

  7. 将报告压缩

  8. 使用发邮件功能将压缩文件发送

  9. 在重点位置,添加日志功能

 

 

接口自动化

本质:通过requests和Excel(数据库)对用例进行批量的自动化回归测试。

关于Excel

操作Excel:https://www.cnblogs.com/Neeo/articles/11650149.html

复制代码
import xlrd
from conf import settings
​
book = xlrd.open_workbook(settings.FILE_PATH)
sheet = book.sheet_by_index(0)
# book.sheet_by_name('通用接口')
# print(sheet.nrows, sheet.ncols)  # 获取所有行和列数
# 获取指定行
# print(sheet.row_values(0))
# print(sheet.row_values(1))
# 获取指定列
# print(sheet.col_values(0))
​
​
l = []
title = sheet.row_values(0)
print(title)
for row in range(1, sheet.nrows):
    l.append(dict(zip(title, sheet.row_values(row))))
​
print(l)
​
View Code
复制代码

 

单元测试框架

pytest 目录结构创建

 

 

操作excel表格

settings.py:关于Ecel配置

复制代码
import os
import datetime
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
​
# -------------------- 关于Ecel配置-------------
# data的excel的路径
FILE_NAME = "接口测试示例-2.xlsx"
FILE_PATH = os.path.join(BASE_DIR, 'data', FILE_NAME)
View Code
复制代码

 

utils/ExcelHandler.py:操作excel

复制代码
"""
处理Excel
"""import xlrd
from conf import settings
from utils.LogHandler import logger
​
​
class ExcelOperate(object):
​
    def __init__(self, file_path, sheet_by_index=0):
        self.file_path = file_path
        # excel操作拿到sheet的索引默认0
        self.sheet_by_index = sheet_by_index
        # 打开excel路径
        book = xlrd.open_workbook(self.file_path)
        self.sheet = book.sheet_by_index(self.sheet_by_index)
​
    def get_excel(self):
        """ 获取Excel数据 """
        # l = []
        # 取第一行的title
        title = self.sheet.row_values(0)
        # print(title)
        # 拿到所有的行和第一行的title对应上
        # for row in range(1, self.sheet.nrows):
               #  通过拉链处理excel数据一条数据是一个个字典放到列表里
        #     l.append(dict(zip(title, self.sheet.row_values(row))))
        # return l
        # print(1111, self.sheet.nrows)
        l = [dict(zip(title, self.sheet.row_values(row))) for row in range(1, self.sheet.nrows)]
        logger().info('读取Excel 成功,数据已返回')
        return l
​
if __name__ == '__main__':
    excel_data_list = ExcelOperate(settings.FILE_PATH, 2).get_excel()
    print(excel_data_list)
View Code
复制代码

test_case.py:

复制代码
import os
import shutil
import pytest
import allure
from deepdiff import DeepDiff
from utils.ExcelHandler import ExcelOperate
from utils.RequestsHandler import RequestsOperate
from utils.LogHandler import logger
from conf import settings
​
excel_data_list = ExcelOperate(settings.FILE_PATH, 3).get_excel()
​
​
​
class TestCase(object):
​
    @pytest.mark.parametrize('item', excel_data_list)
    def test_case(self, item):
        logger().info('正在进行断言.....')
        except_date, result = RequestsOperate(current_case=item, all_excel_data_list=excel_data_list).get_response_msg()
        # print(DeepDiff(except_date, result))
        allure.dynamic.title(item['title'])
​
        allure.dynamic.description(
            "<b style='color:red'>请求的url:</b>{}<hr />"
            "<b style='color:red'>预期值: </b>{}<hr />"
            "<b style='color:red'>实际执行结果: </b>{}<hr />".format(item['url'], item['except'], result)
        )
​
​
        assert not DeepDiff(except_date, result).get('values_changed', None)
​
        logger().info('完成断言,{}'.format(except_date, result))
​
View Code
复制代码

 

 

 

如何处理数据依赖

依赖:当条测试用例要完成的话需要上面的一条测试用例为基础(比如cookie 依赖于上面测试成的cookie)

在Excel中编写测试用例的时候:(token依赖于第一条测试用例data的数据)

  1. 被依赖的接口用例写在上面

  2. 对于依赖的字段设置规则:(case_001>request_data>token 依赖于case_001用例的request_data字段的token的键)

    • {"user":"admin", "token":"xxxxadsasdads"}

    • {"token":"${case_001>request_data>token}$"}

有依赖数据处理:

start.py:

复制代码
import os
import shutil
import pytest
from conf.settings import BASE_DIR
from utils.AllureHandler import AllureOperate
​
if __name__ == '__main__':
​
    # 更改工作目录
    os.chdir(BASE_DIR)
    # 执行用例
    pytest.main()
​
​
View Code
复制代码

 

test_case.py:

复制代码
import os
import shutil
import pytest
import allure
from deepdiff import DeepDiff
from utils.ExcelHandler import ExcelOperate
from utils.RequestsHandler import RequestsOperate
from utils.LogHandler import logger
from conf import settings
​
# 3是excel的sheet的索引
excel_data_list = ExcelOperate(settings.FILE_PATH, 3).get_excel()
​
​
​
class TestCase(object):
​
    @pytest.mark.parametrize('item', excel_data_list)
    def test_case(self, item):
        logger().info('正在进行断言.....')
        except_date, result = RequestsOperate(current_case=item, all_excel_data_list=excel_data_list).get_response_msg()
        # print(DeepDiff(except_date, result))
View Code
复制代码

 

RequestsHandler.py:

复制代码
import re
import json
import requests
from jsonpath_rw import parse
from urllib.parse import urlparse
from conf import settings
from utils.LogHandler import logger
​
​
# ---------------  有数cookies依赖的写法-2 ---------------------
​
​
class RequestsOperate(object):
​
    def __init__(self, current_case, all_excel_data_list):
        self.current_case = current_case
        self.all_excel_data_list = all_excel_data_list
​
    def get_response_msg(self):
        """ 发送请求并且获取结果 """
        return self._send_msg()
​
    def _send_msg(self):
        """ 发请求 """
        # print(1111111111, self.current_case)
        logger().info('正在向 {} 发送请求,{}'.format(self.current_case['url'], self.current_case))
        response = requests.request(
            method=self.current_case['method'],
            url=self.current_case['url'],
            data=self._check__request_data(),
            params=self._check__request_params(),
            cookies=self._check_request_cookies(),
            headers=self._check_request_headers(),
        )
        # print("------------- {} 请求结果 ----------------".format(self.current_case['case_num']), response.json())
        self._write_cookies(response)
​
        # print(111111, "预期值", self.current_case['except'], '实际执行结果', response.json())
        return json.loads(self.current_case['except']), response.json()
​
    def _check_request_headers(self):
        """ 校验请求头 做携带cookis 和 数据依赖的问题 """
        headers = self.current_case.get("headers", None)
        if headers:
            return self._operate_re_msg(headers)
​
        else:
            return {}
​
    def _write_cookies(self, response):
        """ 监测响应结果中是否含有cookies,有就保存起来 """
        cookies = response.cookies.get_dict()
        # print(self.current_case)
        for item in self.all_excel_data_list:
            if item['case_num'] == self.current_case['case_num']:
                # print(111111111, item)
                # 新加一个key再item中
                item['temporary_response_cookies'] = response.cookies.get_dict()
                if response.headers['Content-Type'].lower() == 'application/json;charset=utf-8':
                    item['temporary_response_json'] = response.json()
                item['temporary_request_headers'] = self.current_case['headers']
                item['temporary_request_data'] = self.current_case['data']
                item['temporary_request_json'] = self.current_case['json']
                item['temporary_request_params'] = self.current_case['params']
                item['temporary_response_headers'] = response.headers
​
        # print(self.all_excel_data_list)
def _check__request_data(self):
        """ 处理 请求的 data 参数,检查是否有依赖 """
        data = self.current_case['data']
        if data:
            # print(data, type(data))
            return json.loads(data)
        else:
            return {}
​
    def _check__request_params(self):
        """ 处理 请求的 params 参数,检查是否有依赖 """
        params = self.current_case['params']
        if params:
            return json.loads(params)
        else:
            return {}
​
    def _check_request_cookies(self):
        """ 处理请求中的 cookies """
        cookies_case_num = self.current_case.get("cookies", None)
        # print(1111111111, cookies_case_num)
        if cookies_case_num:  # 当前接口需要cookies
            # print(2222222222, cookies_case_num)
            for item in self.all_excel_data_list:
                if item['case_num'] == cookies_case_num:
                    return item.get('temporary_response_cookies', {})
​
        else:
            return {}
​
    def _operate_re_msg(self, parameter):
        """
        正则校验,数据依赖的字段
        :param parameter: 各种参数,如: data,headers, params
        :return:
        """
        # print(22222222,parameter)
        # 使用 re 提取依赖字段  {"testfan-token": "${neeo_001>response_json>data}$"}
        if isinstance(parameter, dict):
            parameter = json.dumps(parameter)
        # re只能提取字符串 \${(.*?)}\$  匹配:${neeo_001>response_json>data}$"}
        pattern = re.compile('\${(.*?)}\$')
        # 找到匹配好的数据据
        rule_list = pattern.findall(parameter)
        # print(333333333, parameter)
        # ["neeo_001>response_json>data"]
        if rule_list:  # 该参数有数据依赖要处理
            for rule in rule_list:
                case_num, params, json_path = rule.split(">")
                # print(333333333, case_num, params, json_path)
                # neeo_001 response_json data.info.phone
                for line in self.all_excel_data_list:
                    if line['case_num'] == case_num:
                        # print(11111111111, self.current_case, '\n', params, '\n', line)
                        temp_data = line["temporary_{}".format(params)]
                        # print(temp_data, type(temp_data))
                        # 判断temp_data是不是字符串
                        if isinstance(temp_data, str):
                            temp_data = json.loads(temp_data)
                        match_list = parse(json_path).find(temp_data)
                        # print(333333333, match_list)
                        # 如果match_list不为空
                        if match_list:
                            # 提取出用例1的data
                            match_data = [v.value for v in match_list][0]
                        # print(match_data)
                        # 将提取出来的值替换到原来规则 24fcbcc6dc004f3eb9ef13f59160ca7c  --> {"testfan-token": "${neeo_001>response_json>data}$"}
                        parameter = re.sub(pattern=pattern, repl=match_data, string=parameter, count=1)
            return json.loads(parameter)
​
        else:  # 该参数有数据 但没有数据依赖要处理 正常返回参数就行
            if isinstance(parameter, str):
                parameter = json.loads(parameter)
            return parameter
​
View Code
复制代码

 re.sub():匹配替换为选择的文本

从源码中看出re.sub()函数共有5个参数:

  pattern:表示正则中的模式字符串;
  repl:表示要替换的字符串(即匹配到pattern后替换为repl),也可以是个函数;
  string:表示要被处理(查找替换)的原始字符串;
  count:可选参数,表示要替换的最大次数,而且必须是非负整数,该参数默认为0,即所有的匹配都会替换;
  flags:可选参数,表示编译时用的匹配模式(如忽略大小写、多行模式等),数字形式,默认为0。

没有依赖处理:

test_case.py:

复制代码
import os
import shutil
import pytest
import allure
from deepdiff import DeepDiff
from utils.ExcelHandler import ExcelOperate
from utils.RequestsHandler import RequestsOperate
from utils.LogHandler import logger
from conf import settings
​
excel_data_list = ExcelOperate(settings.FILE_PATH, 3).get_excel()
​
​
​
​
class TestCase(object):
​
    # @classmethod
    # def setup_class(cls):
    #     cls.excel_data_list = ExcelOperate(settings.FILE_PATH, 0).get_excel()
​
    @pytest.mark.parametrize('item', excel_data_list)
    def test_case(self, item):
        logger().info('正在进行断言.....')
        # 请求数据 读取excel
        except_date, result = RequestsOperate(current_case=item, all_excel_data_list=excel_data_list).get_response_msg()
        # print(DeepDiff(except_date, result))
        allure.dynamic.title(item['title'])
​
        allure.dynamic.description(
            "<b style='color:red'>请求的url:</b>{}<hr />"
            "<b style='color:red'>预期值: </b>{}<hr />"
            "<b style='color:red'>实际执行结果: </b>{}<hr />".format(item['url'], item['except'], result)
        )
​
​
        assert not DeepDiff(except_date, result).get('values_changed', None)
​
        logger().info('完成断言,{}'.format(except_date, result))
​
View Code
复制代码

 

 

RequestsHandler.py:

复制代码
# --------------- 没有依赖的写法 -------------------------------
# class RequestsOperate(object):
#
#     def __init__(self, current_case):
          # 当前的case
#         self.current_case = current_case
#
#     def get_response_msg(self):
#         """ 发送请求并且获取结果 """
#         self._send_msg()
#
#
#     def _send_msg(self):
#         """ 发请求 """
#         response = requests.request(
#             method=self.current_case['method'],
#             url=self.current_case['url'],
#             data=self._check__request_data(),
#             params=self._check__request_params(),
#         )
#         print("------------- 请求结果 ----------------", response.json(), '\n', "------------- 请求结果 ----------------")
#
#     def _check__request_data(self):
#         """ 处理 请求的 data 参数,检查是否有依赖 """
#         data = self.current_case['data']
#         if data:
#             # print(data, type(data))
#             return json.loads(data)
#         else:
#             return {}
#
#     def _check__request_params(self):
#         """ 处理 请求的 params 参数,检查是否有依赖 """
#         params = self.current_case['params']
#         if params:
#             return json.loads(params)
#         else:
#             return {}
​
​
if __name__ == '__main__':
    from utils.ExcelHandler import ExcelOperate
    from conf import settings
    # 获取excel
    excel_data_list = ExcelOperate(settings.FILE_PATH, sheet_by_index=3).get_excel()
    # 读每条测试用例
    for item in excel_data_list:
        RequestsOperate(current_case=item, all_excel_data_list=excel_data_list).get_response_msg()
        # print(item)
View Code
复制代码

 

数据依赖有cookies的处理

关于cookies处理

第一种

借鉴postman的cookies管理器思路,即每个请求都监测响应结果是否有cookies返回,如果有,就保存,以域名的形式保存该cookies

如果有同域名的请求,就自动在headers中携带该cookies

settings.py:

复制代码
import os
import datetime
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# -------------------- 关于Ecel配置-------------

FILE_NAME = "接口测试示例-2.xlsx"
FILE_PATH = os.path.join(BASE_DIR, 'data', FILE_NAME)

# --------------------- template cookies dict ---------------
COOKIES_DICT = {}
View Code
复制代码

 

RequestsHandler:

复制代码
# ---------------  有数cookies依赖的写法-1 ---------------------


# class RequestsOperate(object):
#
#     def __init__(self, current_case):
#         self.current_case = current_case
#
#     def get_response_msg(self):
#         """ 发送请求并且获取结果 """
#         self._send_msg()
#
#     def _send_msg(self):
#         """ 发请求 """
#         response = requests.request(
#             method=self.current_case['method'],
#             url=self.current_case['url'],
#             data=self._check__request_data(),
#             params=self._check__request_params(),
#             cookies=self._check_request_cookies(),
#         )
#         print("------------- 请求结果 ----------------", response.json(), response.cookies.get_dict(), response.headers, '\n', "------------- 请求结果 ----------------")
#         self._write_cookies(response)
#
#
#     def _write_cookies(self,response):
#         """ 监测响应结果中是否含有cookies,有就保存起来 """
#         cookies = response.cookies.get_dict()
#         if cookies:
              #  拿到域名
#             netloc = urlparse(url=response.url).netloc
#             settings.COOKIES_DICT[netloc] = cookies
#             # print(2222222222, settings.COOKIES_DICT)
#         else:
#             pass
#
#     def _check__request_data(self):
#         """ 处理 请求的 data 参数,检查是否有依赖 """
#         data = self.current_case['data']
#         if data:
#             # print(data, type(data))
#             return json.loads(data)
#         else:
#             return {}
#
#     def _check__request_params(self):
#         """ 处理 请求的 params 参数,检查是否有依赖 """
#         params = self.current_case['params']
#         if params:
#             return json.loads(params)
#         else:
#             return {}
#
#     def _check_request_cookies(self):
#         """ 处理请求中的 cookies (依赖)"""
#         if self.current_case['cookies']:
#             # 获取当前的url的域名
#             netloc = urlparse(url=self.current_case['url']).netloc
#             # 根据域名从配置文件中取该域名下的cookies
#             cookies = settings.COOKIES_DICT.get(netloc, None)
#             if cookies:  # 如果有cookies要携带
#                 return cookies
#             else:
#                 return {}
#         else:
#             return {}
View Code
复制代码

 urlparse()解析url的方法十分简便,在写爬虫时非常有用,值得学习。

python3中urlparse模块和urllib模块合并,urlparse()在urllib.parse中进行调用。
urlparse()把url拆分为6个部分,scheme(协议),netloc(域名),path(路径),params(可选参数),query(连接键值对),fragment(特殊锚),并且以元组形式返回。
urlparse()具体怎么分析url呢,看看下面这个例子你就能明白了。

from urllib.parse import urlparse
url = 'https://github.com/search?q=krislp'
parse = urlparse(url)
print(parse)

输出结果:

ParseResult(scheme='https', netloc='github.com', path='/search', params='', query='q=krislp', fragment='')

 

 

第二种

我们在请求中,判断响应结果中是否返回了cookies,如果返回了,就保存到当前的用例对象中,该对象就是一个大的字典。将cookies和值保存为一个key value 谁要用,就来拿该参数

复制代码
import re
import json
import requests
from jsonpath_rw import parse
from utils.LogHandler import logger


# ---------------  有数cookies依赖的写法-2 ---------------------


class RequestsOperate(object):

    def __init__(self, current_case, all_excel_data_list):
        self.current_case = current_case
        self.all_excel_data_list = all_excel_data_list

    def get_response_msg(self):
        """ 发送请求并且获取结果 """
        return self._send_msg()

    def _send_msg(self):
        """ 发请求 """
        # print(1111111111, self.current_case)
        logger().info('正在向 {} 发送请求,{}'.format(self.current_case['url'], self.current_case))
        response = requests.request(
            method=self.current_case['method'],
            url=self.current_case['url'],
            data=self._check__request_data(),
            params=self._check__request_params(),
            cookies=self._check_request_cookies(),
            headers=self._check_request_headers(),
        )
        # print("------------- {} 请求结果 ----------------".format(self.current_case['case_num']), response.json())
        self._write_cookies(response)

        # print(111111, "预期值", self.current_case['except'], '实际执行结果', response.json())
        return json.loads(self.current_case['except']), response.json()

    def _check_request_headers(self):
        """ 校验请求头 做携带cookis 和 数据依赖的问题 """
        headers = self.current_case.get("headers", None)
        if headers:
            return self._operate_re_msg(headers)

        else:
            return {}

    def _write_cookies(self, response):
        """ 监测响应结果中是否含有cookies,有就保存起来 """
        cookies = response.cookies.get_dict()
        # print(self.current_case)
        for item in self.all_excel_data_list:
            if item['case_num'] == self.current_case['case_num']:
                # print(111111111, item)
                # 新加一个key再item中
                item['temporary_response_cookies'] = response.cookies.get_dict()
                if response.headers['Content-Type'].lower() == 'application/json;charset=utf-8':
                    item['temporary_response_json'] = response.json()
                item['temporary_request_headers'] = self.current_case['headers']
                item['temporary_request_data'] = self.current_case['data']
                item['temporary_request_json'] = self.current_case['json']
                item['temporary_request_params'] = self.current_case['params']
                item['temporary_response_headers'] = response.headers

        # print(self.all_excel_data_list)


    def _check_request_cookies(self):
        """ 处理请求中的 cookies 是否有数据依赖"""
        cookies_case_num = self.current_case.get("cookies", None)
        # print(1111111111, cookies_case_num)
        if cookies_case_num:  # 当前接口需要cookies
            # print(2222222222, cookies_case_num)
            for item in self.all_excel_data_list:
                if item['case_num'] == cookies_case_num:
                    return item.get('temporary_response_cookies', {})

        else:
            return {}

   
View Code
复制代码

 

 

Deepdiff模块:

https://www.cnblogs.com/Neeo/articles/12785493.html

DeepDiff模块常用来校验两个对象是否一致,并找出其中差异之处,它提供了:

  • DeepDiff:字典,可迭代项,字符串和其他对象的深层差异。它将递归地查找所有更改。

  • DeepSearch:在其他对象中搜索对象。

  • DeepHash:根据对象的内容对其进行哈希处理。


复制代码
from deepdiff import DeepDiff
​
​
​
data = {'code': '1', 'message': 'success'}
response_json = {'code': '0', 'data': 'c03d4a1ef6d34ccdbebbe1b6fd72ca31'}
​
​
d1 = {"a": 1, "b": 2, "c": 3, 'e': {"a": 2}}
d2 = {"a": 1, "b": 2, "c": 3, "d": 4, 'e': {"a": 1}}
​
​
# print(DeepDiff(d1, d2))
print(DeepDiff(data, response_json))
​
View Code
复制代码

 

 
结果:{'dictionary_item_added': [root['data']], 'dictionary_item_removed': [root['message']], 'values_changed': {"root['code']": {'new_value': '0', 'old_value': '1'}}}

断言处理test_case.py:


复制代码
import os
import shutil
import pytest
from deepdiff import DeepDiff
from utils.ExcelHandler import ExcelOperate
from utils.RequestsHandler import RequestsOperate
from utils.LogHandler import logger
from conf import settings
​
excel_data_list = ExcelOperate(settings.FILE_PATH, 3).get_excel()
​
​
class TestCase(object):
​
    # @classmethod
    # def setup_class(cls):
    #     cls.excel_data_list = ExcelOperate(settings.FILE_PATH, 0).get_excel()
​
    @pytest.mark.parametrize('item', excel_data_list)
    def test_case(self, item):
        logger().info('正在进行断言.....')
        
        except_date, result = RequestsOperate(current_case=item, all_excel_data_list=excel_data_list).get_response_msg()
        # print(DeepDiff(except_date, result))
        # 断言处理
        # get 如果能取到values_changed就返回,娶不到就返回none
        assert not DeepDiff(except_date, result).get('values_changed', None)
        
        logger().info('完成断言,{}'.format(except_date, result))
​
View Code
复制代码

 

 

alluredir

python库:https://pypi.org/

下载alluredir库:

pip install -i https://pypi.doubanio.com/simple allure-pytest

 

pytest.ini:

复制代码
[pytest]
addopts = -s -v  --alluredir ./report/json_result
testpaths = ./scripts
python_files = test_*.py
python_classes = Test*
python_functions = test_*
View Code
复制代码

 

生成报告之前删除之前的json文件(在用例执行前)start.py:

复制代码
import os
import shutil
import pytest
from conf.settings import BASE_DIR
from utils.AllureHandler import AllureOperate
​
if __name__ == '__main__':
    # 删除之前的json文件(在用例执行前)
    dir_path = os.path.join(BASE_DIR, 'report', 'json_result')
    shutil.rmtree(dir_path)
    # 更改工作目录
    os.chdir(BASE_DIR)
    # 执行用例
    pytest.main()
 
View Code
复制代码

 

关于urllib

取域名用

参考:https://www.cnblogs.com/Neeo/articles/11520952.html

 

如何删除一个非空目录

用shuitil

复制代码
import shutil
'''
shutil模块对文件和文件集合提供了许多高级操作。特别是,提供了支持文件复制和删除的功能
'''# shutil.copy(src, dst)   # 拷贝文件
# shutil.move(src, dst)  # 移动目录或者文件
# shutil.rmtree(path)   # 递归删除目录,无法直接删除文件
# shutil.make_archive(base_name, format('zip'))   # 将目录或者文件以指定格式压缩
# shutil.unpack_archive(filename, extract_dir)  # 解压缩
# see also: https://docs.python.org/3/library/shutil.html
View Code
复制代码

 


关于生成测试报告的命令

subprocess --- 子进程管理

参考:https://docs.python.org/zh-cn/3/library/subprocess.html

settings.py:


复制代码
import os
import datetime
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
​
# -------------------- 关于Ecel配置-------------
​
FILE_NAME = "接口测试示例-2.xlsx"
FILE_PATH = os.path.join(BASE_DIR, 'data', FILE_NAME)
​
# --------------------- template cookies dict ---------------
COOKIES_DICT = {}
​
​
​
# -------------------- allure 报告相关 ------------
​
​
​
ALLURE_COMMAND = 'allure generate {from_json_path} -o {save_to_path} --clean'.format(
    from_json_path=os.path.join(BASE_DIR, 'report', 'json_result'),
    save_to_path=os.path.join(BASE_DIR, 'report', "allure_result")
)
​
​
View Code
复制代码

 

 

utils/AllureHandler.py:

复制代码
from utils.LogHandler import logger
​
from conf import settings
​
​
class AllureOperate(object):
​
    def get_allure_report(self):
        """ 生成报告 """
​
        logger().info('正在生成测试报告.....')
        # 字符串命令ALLURE_COMMAND,此命令要像字符串shell一样执行 shell=True
        subprocess.call(settings.ALLURE_COMMAND, shell=True)
        logger().info('生成测试报告成功.....')
        '''
            ["allure", "generate", ]
        '''
View Code
复制代码

 


start.py:


复制代码
import os
import shutil
import pytest
from conf.settings import BASE_DIR
from utils.AllureHandler import AllureOperate
​
if __name__ == '__main__':
    # 删除之前的json文件(再用例执行前)
    dir_path = os.path.join(BASE_DIR, 'report', 'json_result')
    shutil.rmtree(dir_path)
    # 更改工作目录
    os.chdir(BASE_DIR)
    # 执行用例
    pytest.main()
    # 生成allure报告
    allure_obj = AllureOperate()
    allure_obj.get_allure_report()
​
 
View Code
复制代码

 

 

关于打包

zipfile

参考:https://www.cnblogs.com/Neeo/articles/11934072.html

AllureHandler.py:

复制代码
"""
​
allure报告相关
"""import os
import subprocess
import zipfile
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header
from utils.LogHandler import logger
​
from conf import settings
​
​
class AllureOperate(object):
​
    def get_allure_report(self):
        """ 生成报告 """# os.system(ALLURE_COMMAND)
        # subprocess.Popen(ALLURE_COMMAND, shell=True)
        logger().info('正在生成测试报告.....')
        subprocess.call(settings.ALLURE_COMMAND, shell=True)
        logger().info('生成测试报告成功.....')
        '''
            ["allure", "generate", ]
        '''def check_zip(self):
        """ 打包 """
        try:
            logger().info('正在打包测试报告.....')
            # BASE_DIR:当前脚本的父级目录
            BASE_DIR = os.path.join(settings.BASE_DIR, 'report')
            start_zip_dir = os.path.join(BASE_DIR, 'allure_result')  # 要压缩文件夹的根路径
            zip_file_name = 'allure_report.zip'  # 为压缩后的文件起个名字
            # 下面是固定写法
            zip_file_path = os.path.join(BASE_DIR, zip_file_name)
            f = zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED)
            for dir_path, dir_name, file_names in os.walk(start_zip_dir):
                # 要是不replace,就从根目录开始复制
                file_path = dir_path.replace(start_zip_dir, '')
                # 实现当前文件夹以及包含的所有文件
                file_path = file_path and file_path + os.sep or ''
                for file_name in file_names:
                    f.write(os.path.join(dir_path, file_name), file_path + file_name)
            f.close()
            logger().info('打包测试报告完成.....')
        except Exception as e:
            logger().error('打包测试报告失败: {}'.format(e))
​
​
if __name__ == '__main__':
    AllureOperate().get_allure_report()
​
View Code
复制代码

 

start.py:

复制代码
import os
import shutil
import pytest
from conf.settings import BASE_DIR
from utils.AllureHandler import AllureOperate
​
if __name__ == '__main__':
    # 删除之前的json文件(再用例执行前)
    dir_path = os.path.join(BASE_DIR, 'report', 'json_result')
    shutil.rmtree(dir_path)
    # 更改工作目录
    os.chdir(BASE_DIR)
    # 执行用例
    pytest.main()
    # 生成allure报告
    allure_obj = AllureOperate()
    allure_obj.get_allure_report()
    # 压缩文件
    allure_obj.check_zip()
​
View Code
复制代码

 

发邮件

AllureHandler.py:

复制代码
"""
​
allure报告相关
"""import os
import subprocess
import zipfile
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header
from utils.LogHandler import logger
​
from conf import settings
​
​
class AllureOperate(object):
​
    def get_allure_report(self):
        """ 生成报告 """# os.system(ALLURE_COMMAND)
        # subprocess.Popen(ALLURE_COMMAND, shell=True)
        logger().info('正在生成测试报告.....')
        subprocess.call(settings.ALLURE_COMMAND, shell=True)
        logger().info('生成测试报告成功.....')
        '''
            ["allure", "generate", ]
        '''def check_zip(self):
        """ 打包 """
        try:
            logger().info('正在打包测试报告.....')
            # BASE_DIR:当前脚本的父级目录
            BASE_DIR = os.path.join(settings.BASE_DIR, 'report')
            start_zip_dir = os.path.join(BASE_DIR, 'allure_result')  # 要压缩文件夹的根路径
            zip_file_name = 'allure_report.zip'  # 为压缩后的文件起个名字
            # 下面是固定写法
            zip_file_path = os.path.join(BASE_DIR, zip_file_name)
            f = zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED)
            for dir_path, dir_name, file_names in os.walk(start_zip_dir):
                # 要是不replace,就从根目录开始复制
                file_path = dir_path.replace(start_zip_dir, '')
                # 实现当前文件夹以及包含的所有文件
                file_path = file_path and file_path + os.sep or ''
                for file_name in file_names:
                    f.write(os.path.join(dir_path, file_name), file_path + file_name)
            f.close()
            logger().info('打包测试报告完成.....')
        except Exception as e:
            logger().error('打包测试报告失败: {}'.format(e))
​
    def send_mail(self):
​
        # 第三方 SMTP 服务
        mail_host = settings.MAIL_HOST  # 设置服务器   # 勿动
        mail_user = settings.MAIL_USER  # 用户名
        mail_pass = settings.MAIL_TOKEN  # 口令
        # 设置收件人和发件人
        sender = settings.SENDER
        receivers = settings.RECEIVERS
​
        # 创建一个带附件的实例对象
        message = MIMEMultipart()
​
        # 邮件主题、收件人、发件人
        subject = settings.THEME  # 邮件主题
        message['Subject'] = Header(subject, 'utf-8')
        message['From'] = Header("{}".format(sender), 'utf-8')  # 发件人
        message['To'] = Header("{}".format(';'.join(receivers)), 'utf-8')  # 收件人
        # 邮件正文内容
        send_content = settings.SEND_CONTENT
        content_obj = MIMEText(send_content, 'plain', 'utf-8')  # 第一个参数为邮件内容
        message.attach(content_obj)
​
        # 构造附件
        att = MIMEText(_text=self._get_zip_file(), _subtype='base64', _charset='utf-8')
        att["Content-Type"] = 'application/octet-stream'
        att["Content-Disposition"] = 'attachment; filename="{}"'.format(settings.SEND_FILE_NAME
                                                                        )  # # filename 为邮件附件中显示什么名字
        message.attach(att)
​
        try:
            smtp_obj = smtplib.SMTP()
            smtp_obj.connect(mail_host, 25)  # 25 为 SMTP 端口号
            smtp_obj.login(mail_user, mail_pass)
            smtp_obj.sendmail(sender, receivers, message.as_string())
            smtp_obj.quit()
            logger().info('邮件发送成功')
​
        except smtplib.SMTPException as e:
            logger().error('email send error: {}'.format(e))
​
    def _get_zip_file(self):
        """ 获取zip文件内容 """
        with open(file=os.path.join(settings.BASE_DIR, 'report', 'allure_report.zip'), mode='rb') as f:
            return f.read()
​
​
if __name__ == '__main__':
    AllureOperate().get_allure_report()
​
View Code
复制代码

 

settings.py:


复制代码
import os
import datetime
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
​
# -------------------- 关于Ecel配置-------------
​
FILE_NAME = "接口测试示例-2.xlsx"
FILE_PATH = os.path.join(BASE_DIR, 'data', FILE_NAME)
​
# --------------------- template cookies dict ---------------
COOKIES_DICT = {}
​
​
​
# -------------------- allure 报告相关 ------------
​
​
​
ALLURE_COMMAND = 'allure generate {from_json_path} -o {save_to_path} --clean'.format(
    from_json_path=os.path.join(BASE_DIR, 'report', 'json_result'),
    save_to_path=os.path.join(BASE_DIR, 'report', "allure_result")
)
​
# ------------------------ 邮件相关 -------------
​
MAIL_HOST = "smtp.qq.com"  # 设置服务器   # 勿动
MAIL_USER = "你的QQ邮箱@qq.com"  # 用户名
MAIL_TOKEN = "你的授权码"  # 授权码
# 设置收件人和发件人
SENDER = 'xxxx@qq.com'
RECEIVERS = ['xxxxx@qq.com', 'xxxxxx@163.com']  # 接收邮件,可设置为你的QQ邮箱或者其他邮箱
# 邮件主题
THEME = '请查阅--s28的第一个测试报告'
# 正文内容
SEND_CONTENT = 'hi man,你收到附件了吗?'# 附件的file name
​
SEND_FILE_NAME = 'allure_report.zip'
View Code
复制代码

 



start.py:

复制代码
import os
import shutil
import pytest
from conf.settings import BASE_DIR
from utils.AllureHandler import AllureOperate
​
if __name__ == '__main__':
    # 删除之前的json文件(再用例执行前)
    dir_path = os.path.join(BASE_DIR, 'report', 'json_result')
    shutil.rmtree(dir_path)
    # 更改工作目录
    os.chdir(BASE_DIR)
    # 执行用例
    pytest.main()
    # 生成allure报告
    allure_obj = AllureOperate()
    allure_obj.get_allure_report()
    # 压缩文件
    allure_obj.check_zip()
    # 发邮件
    allure_obj.send_mail()
​
 
View Code
复制代码

 



日志

参考:https://www.cnblogs.com/Neeo/articles/10951734.html#%E7%A4%BA%E4%BE%8B

LogHandler.py:

复制代码
import logging
from conf import settings
​
​
class LoggerHandler:
    """ 日志操作 """
    _logger_level = {
        'debug': logging.DEBUG,
        'info': logging.INFO,
        'warning': logging.WARNING,
        'error': logging.ERROR,
        'critical': logging.CRITICAL
    }
​
    def __init__(self, log_name, file_name, logger_level, stream_level='info', file_level='warning'):
        self.log_name = log_name
        self.file_name = file_name
        self.logger_level = self._logger_level.get(logger_level, 'debug')
        self.stream_level = self._logger_level.get(stream_level, 'info')
        self.file_level = self._logger_level.get(file_level, 'warning')
        # 创建日志对象
        self.logger = logging.getLogger(self.log_name)
        # 设置日志级别
        self.logger.setLevel(self.logger_level)
​
        if not self.logger.handlers:
            # 设置日志输出流
            f_stream = logging.StreamHandler()
            f_file = logging.FileHandler(self.file_name)
            # 设置输出流级别
            f_stream.setLevel(self.stream_level)
            f_file.setLevel(self.file_level)
            # 设置日志输出格式
            formatter = logging.Formatter(
                "%(asctime)s %(name)s %(levelname)s %(message)s"
            )
            f_stream.setFormatter(formatter)
            f_file.setFormatter(formatter)
            self.logger.addHandler(f_stream)
            self.logger.addHandler(f_file)
        # print(11111111111, self.logger.handlers)
    @property
    def get_logger(self):
        return self.logger
​
​
def logger(log_name='接口测试'):
    return LoggerHandler(
        log_name=log_name,
        logger_level=settings.LOG_LEVEL,
        file_name=settings.LOG_FILE_NAME,
        stream_level=settings.LOG_STREAM_LEVEL,
        file_level=settings.LOG_FILE_LEVEL
    ).get_logger
​
​
if __name__ == '__main__':
    logger().debug('aaaa')
    logger().info('aaaa')
    logger().warning('aaaa')
    logger().error('bbbbbbbb')
View Code
复制代码

 

settings.py

复制代码
import os
import datetime
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
​
# -------------------- 关于Ecel配置-------------
​
FILE_NAME = "接口测试示例-2.xlsx"
FILE_PATH = os.path.join(BASE_DIR, 'data', FILE_NAME)
​
# --------------------- template cookies dict ---------------
COOKIES_DICT = {}
​
​
​
# -------------------- allure 报告相关 ------------
​
​
​
ALLURE_COMMAND = 'allure generate {from_json_path} -o {save_to_path} --clean'.format(
    from_json_path=os.path.join(BASE_DIR, 'report', 'json_result'),
    save_to_path=os.path.join(BASE_DIR, 'report', "allure_result")
)
​
# ------------------------ 邮件相关 -------------
​
MAIL_HOST = "smtp.qq.com"  # 设置服务器   # 勿动
MAIL_USER = "你的QQ邮箱@qq.com"  # 用户名
MAIL_TOKEN = "你的授权码"  # 授权码
# 设置收件人和发件人
SENDER = 'xxxx@qq.com'
RECEIVERS = ['xxxxx@qq.com', 'xxxxxx@163.com']  # 接收邮件,可设置为你的QQ邮箱或者其他邮箱
# 邮件主题
THEME = '请查阅--s28的第一个测试报告'
# 正文内容
SEND_CONTENT = 'hi man,你收到附件了吗?'# 附件的file name
​
SEND_FILE_NAME = 'allure_report.zip'# ---------------- 日志相关 --------------------
# 日志级别
LOG_LEVEL = 'debug'
LOG_STREAM_LEVEL = 'debug'  # 屏幕输出流
LOG_FILE_LEVEL = 'info'   # 文件输出流
# 日志文件命名
​
LOG_FILE_NAME = os.path.join(BASE_DIR, 'logs', datetime.datetime.now().strftime('%Y-%m-%d') + '.log')
​
if __name__ == '__main__':
    print(LOG_FILE_NAME)
​
​
View Code
复制代码

 

 

使用:

复制代码
"""
​
allure报告相关
"""import os
import subprocess
import zipfile
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header
from utils.LogHandler import logger
​
from conf import settings
​
​
class AllureOperate(object):
​
    def get_allure_report(self):
        """ 生成报告 """# os.system(ALLURE_COMMAND)
        # subprocess.Popen(ALLURE_COMMAND, shell=True)
        logger().info('正在生成测试报告.....')
        subprocess.call(settings.ALLURE_COMMAND, shell=True)
        logger().info('生成测试报告成功.....')
        '''
            ["allure", "generate", ]
        '''def check_zip(self):
        """ 打包 """
        try:
            logger().info('正在打包测试报告.....')
            # BASE_DIR:当前脚本的父级目录
            BASE_DIR = os.path.join(settings.BASE_DIR, 'report')
            start_zip_dir = os.path.join(BASE_DIR, 'allure_result')  # 要压缩文件夹的根路径
            zip_file_name = 'allure_report.zip'  # 为压缩后的文件起个名字
            # 下面是固定写法
            zip_file_path = os.path.join(BASE_DIR, zip_file_name)
            f = zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED)
            for dir_path, dir_name, file_names in os.walk(start_zip_dir):
                # 要是不replace,就从根目录开始复制
                file_path = dir_path.replace(start_zip_dir, '')
                # 实现当前文件夹以及包含的所有文件
                file_path = file_path and file_path + os.sep or ''
                for file_name in file_names:
                    f.write(os.path.join(dir_path, file_name), file_path + file_name)
            f.close()
            logger().info('打包测试报告完成.....')
        except Exception as e:
            logger().error('打包测试报告失败: {}'.format(e))
​
    def send_mail(self):
​
        # 第三方 SMTP 服务
        mail_host = settings.MAIL_HOST  # 设置服务器   # 勿动
        mail_user = settings.MAIL_USER  # 用户名
        mail_pass = settings.MAIL_TOKEN  # 口令
        # 设置收件人和发件人
        sender = settings.SENDER
        receivers = settings.RECEIVERS
​
        # 创建一个带附件的实例对象
        message = MIMEMultipart()
​
        # 邮件主题、收件人、发件人
        subject = settings.THEME  # 邮件主题
        message['Subject'] = Header(subject, 'utf-8')
        message['From'] = Header("{}".format(sender), 'utf-8')  # 发件人
        message['To'] = Header("{}".format(';'.join(receivers)), 'utf-8')  # 收件人
        # 邮件正文内容
        send_content = settings.SEND_CONTENT
        content_obj = MIMEText(send_content, 'plain', 'utf-8')  # 第一个参数为邮件内容
        message.attach(content_obj)
​
        # 构造附件
        att = MIMEText(_text=self._get_zip_file(), _subtype='base64', _charset='utf-8')
        att["Content-Type"] = 'application/octet-stream'
        att["Content-Disposition"] = 'attachment; filename="{}"'.format(settings.SEND_FILE_NAME
                                                                        )  # # filename 为邮件附件中显示什么名字
        message.attach(att)
​
        try:
            smtp_obj = smtplib.SMTP()
            smtp_obj.connect(mail_host, 25)  # 25 为 SMTP 端口号
            smtp_obj.login(mail_user, mail_pass)
            smtp_obj.sendmail(sender, receivers, message.as_string())
            smtp_obj.quit()
            logger().info('邮件发送成功')
​
        except smtplib.SMTPException as e:
            logger().error('email send error: {}'.format(e))
​
    def _get_zip_file(self):
        """ 获取zip文件内容 """
        with open(file=os.path.join(settings.BASE_DIR, 'report', 'allure_report.zip'), mode='rb') as f:
            return f.read()
​
​
if __name__ == '__main__':
    AllureOperate().get_allure_report()
View Code
复制代码

 

allure生成测试报告的优化

test_case.py:

复制代码
import os
import shutil
import pytest
import allure
from deepdiff import DeepDiff
from utils.ExcelHandler import ExcelOperate
from utils.RequestsHandler import RequestsOperate
from utils.LogHandler import logger
from conf import settings
​
excel_data_list = ExcelOperate(settings.FILE_PATH, 3).get_excel()
​

​
class TestCase(object):
​
    # @classmethod
    # def setup_class(cls):
    #     cls.excel_data_list = ExcelOperate(settings.FILE_PATH, 0).get_excel()
​
    @pytest.mark.parametrize('item', excel_data_list)
    def test_case(self, item):
        logger().info('正在进行断言.....')
        except_date, result = RequestsOperate(current_case=item, all_excel_data_list=excel_data_list).get_response_msg()
        # print(DeepDiff(except_date, result))
        # 生成报告的优化
        allure.dynamic.title(item['title'])
​
        allure.dynamic.description(
            "<b style='color:red'>请求的url:</b>{}<hr />"
            "<b style='color:red'>预期值: </b>{}<hr />"
            "<b style='color:red'>实际执行结果: </b>{}<hr />".format(item['url'], item['except'], result)
        )
        # 断言处理
        # get 如果能取到values_changed就返回,娶不到就返回none
        assert not DeepDiff(except_date, result).get('values_changed', None)
​
        logger().info('完成断言,{}'.format(except_date, result))
View Code
复制代码

 

posted @   贰号猿  阅读(79)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示