测试平台系列(141) 优化添加用例功能.md

2022/6/22 测试平台接口测试FastApiPythonReact

大家好~我是米洛
我正在从0到1打造一个开源的接口测试平台, 也在编写一套与之对应的教程,希望大家多多支持。
欢迎关注我的公众号米洛的测开日记,获取最新文章教程!

# 回顾

上一节我们编写了Mac环境配置mitmproxy相关的教程,我们也留下了一个疑问。(其实我也没有很好的办法解决它,就暂时先放一放)这节我们可以收拾下心情,把用例录制生成功能做得稍微细致一点。也就是能够在添加用例的时候,多一些选择。

# 改造添加用例

添加用例这块,之前我们改造过一次,把它从独立的页面给分解了出来,现在是一个弹窗形式:

但现在我们想支持一些别的形式的case,比如har/录制的用例,后续可能还要导入httprunner/jmx的用例,所以我们点击添加用例的时候,需要增加一个新的入口, 如下图所示:

点开后,我们可以看到一个新的表单:

录制的话,基本决定目录和用例名称就可以了,稍微简单点。下方的2个按钮则是给用户一个选择,到底是选用录制到的按钮,还是决定导入har文件。

这2者都没有大的区别,都是为了先生成RequestInfo数组,接着将他们转换为测试用例

# 编写代码

# 微调生成用例接口

虽然我们可以简单点,让用户手动生成用例之后去改用例名称,但是本身我们就是需要用户选择目录的,所以我们可以直接把用例名称也安排了。

为此我们需要修改generate_case接口:

  • 修改schema

  • 修改接口

多传入一个form参数

  • 修改具体细节

删除这一行代码,使用传入的name参数。

# 编写导入har的接口

因为我们后续可能还支持其他文件的导入,所以url,参数等都不能写死,最好是能够支持一些类型,比如我现在只支持har,那就har=1,所以我们去定义一个har枚举:

接着我们需要调整一下之前的导入方法,因为我们之前是传入文件路径+正则的形式,我们这里的上传har接口接收的直接就是文件流对象,所以我们需要改为file_object:

import json
import re
from typing import List

from app.core.request.convertor import Convertor
from app.excpetions.convert.ConvertException import HarConvertException
from app.schema.request import RequestInfo


class HarConvertor(Convertor):
    @staticmethod
    def convert_from_file(file, regex: str = None) -> List[RequestInfo]:
        with open(file, "r", encoding="utf-8") as f:
            return HarConvertor._convert(f, regex)

    @staticmethod
    def _convert(f, regex: str = None):
        try:
            flag = None
            if regex is not None:
                flag = re.compile(regex)
            # 加载har请求数据
            data = json.load(f)
            ans = []
            entries = data.get("log", {}).get("entries")
            if not entries:
                raise HarConvertException("entries数据为空")
            for entry in entries:
                # 如果是fetch或xhr接口,说明是http请求(暂不支持js)
                if entry.get("_resourceType").lower() in ("fetch", "xhr"):
                    request_data = entry.get("request")
                    response_data = entry.get("response")
                    url = request_data.get("url")
                    if flag is not None and not re.findall(flag, url):
                        # 由于不符合预期的url,所以过滤掉
                        continue
                    info = RequestInfo(url=url, response_data=entry.get("response"),
                                       body=HarConvertor.get_body(request_data),
                                       status_code=response_data.get("status"),
                                       request_method=request_data.get("method"),
                                       request_headers=HarConvertor.get_kv(request_data),
                                       response_headers=HarConvertor.get_kv(response_data),
                                       cookies=HarConvertor.get_kv(response_data, "cookies"),
                                       request_cookies=HarConvertor.get_kv(request_data, "cookies"),
                                       response_content=response_data.get("content", {}).get("text")
                                       )
                    ans.append(info)
            return ans
        except HarConvertException as e:
            raise HarConvertException(f"har文件转换异常: {e}")
        except Exception as e:
            raise HarConvertException(f"har文件转换失败: {e}")

    @staticmethod
    def convert(file_data, regex: str = None) -> List[RequestInfo]:
        return HarConvertor._convert(file_data, regex)

    @staticmethod
    def get_kv(request_data: dict, key: str = "headers") -> dict:
        """
        通过response/request获取header信息
        :param key:
        :param request_data:
        :return:
        """
        headers = request_data.get(key)
        result = dict()
        for h in headers:
            result[h.get("name")] = h.get("value")
        return result

    @staticmethod
    def get_body(request_data: dict):
        data = request_data.get("postData", {})
        return data.get("text", '')


if __name__ == "__main__":
    requests = HarConvertor.convert_from_file("./pity.fun.har")
    print(requests)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

其他代码没变化,我们加入了一个_convert方法,并写了一个内部的convert_from_file的方法,以此来兼容之前的测试数据。

注意这里还有一些变化,由于我们之前提取的RequestInfo被我们放入了schema,所以他不能直接初始化,得修改下构造的地方。

接着我们就需要编写对应的接口了,我们得先编写一个根据ConvertorType值返回转换器的方法:

新建app/core/request/__init__.py

from app.core.request.convertor import Convertor
from app.core.request.har_convertor import HarConvertor
from app.enums.ConvertorEnum import CaseConvertorType


def get_convertor(c: CaseConvertorType) -> (Convertor.convert, str):
    if c == CaseConvertorType.har:
        return HarConvertor.convert, CaseConvertorType.har.name
    return None, ""

1
2
3
4
5
6
7
8
9
10

当等于har时,我们返回Har的构造器,否则返回None,第二个变量会返回枚举名字,也就是har或其他。因为文件上传的时候我们需要判断后缀,用户会传入import_type,我们顺便也一起获取了。

下面来看看具体的接口:

很好理解,如果转换器获取失败了,则返回。接着判断文件后缀对不对,不对就,嘿嘿嘿!!~

# 最后直接调用转换方法即可,比较实在。

今天的内容就先介绍到这里,下一节我们直接进入前端页面的开发。顺便感受下tsx的魅力。