0%

自动化脚本生成错误结果文档

早期项目搭建的时候为了统一接口返回格式,基于werkzeug的HTTPException实现了APIException用于返回json格式,并制定了一套完善的错误码体系,但测试小伙伴对错误码对应的问题类型并不了解,因此需要编写一份文档用于方便查阅错误码和错误的原因。但是每次添加新的错误码都需要同步更新文档,这可不太妙。因此设计一个文档的同步更新机制迫在眉睫

APIException

flask 主要是使用werkzeug来实现WSGI application。当api请求错误会返回HTTPException。
查看源码发现返回类型是文本格式。

1
2
3
def get_headers(self, environ=None):
"""Get a list of headers."""
return [("Content-Type", "text/html")]

为了统一返回的格式类型,继承HTTPException实现了APIException。

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
from flask import request, json
from werkzeug.exceptions import HTTPException


class APIException(HTTPException):
code = 500
description = 'sorry, we made a mistake (* ̄︶ ̄)!'
error_code = 999

def __init__(self, description=None, code=None, error_code=None,
headers=None):
if code:
self.code = code
if error_code:
self.error_code = error_code
if description:
self.description = description
super(APIException, self).__init__(description)

def get_body(self, environ=None):
body = dict(
description=self.description,
error_code=self.error_code,
request=request.method + ' ' + self.get_url_no_param()
)
text = json.dumps(body)
return text

def get_headers(self, environ=None):
return [('Content-Type', 'application/json')]

@staticmethod
def get_url_no_param():
full_path = str(request.full_path)
main_path = full_path.split('?')
return main_path[0]

通过flask核心对象的errorhandler拦截所有的Exception并返回统一的APIException实现异常的请求也返回json格式。

1
2
3
4
5
6
7
8
9
10
11
@app.errorhandler(Exception)
def global_error(error):
if isinstance(error, APIException):
return error
elif isinstance(error, HTTPException):
return RecvHttpException(description=error.description, code=error.code)
else:
if not app.config['DEBUG']:
return ServerError()
else:
raise error

自动化文档实现原理

继承APIException的类通常格式如下:

1
2
3
4
5
6
7
class PinyinTransferFail(APIException):
"""
无法识别的拼音字母
"""
error_code = 1010
code = 400
description = "un-recognize pinyin"

项目结构:
1

项目中还有一些错误并不是由APIException定义的因此将其定义在special_error.json中,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
[
{
"error_code": 1,
"description": "任务执行失败"
},
{
"error_code": -2,
"description": "adb协议故障"
},{
"error_code": -3,
"description": "设备没连/连错wifi"
}
]

思路:
通常,动态读取所有的错误类型的类,并将类的error_code 和 doc(doc为空着取description) 一一对应,生成表单返回。

因此定义如下方法:

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
import importlib
import pkgutil
from typing import List

import pandas as pd

import inspect

def get_module_from_package(package):
"""
非递归,值获取package下第一层的module
"""
prefix = package.__name__ + "."
return [importlib.import_module(modname) for importer, modname, ispkg in
pkgutil.iter_modules(package.__path__, prefix) if not ispkg]


def get_classes(module, superclass=object):
"""
获取module下的为 superclass 的子类的所有类
"""
clsmembers = inspect.getmembers(module, inspect.isclass)

return [_class for (name, _class) in clsmembers if issubclass(_class, superclass)]


def convert_to_html(result: [List[List[str]]], title: List[str]):
"""
将[[str],[str],[str]] [str] 转化成html
"""
d = {}
index = 0
for t in title:
d[t] = result[index]
index = index + 1
df = pd.DataFrame(d)
df = df[title]
h = df.to_html(index=False)
return h

定义接口,返回html表格

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
@app.route('/doc/')
def doc():
from app.execption.outer.error import APIException
from app.execption.outer import error_code
from app.libs.extension.tools import convert_to_html, get_module_from_package, get_classes
import json
from collections import namedtuple
SpecialException = namedtuple("SpecialException", ["error_code", "description"])

title = ['code', 'name', 'description']

def special_case():
special_error_file_path = os.path.join(BASE_DIR, "app", "execption", "outer", "error_code",
"special_error.json")
with open(special_error_file_path, "r") as json_file:
special_msg_list = json.load(json_file)
return [
SpecialException(error_code=special_msg["error_code"], description=special_msg["description"])
for special_msg in special_msg_list
]

_all = special_case()
result = [[], [], []]
for module in get_module_from_package(error_code):
_all += get_classes(module, APIException)
# 鸭子类型特性
for _class in sorted(set(_all), key=lambda obj: obj.error_code):
result[0].append(_class.error_code)
result[1].append(getattr(_class, "__name__", ""))
result[2].append(
_class.__doc__.strip()
if not isinstance(_class, SpecialException) and getattr(_class, "__doc__", "")
else _class.description
)
return convert_to_html(result, title)