Skip to content

138.完成用例生成功能(上)

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

回顾

上一节我们编写了提取参数部分,我们已经可以拿到组成一个用例的全部数据了。

接着我们就来实现它。老规矩,先捋一捋思路。

思路

我们在页面上可以录制到具体的接口参数,其实每一条记录都是RequestInfo的具体表现。所以,我们根据用户选择的RequestInfo数组,按照他们的顺序,就可以提取为具体的用例数据,接着我们调用之前编写过的提交用例全部数据方法就完事了。

调整枚举类

在此之前,我做了一个改动:将曾经config.py里面的枚举类或者子类都抽离出来,放到enums文件夹。

这里就不得不思考一下这样做的好处,最近几天也是感悟了一些,以下是我的个人看法。

有个朋友一直很纠结于他的项目目录设计,我仔细考虑了一下,得出了以下的看法(个人观点):

每个人都会有自己的命名习惯或者目录规范,比如有的人喜欢用util有的喜欢用utils,或者说有的人喜欢dao dto entity等Java风格的命名,有的喜欢用src,internal这样的内部包命名。但这些,其实都不是最重要的。

所谓规范,其实就是为了给其他人理解,并更好地对代码文件分类,比如我这里有个enums文件夹,那么不用想,进去就知道他是放枚举类的,如果我放一个orm在里面,那自然是不合适的。所以基本的目录含义满足了,也就没必要过于纠结目录规不规范了。如果作为一个团队,团队肯定有对应的规范,如果是个人,那你自己就是规范。

  • 添加2个枚举类

app/enums/CaseStatusEnum.py

# 1: 调试中 2: 暂时关闭 3: 正常运作
from enum import IntEnum


class CaseStatus(IntEnum):
    debugging = 1
    closed = 2
    running = 3

app/enums/RequestTypeEnum.py

from enum import IntEnum


class RequestType(IntEnum):
    http = 1
    grpc = 2
    dubbo = 3
    websocket = 4

把RequestInfo放入schema

由于RequestInfo在用例生成页面即将成为一个入参,所以他是适合放到schema的:

__author__ = "woody"

import json
from typing import TypeVar

import pydantic
from loguru import logger

"""
translate mitmproxy request and response data
"""

body = TypeVar("body", bytes, str)


class RequestInfo(pydantic.BaseModel):
    url: str
    body: str
    request_method: str
    # request_data: str
    request_headers: dict
    response_headers: dict
    cookies: dict
    request_cookies: dict
    response_content: str
    status_code: int

    def __init__(self, flow=None, **kwargs):
        if flow:
            kwargs.update(
                dict(status_code=flow.response.status_code,
                     url=flow.request.url,
                     request_method=flow.request.method,
                     request_headers=dict(flow.request.headers),
                     response_headers=dict(flow.response.headers),
                     response_content=self.get_response(flow.response),
                     body=self.get_body(flow.request),
                     cookies=dict(flow.response.cookies),
                     request_cookies=dict(flow.request.cookies),
                     ))
        super().__init__(**kwargs)

    def from_dict(self, **kwargs):
        for k, v in kwargs:
            if not hasattr(self, k):
                raise Exception(f"set RequestInfo error, no field: {k}")
            setattr(self, k, v)

    @classmethod
    def translate_json(cls, text):
        try:
            return json.dumps(json.loads(text), indent=4, ensure_ascii=False)
        except Exception as e:
            logger.bind(name=None).warning(f"解析json格式请求失败: {e}")
            return text

    @classmethod
    def get_response(cls, response):
        content_type = response.headers.get("Content-Type")
        if "json" in content_type.lower():
            return cls.translate_json(response.text)
        if "text" in content_type.lower() or "xml" in content_type.lower():
            return response.text
        return response.data.decode('utf-8')

    @classmethod
    def get_body(cls, request):
        if len(request.content) == 0:
            return ''
        content_type = request.headers.get("Content-Type")
        if "json" in content_type.lower():
            return cls.translate_json(request.text)
        if "text" in content_type.lower() or "xml" in content_type.lower():
            return request.text
        return request.data.decode('utf-8')

    def dumps(self):
        return json.dumps(self.dict(), ensure_ascii=False)

基本是从app/core/request/request.py里面拷贝来的代码,不过这里多了一种情况:

由于pydantic的初始化不适配mitmproxy的初始化,也就是flow与普通的__init__(),所以我这把进行了一下判断。

如果有flow,说明是录制的时候),否则说明是请求参数初始化的时候。

testcase_schema.py新增一个类

class TestCaseGeneratorForm(BaseModel):
    directory_id: int
    requests: List[RequestInfo]

由于一个用例必须有自己归属的目录,所以我们需要为它归属一个目录,其实还需要用例名称等等,但我们目前只做一个雏形,后面可以慢慢完善,比如我们给一个默认值就好了。

新的ide配色还有点不习惯,今天的内容就到这里了,下一节我们继续深入。