Practical tutorial of python+excel interface automation testing framework (video explanation + source code)

Table of contents

Design flow chart

Excel and result preview

Framework

Excel related

log encapsulation

regular operation

core operation

test operation

Test report sending mail class

run


Design flow chart

This picture is some design ideas of my excel interface test framework.

First read the excel file to get the test information, and then use the unittest to test through the encapsulated requests method.

Among them, the parameters associated with the interface are searched and replaced by regular expressions. For this reason, I have specially opened up a global variable pool to manage various variables.

Finally, a test report is generated through HTMLrunner. If the execution fails, send a test report result email.

Excel and result preview

This is the test case organization chart of excel.

Video explanation: Mashang’s products must be high-quality products. Mr. Beifan will teach you the actual combat of python+excel interface automation testing, which is not to be missed_哔哩哔哩_bilibili icon-default.png?t=N3I4https://www.bilibili.com/video/BV1ZX4y1B7ci/?spm_id_from= 333.999.0.0

yH5BAAAAAAALAAAAAAOAA4AAAIMhI+py+0Po5y02qsKADs=

This is the HTML test report generated after running.

The excel report generated after running this time. You can see that I deliberately 预期正则set the wrong value in , and then when the use case failed, I also marked the failed expected value.

 

The email received after the test failed

 

Well, the above are some simple introductions, and we start to get to the point.

Framework

First of all, to develop such an excel interface automation test project, we must have a clear design idea, so that we will understand what we want to do in the process of developing the framework.

directory/file illustrate Is it a python package
common D yes
core Core classes, encapsulation, requestsetc. yes
data The excel file storage directory used for the test
logs log directory
tests Test case directory yes
utils Tools, such as: log yes
config.py configuration file
run.py executable file

Excel related

use case design

This time, the login interface is still used 智学网. The login interface and login verification interface in Zhixue.com are used, and there are dependent parameters between these two interfaces.

configuration file

Create it in the root directory of the project config.py, and put all the configuration information you can think of into this file for unified management.

 

#!/usr/bin/env python3
# coding=utf-8
import os


class CF:
    """配置文件"""
    # 项目目录
    BASE_DIR = os.path.abspath(os.path.dirname(__file__))

    # Excel首行配置
    NUMBER = 0
    NAME = 1
    METHOD = 2
    URL = 3
    ROUTE = 4
    HEADERS = 5
    PARAMETER = 6  # 参数
    EXPECTED_CODE = 7  # 预期响应码
    EXPECTED_REGULAR = 8  # 预期正则
    EXPECTED_VALUE = 9  # 预期结果值
    SPEND_TIME = 10  # 响应时间
    TEST_RESULTS = 11  # 测试结果
    EXTRACT_VARIABLE = 12  # 提取变量
    RESPONSE_TEXT = 13  # 响应文本
    # 字体大小
    FONT_SET = "微软雅黑"
    FONT_SIZE = 16
    # 颜色配置
    COLOR_PASSED = "90EE90"
    COLOR_FAILED = "FA8072"

    # 邮箱配置
    EMAIL_INFO = {
        'username': '[email protected]',
        'password': 2,
        'smtp_host': 'smtp.qq.com',
        'smtp_port': 465
    }
    # 收件人
    ADDRESSEE = ['[email protected]']


if __name__ == '__main__':
    print(CF.EXPECTED_CODE)


read/write excel

​ Create a new file in the common directory excelset.py. In this file, we need to implement, read the use cases in excel, write the test results and draw the corresponding colors, and it takes a long time to write the test.

#!/usr/bin/env python
# coding=utf-8
import shutil
import openpyxl
from config import CF
from openpyxl.styles import Font
from openpyxl.styles import PatternFill
from common.variables import VariablePool


class ExcelSet:
    """Excel配置"""

    def __init__(self):
        shutil.copyfile(VariablePool.get('excel_input'), VariablePool.get('excel_output'))
        self.path = VariablePool.get('excel_output')
        self.wb = openpyxl.load_workbook(self.path)
        self.table = self.wb.active

    def get_cases(self, min_row=2):
        """获取用例"""
        all_cases = []
        for row in self.table.iter_rows(min_row=min_row):
            all_cases.append((self.table.cell(min_row, CF.NAME + 1).value,
                              min_row, [cell.value for cell in row]))
            min_row += 1
        return all_cases

    def write_color(self, row_n, col_n, color=CF.COLOR_FAILED):
        """写入颜色"""
        cell = self.table.cell(row_n, col_n + 1)
        fill = PatternFill("solid", fgColor=color)
        cell.fill = fill

    def write_results(self, row_n, col_n, value, color=True):
        """写入结果"""
        cell = self.table.cell(row_n, col_n + 1)
        cell.value = value
        font = Font(name=CF.FONT_SET, size=CF.FONT_SIZE)
        cell.font = font
        if color:
            if value.lower() in ("fail", 'failed'):
                fill = PatternFill("solid", fgColor=CF.COLOR_FAILED)
                cell.fill = fill
            elif value.lower() in ("pass", "ok"):
                fill = PatternFill("solid", fgColor=CF.COLOR_PASSED)
                cell.fill = fill
        self.wb.save(self.path)


excel_set = ExcelSet()
if __name__ == '__main__':
    print(excel_set.get_cases())


log encapsulation

logger.py

The log is an essential thing in a project, and it can give feedback on problems in the first time.

#!/usr/bin/env python3
# coding=utf-8
import os
import logging
from config import CF
from datetime import datetime


class Logger:
    def __init__(self):
        self.logger = logging.getLogger()
        if not self.logger.handlers:
            self.logger.setLevel(logging.DEBUG)

            # 创建一个handler,用于写入日志文件
            fh = logging.FileHandler(self.log_path, encoding='utf-8')
            fh.setLevel(logging.DEBUG)

            # 创建一个handler,用于输出到控制台
            ch = logging.StreamHandler()
            ch.setLevel(logging.INFO)

            # 定义handler的输出格式
            formatter = logging.Formatter(self.fmt)
            fh.setFormatter(formatter)
            ch.setFormatter(formatter)

            # 给logger添加handler
            self.logger.addHandler(fh)
            self.logger.addHandler(ch)

    @property
    def log_path(self):
        logs_path = os.path.join(CF.BASE_DIR, 'logs')
        if not os.path.exists(logs_path):
            os.makedirs(logs_path)
        now_month = datetime.now().strftime("%Y%m")
        return os.path.join(logs_path, '{}.log'.format(now_month))

    @property
    def fmt(self):
        return '%(levelname)s %(asctime)s %(filename)s:%(lineno)d %(message)s'


log = Logger().logger
if __name__ == '__main__':
    log.info("你好")


regular operation

regular.py

It plays a decisive role in the extraction and passing of interface associated parameters.

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import re
from utils.logger import log
from common.variables import VariablePool
from core.serialize import is_json_str


class Regular:
    """正则类"""

    def __init__(self):
        self.reg = re.compile

    def finds(self, string):
        return self.reg(r'\{
   
   {(.*?)}\}').findall(string)

    def subs(self, keys, string):
        result = None
        log.info("提取变量:{}".format(keys))
        for i in keys:
            if VariablePool.has(i):
                log.info("替换变量:{}".format(i))
                comment = self.reg(r"\{
   
   {%s}}" % i)
                result = comment.sub(VariablePool.get(i), string)
        log.info("替换结果:{}".format(result))
        return result

    def find_res(self, exp, string):
        """在结果中查找"""
        if is_json_str(string):
            return self.reg(r'\"%s":"(.*?)"' % exp).findall(string)[0]
        else:
            return self.reg(r'%s' % exp).findall(string)[0]


if __name__ == '__main__':
    a = "{'data': {'loginName': 18291900215, 'password': '{
   
   {dd636482aca022}}', 'code': None, 'description': 'encrypt'}}"
    print(Regular().finds(a))

core operation

Video explanation: Mashang’s products must be high-quality products. Mr. Beifan will teach you the actual combat of python+excel interface automation testing, which is not to be missed_哔哩哔哩_bilibili icon-default.png?t=N3I4https://www.bilibili.com/video/BV1ZX4y1B7ci/?spm_id_from= 333.999.0.0

Define variable pool

variables.py

The global variable pool is here, is it very simple, but the effect is indeed huge.

#!/usr/bin/env python3
# -*- coding:utf-8 -*-


class VariablePool:
    """全局变量池"""

    @staticmethod
    def get(name):
        """获取变量"""
        return getattr(VariablePool, name)

    @staticmethod
    def set(name, value):
        """设置变量"""
        setattr(VariablePool, name, value)

    @staticmethod
    def has(name):
        return hasattr(VariablePool, name)


if __name__ == '__main__':
    VariablePool.set('name', 'wxhou')
    print(VariablePool.get('name'))

Package requests

request.py

The most core part is the secondary encapsulation of the python requests library. It is used to realize the request of the interface and obtain the returned result.

#!/usr/bin/env python
# coding=utf-8
import urllib3
import requests
from config import CF
from utils.logger import log
from common.regular import Regular
from common.setResult import replace_param
from core.serialize import deserialization
from requests.exceptions import RequestException
from common.variables import VariablePool

urllib3.disable_warnings()


class HttpRequest:
    """二次封装requests方法"""

    http_method_names = 'get', 'post', 'put', 'delete', 'patch', 'head', 'options'

    def __init__(self):
        self.r = requests.session()
        self.reg = Regular()

    def send_request(self, case, **kwargs):
        """发送请求
        :param case: 测试用例
        :param kwargs: 其他参数
        :return: request响应
        """
        if case[CF.URL]:
            VariablePool.set('url', case[CF.URL])
        if case[CF.HEADERS]:
            VariablePool.set('headers', deserialization(case[CF.HEADERS]))

        method = case[CF.METHOD].upper()
        url = VariablePool.get('url') + case[CF.ROUTE]
        self.r.headers = VariablePool.get('headers')
        params = replace_param(case)
        if params: kwargs = params
        try:
            log.info("Request Url: {}".format(url))
            log.info("Request Method: {}".format(method))
            log.info("Request Data: {}".format(kwargs))

            def dispatch(method, *args, **kwargs):
                if method in self.http_method_names:
                    handler = getattr(self.r, method)
                    return handler(*args, **kwargs)
                else:
                    raise AttributeError('request method is ERROR!')
            response = dispatch(method.lower(), url, **kwargs)
            log.info(response)
            log.info("Response Data: {}".format(response.text))
            return response
        except RequestException as e:
            log.exception(format(e))
        except Exception as e:
            raise e

Serialization and deserialization

serialize.py

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import json
from json.decoder import JSONDecodeError


def deserialization(content: json):
    """
    反序列化
        json对象 -> python数据类型
    """
    return json.loads(content)


def serialization(content, ensure_ascii=True):
    """
    序列化
        python数据类型 -> json对象
    """
    return json.dumps(content, ensure_ascii=ensure_ascii)


def is_json_str(string):
    """判断是否是json格式字符串"""
    if isinstance(string, str):
        try:
            json.loads(string)
            return True
        except JSONDecodeError:
            return False
    return False


if __name__ == '__main__':
    a = "{'data': {'loginName': 18291900215, 'password': 'dd636482aca022', 'code': None, 'description': 'encrypt'}}"
    print(is_json_str(a))

test result

checkResult.py

In this file, we will do the expected validation of the results returned by the tests.

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import re
from config import CF
from utils.logger import log
from requests import Response
from common.excelset import excel_set


def check_result(r: Response, number, case):
    """获取结果"""
    results = []
    excel_set.write_results(number, CF.SPEND_TIME, r.elapsed.total_seconds(), color=False)
    if case[CF.EXPECTED_CODE]:
        res = int(case[CF.EXPECTED_CODE]) == r.status_code
        results.append(res)
        if not res: excel_set.write_color(number, CF.EXPECTED_CODE)
        log.info(f"预期响应码:{case[CF.EXPECTED_CODE]},实际响应码:{r.status_code}")
    if case[CF.EXPECTED_VALUE]:
        res = case[CF.EXPECTED_VALUE] in r.text
        results.append(res)
        if not res: excel_set.write_color(number, CF.EXPECTED_VALUE)
        log.info(f"预期响应值:{case[CF.EXPECTED_VALUE]},实际响应值:{r.text}")
    if case[CF.EXPECTED_REGULAR]:
        res = r'%s' % case[CF.EXPECTED_REGULAR]
        ref = re.findall(res, r.text)
        results.append(ref)
        if not ref: excel_set.write_color(number, CF.EXPECTED_REGULAR)
        log.info(f"预期正则:{res},响应{ref}")
    if all(results):
        excel_set.write_results(number, CF.TEST_RESULTS, 'Pass')
        log.info(f"用例【{case[CF.NAME]}】测试成功!")
    else:
        excel_set.write_results(number, CF.TEST_RESULTS, 'Failed')
        assert all(results), f"用例【{case[CF.NUMBER]}{case[CF.NAME]}】测试失败:{results}"


 

Setting parameters

setResult.py

In this file, we have realized the extraction of the return value of the interface, and realized the function of the interface passing parameters.

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
from requests import Response
from utils.logger import log
from common.regular import Regular
from common.excelset import excel_set
from common.variables import VariablePool
from core.serialize import is_json_str, deserialization
from config import CF

reg = Regular()


def get_var_result(r: Response, number, case):
    """替换变量"""
    if case[CF.EXTRACT_VARIABLE]:
        for i in case[CF.EXTRACT_VARIABLE].split(','):
            result = reg.find_res(i, r.text)
            VariablePool.set(i, result)
            log.info(f"提取变量{i}={result}")
            if not VariablePool.get(i):
                excel_set.write_results(number, CF.EXTRACT_VARIABLE, f"提变量{i}失败")
    excel_set.write_results(number, CF.RESPONSE_TEXT,
                            f"ResponseCode:{r.status_code}\nResponseText:{r.text}")


def replace_param(case):
    """传入参数"""
    if case[CF.PARAMETER]:
        if is_json_str(case[CF.PARAMETER]):
            is_extract = reg.finds(case[CF.PARAMETER])
            if is_extract:
                return deserialization(reg.subs(is_extract, case[CF.PARAMETER]))
    return deserialization(case[CF.PARAMETER])


test operation

test_api.py

We use unittest for testing. In the preconditions and postconditions, we initialize and close the session on the encapsulated HttpRequest method.

Use the expend method in the parameterized library to perform parameterized read execution for use cases in excel.

#!/usr/bin/env python
# coding=utf-8
import unittest
from parameterized import parameterized
from common.excelset import excel_set
from core.request import HttpRequest
from common.checkResult import check_result
from common.setResult import get_var_result


class TestApi(unittest.TestCase):
    """测试接口"""

    @classmethod
    def setUpClass(cls) -> None:
        cls.req = HttpRequest()

    @classmethod
    def tearDownClass(cls) -> None:
        cls.req.r.close()

    @parameterized.expand(excel_set.get_cases())
    def test_api(self, name, number, case):
        """
        测试excel接口用例
        """
        r = self.req.send_request(case)
        get_var_result(r, number, case)
        check_result(r, number, case)


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

Test report sending mail class

run.py

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import os
import platform
import argparse
import unittest
from common.variables import VariablePool
from utils.send_mail import send_report_mail
from utils.HTMLTestRunner import HTMLTestRunner


def running(path):
    """运行"""
    test_case = unittest.defaultTestLoader.discover('tests', 'test*.py')
    with open(path, 'wb') as fp:
        runner = HTMLTestRunner(stream=fp,
                                title='Excel接口测试',
                                description="用例执行情况",
                                verbosity=2)
        result = runner.run(test_case)
    if result.failure_count:
        send_report_mail(path)


def file_path(arg):
    """获取输入的文件路径"""
    if 'Windows' in platform.platform():
        _dir = os.popen('chdir').read().strip()
    else:
        _dir = os.popen('pwd').read().strip()
    if _dir in arg:
        return arg
    return os.path.join(_dir, arg)


def main():
    """主函数"""
    parser = argparse.ArgumentParser(description="运行Excel接口测试")
    parser.add_argument('-i', type=str, help='原始文件')
    parser.add_argument('-o', type=str, default='report.xlsx', help="输出文件")
    parser.add_argument('-html', type=str, default='report.html', help="报告文件")
    args = parser.parse_args()
    VariablePool.set('excel_input', file_path(args.i))
    VariablePool.set('excel_output', file_path(args.o))
    VariablePool.set('report_path', file_path(args.html))
    running(VariablePool.get('report_path'))


if __name__ == '__main__':
    main()



run

It is worth noting that when running the test, you must close the office and open the excel file.

In the final file, I used argparse for command line management, which means that we can test through the command line without caring about the directory where excel is stored.

python run.py -i data\usercase.xlsx

Enter the following command to execute it.

INFO 2020-07-30 22:07:52,713 request.py:40 Request Url: https://www.zhixue.com/weakPwdLogin/?from=web_login
INFO 2020-07-30 22:07:52,714 request.py:41 Request Method: POST
INFO 2020-07-30 22:07:52,715 request.py:42 Request Data: {'data': {'loginName': 18291900215, 'password': 'dd636482aca022', 'code': None, 'descriptio
n': 'encrypt'}}
INFO 2020-07-30 22:08:17,204 request.py:55 <Response [200]>
INFO 2020-07-30 22:08:17,204 request.py:56 Response Data: {"data":"1500000100070008427","result":"success"}
INFO 2020-07-30 22:08:17,207 setResult.py:20 提取变量data=1500000100070008427
INFO 2020-07-30 22:08:17,307 checkResult.py:18 预期响应码:200,实际响应码:200
INFO 2020-07-30 22:08:17,308 checkResult.py:23 预期响应值:"result":"success",实际响应值:{"data":"1500000100070008427","result":"success"}
INFO 2020-07-30 22:08:17,310 checkResult.py:29 预期正则:[\d]{16},响应['1500000100070008']
INFO 2020-07-30 22:08:17,356 checkResult.py:32 用例【登录】测试成功!
ok test_api_0__ (test_api.TestApi)
INFO 2020-07-30 22:08:17,358 regular.py:20 提取变量:['data']
INFO 2020-07-30 22:08:17,359 regular.py:23 替换变量:data
INFO 2020-07-30 22:08:17,361 regular.py:26 替换结果:{"data": {"userId": "1500000100070008427"}}
INFO 2020-07-30 22:08:17,363 request.py:40 Request Url: https://www.zhixue.com/loginSuccess/
INFO 2020-07-30 22:08:17,366 request.py:41 Request Method: POST
INFO 2020-07-30 22:08:17,367 request.py:42 Request Data: {'data': {'userId': '1500000100070008427'}}
INFO 2020-07-30 22:08:20,850 request.py:55 <Response [200]>
INFO 2020-07-30 22:08:20,851 request.py:56 Response Data: {"result":"success"}
INFO 2020-07-30 22:08:20,932 checkResult.py:18 预期响应码:200,实际响应码:200
INFO 2020-07-30 22:08:20,933 checkResult.py:23 预期响应值:"result":"success",实际响应值:{"result":"success"}
INFO 2020-07-30 22:08:20,935 checkResult.py:29 预期正则:11,响应[]
F  test_api_1__ (test_api.TestApi)

Time Elapsed: 0:00:28.281434
测试结果邮件发送成功!

enforce rules

(venv) C:\Users\hoou\PycharmProjects\httptest-excel>python run.py -h
usage: run.py [-h] [-i I] [-o O] [-html HTML]

运行Excel接口测试

optional arguments:
  -h, --help  show this help message and exit
  -i I        原始文件
  -o O        输出文件
  -html HTML  报告文件

Enter at the command linepython run.py excel路径 新excel路径 报告路径

If you do not enter 新excel路径and 报告路径, run.pytwo report.xlsx and report.html will be generated in the directory.

The excel test framework of this article is complete.

Video explanation: Mashang’s products must be high-quality products. Mr. Beifan will teach you the actual combat of python+excel interface automation testing, which is not to be missed_哔哩哔哩_bilibili icon-default.png?t=N3I4https://www.bilibili.com/video/BV1ZX4y1B7ci/?spm_id_from= 333.999.0.0

 

 

Guess you like

Origin blog.csdn.net/MXB_1220/article/details/130477274