接口自动化:一种基于 Excel 合并单元格的设想与实践

本贴最后更新于 1575 天前,其中的信息可能已经时移俗易

前言

在实际工作中,使用Excel存储自动化测试数据是很多公司的首选,一方面由于Excel比较直观,方便操作;另一方面Python读取Excel也比较方便,不需要写多少代码就可以读出所有的测试数据。但是,随着项目的业务日趋复杂,使用Excel中的一行作为一个测试用例,对于复杂的测试场景来说,涉及到几个甚至十几个接口的时候,分散的接口用例关联起来难度较大,比如注册接口在第二行,登录接口在第五行,选择商品接口在第二十行,这种散落在各个地方的接口找起来就特别费时间。因此,需要一种方式,将这些关联的接口合并一起,一个合并单元格作为一条用例,一条用例可能包含多个关联接口,这样整齐划一的方式极大的方便了后期的维护
image.png

扩展读取Excel类

对合并单元格的读取,要求对现有的读取Excel的类做一扩展。具体的思路是:通过self.sh.merged_cells拿到合并单元格的行和列的范围,将这些行和列的范围添加到集合set中,分别命名为m_rowsm_cols,然后遍历m_rows,拿到其中的每一个合并行范围,再对这个合并行范围做遍历,将合并行每一行对应的单元格的值组合成一个列表,通过列表生成式获取列表title,再将合并单元格的列表和列表title通过zip,dict转化成一个字典,添加到all_case_data
通过对最大行范围和合并行范围取差集,得到的就是非合并行,然后和前面类似,非合并行每一行的值都是列表,和列表title转化成一个字典,添加到all_case_data
最后,你看到得到的all_case_data是一个顺序混乱的列表字典的形式,将sorted中的key指定为列表中的字典的其中一个key:case_data["case_id"][0]来升序排列(为了更好的排序,建议Excel中的case_id列采用001,002,003...这样的方式)

from openpyxl import load_workbook
from common.replace_variable import *
import json, re


class DoExcel:

    def __init__(self, file_path):
        self.file_path = file_path
        self.wb = load_workbook(self.file_path)
        self.sh = self.wb["case_datas"]


    #读取所有测试数据
    def read_all_caseData(self):
        #得到最大行、最大列
        max_row = self.sh.max_row
        max_column = self.sh.max_column
        #得到合并单元格的范围
        m_rows = []
        m_cols = []
        for m_area in self.sh.merged_cells:
            r1, r2, c1, c2 = m_area.min_row, m_area.max_row, m_area.min_col, m_area.max_col
            m_rows.append((r1, r2))
            m_cols.extend([c1, c2])
        m_rows = set(m_rows)
        m_cols = set(m_cols)

        #获取所有的测试数据,将其以字典的形式存储在列表中
        all_case_data = []
        title = [self.sh.cell(1, column).value for column in range(1, max_column + 1)]
        #获取合并行数据
        for m_row in m_rows:
            case_data = []
            for column in range(1, max_column + 1):
                for row in range(m_row[0], m_row[1] + 1):
                    if row == m_row[0]:
                        value = [[self.sh.cell(row, column).value]]
                    else:
                        for item in value:
                            item.append(self.sh.cell(row, column).value)
                case_data.extend(value)
            all_case_data.append(dict(zip(title, case_data)))

        #最大行范围和合并范围求差集,就是非合并行
        max_row_range = set(range(2, max_row + 1))
        for m_row in m_rows:
            m_row_range = set(range(m_row[0], m_row[1] + 1))
            max_row_range = max_row_range.difference(m_row_range)

        #获取非合并行数据
        for row in max_row_range:
            case_data = []
            for column in range(1, max_column + 1):
                value = [self.sh.cell(row, column).value]
                case_data.append(value)
            all_case_data.append(dict(zip(title, case_data)))

        #排序:根据case_id的数字从大到小
        all_case_data = sorted(all_case_data, key=lambda case_data: case_data["case_id"][0])

        #对检查的字符串做字典的转化,最终以字典的形式存储在列表中
        for case_data in all_case_data:
            self._check_convert_dict(case_data)
        print(all_case_data)
        return all_case_data


    #将检查项转化为字典
    def _check_convert_dict(self, case_data):
        for i in range(len(case_data["case_id"])):
            check_list = case_data["check"]
            new_check_list = []
            for check in check_list:
                new_check = {}
                items = re.findall("(\$.*?):.*?\"(.*?)\"", check)
                for item in items:
                    key, value = item
                    new_check[key] = value
                new_check_list.append(new_check)
        case_data["check"] = new_check_list




if __name__ == "__main__":
    all_case_data = DoExcel(r"E:\virtual_workshop\Python_API\test_datas\api_info.xlsx").read_all_caseData()

扩展测试类

拿到的all_case_data是列表字典的形式,其中的每个元素都是字典,每个字典的value都是列表,其中列表中的每一项元素都是合并单元格的每个单元格对应的值,就是这种形式
image.png
这就要求测试类test_my_request,在ddt的基础上,增加for循环。前面说过for循环遍历的N条用例都会默认是一条用例,正好将每一个合并单元格当做了一条用例对待

import unittest
import ddt
import json
from jsonpath import jsonpath
from common.do_excel import DoExcel
from common.replace_variable import ReplaceVariable
from common.context import Context
from common.my_request import *
from common.my_logging import *
from common.read_cfg import *
import logging


@ddt.ddt
class TestMyRequest(unittest.TestCase):
    do_excel = DoExcel(r"{0}\api_info.xlsx".format(testdatas_dir))
    all_case_data = do_excel.read_all_caseData()
    #读取配置文件
    test_host, sql_host = get_host_section()

    @ddt.data(*all_case_data)
    def test_my_request(self, case_data):


        logging.info("用例『{0}』测试开始了".format(case_data["case_desc"][0]))
        for i in range(len(case_data["case_id"])):

            #拼接url
            if case_data["host"][i] == "test":
                case_data["url"][i] = self.test_host + case_data["url"][i]
            elif case_data["host"][i] == "sql":
                case_data["url"][i] = self.sql_host + case_data["url"][i]

            #替换请求参数
            request_data = json.loads(ReplaceVariable.replace_variable(case_data["request_data"][i]))
            logging.info("请求数据: {0}".format(request_data))

            #请求数据反射
            request_extract = case_data["request_extract"][i]
            if request_extract:
                key, express = request_extract.split("=")
                setattr(Context, key, jsonpath(request_data, express)[0])


            if case_data["method"][i].lower() == "get":
                result = send_request(case_data["method"][i], case_data["url"][i], params=request_data)
            elif case_data["method"][i].lower() == "post":
                result = send_request(case_data["method"][i], case_data["url"][i], data=request_data)
            logging.info("响应结果: {0}".format(result))

            #响应数据反射
            response_extract = case_data["response_extract"][i]
            if response_extract:
                key, express = response_extract.split("=")
                setattr(Context, key, str(jsonpath(result, express)[0]))


            #断言
            for key, value in case_data["check"][i].items():
                #替换期望结果
                actual = str(jsonpath(result, key)[0])
                expect = ReplaceVariable.replace_variable(value)
                logging.info("实际结果: {0}".format(actual))
                logging.info("期望结果: {0}".format(expect))
                self.assertEqual(actual, expect)

        logging.info("\n\n")

数据库断言

常规的做法是,在Excel中写一条用例,通过PyMSQL读取,拿到数据后,再去响应结果里查找。这种方法很繁琐,因为你得解析响应结果。之前的接口框架也对请求数据和响应结果做了反射,为什么不利用这些数据呢?一个设想是,我把数据库的查询作为一个接口来测,使用Flask封装成接口,再通过Excel传入对应的sql语句,这个sql语句里需要的条件,我可能从上个接口里提取,提取到作为反射数据来用。举个例子,常规做法是我投资了200元,通过select语句查询这个人的投资记录,拿到投资记录的结果筛选出金额,再去响应结果里找,现在的做法是,我传入select * from t_user where uid=xxx and invest_money=200,将这个作为请求参数,传给sql对应的查询接口,只要查询有结果就行,不需要再做额外的解析和比对
image.png

from flask import Flask, request
from common.read_mysql import ReadMysql
import json

app = Flask(__name__)



@app.route("/query/one", methods=["post"])
def query_one():
    read_mysql = ReadMysql()
    query_sql = request.form.get("query_sql")
    one_data = read_mysql.select_one_data(query_sql)
    if one_data:
        return json.dumps({"code": "200", "result": str(one_data)})
    else:
        return json.dumps({"code": "404"})


app.run()
回帖
请输入回帖内容 ...