一税朴、背景介紹
- 因在版本迭代過(guò)程中,一般都需要調(diào)用接口來(lái)實(shí)現(xiàn)需求業(yè)務(wù)瘩燥。而前后端或各系統(tǒng)之間都存在強(qiáng)依賴(lài)性秕重,故構(gòu)思了此Mock接口;主要解決如下場(chǎng)景痛點(diǎn):
- 1厉膀、前后端的依賴(lài)關(guān)系:如前端已開(kāi)發(fā)完畢但后端還沒(méi)完成溶耘,導(dǎo)致前端無(wú)法進(jìn)行調(diào)試;
- 2、外部系統(tǒng)依賴(lài)關(guān)系:如外部系統(tǒng)未開(kāi)發(fā)完或者環(huán)境的因素?zé)o法完成對(duì)接調(diào)試;
- 3服鹅、測(cè)試階段依賴(lài)關(guān)系:測(cè)試的某些場(chǎng)景無(wú)法模擬下凳兵,可調(diào)用mock接口設(shè)置自定義返回值,從而達(dá)到測(cè)試場(chǎng)景的覆蓋(主要就是這塊企软,因咱就是干測(cè)試滴)庐扫;
二、構(gòu)思設(shè)計(jì)及主要功能點(diǎn)
2.1)仗哨、主要功能介紹:
1形庭、 接口可自定義規(guī)則:對(duì)mock接口依據(jù)配置規(guī)則,做字段數(shù)據(jù)必填項(xiàng)校驗(yàn)厌漂、數(shù)據(jù)類(lèi)型校驗(yàn)
2萨醒、 接口可自定義匹配:可設(shè)置數(shù)據(jù)匹配規(guī)則,且支持設(shè)置多個(gè)匹配值苇倡;
3富纸、 接口支持多級(jí)url、動(dòng)態(tài)url旨椒、多種請(qǐng)求方式(get/post/put等)
4胜嗓、 接口狀態(tài)碼自定義:可自定義設(shè)置接口返回狀態(tài)碼
5、 響應(yīng)數(shù)據(jù)-可自定義變量返回:可依據(jù)python代碼設(shè)置自定義變量(如時(shí)間戳钩乍、UUID等變量)
6辞州、 接口可自定義控制關(guān)閉和開(kāi)啟,關(guān)閉后將無(wú)法調(diào)用
7寥粹、 響應(yīng)數(shù)據(jù)后門(mén)屬性:可依據(jù)請(qǐng)求指定key后变过,返回請(qǐng)求中的value數(shù)據(jù)
2.2)埃元、功能/思路導(dǎo)圖:
三、直接先看最后的成果:
-
1媚狰、Mock/路由配置頁(yè)面:定義接口路徑以及相應(yīng)的規(guī)則.
-
2岛杀、Mock/接口響應(yīng)數(shù)據(jù)頁(yè)面:定義接口匹配數(shù)據(jù)及返回的數(shù)據(jù)
-
3、實(shí)際調(diào)用結(jié)果:調(diào)用接口驗(yàn)證有效性.
四崭孤、上代碼說(shuō)明( 因瑣碎點(diǎn)太多类嗤,只抽取主流點(diǎn)和關(guān)鍵點(diǎn)作為說(shuō)明)
- 作為一個(gè)測(cè)試小白,比不了他們專(zhuān)業(yè)的開(kāi)發(fā)人員辨宠。這小項(xiàng)目整整一個(gè)月斷斷續(xù)續(xù)的寫(xiě)完了遗锣,其中還是有很多優(yōu)化空間,后續(xù)再慢慢調(diào)整嗤形;
-
各位大佬精偿,如有優(yōu)化或其他構(gòu)思思路歡迎指點(diǎn)評(píng)論,我再修改修改赋兵;
1笔咽、從開(kāi)始的路由方面:
- 1.1)、主路由:此處為入口的主路由霹期,依據(jù)settings配置中ROOT_URLCONF字段判斷主路由位置
#settings配置中ROOT_URLCONF字段判斷主路由位置
ROOT_URLCONF = 'project.urls'
urlpatterns = [
path(r'admin/mockapi/index/', views.IndexClass.index, name='index-test'), # 接口測(cè)試頁(yè)面
path('admin/', admin.site.urls),#django后臺(tái)管理頁(yè)面路由
path('api/mock/', include('mockapi.urls')),#通用接口的路由
path('api/test/', include('testapi.urls')),#自定義接口的路由
]
urlpatterns += staticfiles_urlpatterns()
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
- 1.2)叶组、二級(jí)路由(以api/mock為例):找到二級(jí)路由后并進(jìn)入對(duì)應(yīng)的views(視圖)中,既進(jìn)入視圖邏輯:
urlpatterns = [
path(r'index', IndexClass.index, name='index-test'), # 接口測(cè)試頁(yè)面
path('caseid/registno', views.Caseid.as_view(), name='caseid'),
]
add_path = [
re_path(r'(\w+)/(\w+)/(\w+)', views.CurrencyRoute.as_view(), name='test3-api-viewstwo'), # 動(dòng)態(tài)路由+3
re_path(r'(\w+)/(\w+)', views.CurrencyRoute.as_view(), name='test2-api-viewstwo'), # 動(dòng)態(tài)路由+2
re_path(r'(\w+)', views.CurrencyRoute.as_view(), name='test1-api-viewstwo'), # 動(dòng)態(tài)路由+1
]
urlpatterns.extend(add_path)
2历造、從views視圖方面:
class CurrencyRoute(views.APIView, Currency):
'''通用模式下-API接口視圖類(lèi)'''
def __init__(self):
super(Currency, self).__init__()
self.code = ['code', '-1']
self.msg = ['msg']
self.data = ['data', None]
self.DataCheck = DataCheck(self.code, self.msg, self.data)
self.response_data = {self.code[0]: self.code[1], self.msg[0]: None, self.data[0]: self.data[1]}
# 根據(jù)url找路由表數(shù)據(jù):路由id、條件匹配規(guī)則帕膜、字段要求規(guī)則
def __getroute(self, args, UrlType, replace='/api/mock/', APIOnOff=True):
# route = "/".join(list(args)) if re_path.find('-') == -1 else self.find_path("/", re_path, args)
route = self.request.path.replace(replace, '')
try:
get_data = RouteTabMock.objects.get(CustomUrl=route, UrlType=UrlType, APIOnOff=APIOnOff)
except:
# 兼容動(dòng)態(tài)url接口枣氧,目前只支持兩級(jí)動(dòng)態(tài),樣例:test/case/{variable}/{variable}
route_url = [m.start() for m in re.finditer('/', route)]
for count_i in range(1, len(route_url)):
try:
get_data = RouteTabMock.objects.get(CustomUrl=route[:route_url[-count_i]] + r'/{}' * count_i,
UrlType=UrlType, APIOnOff=APIOnOff)
get_data.ConditionValue = [route[route_url[-count_i] + 1:]]
except RouteTabMock.DoesNotExist:
if count_i == len(route_url) - 1:
raise '動(dòng)態(tài)url匹配模式下,未匹配該url=' + route
else:
break
re_data = get_data.id, get_data.ConditionValue, get_data.APIRequired
logger.info('路由id={},條件匹配={},必填及類(lèi)型規(guī)則={}'.format(re_data[0], re_data[1], re_data[2]))
return re_data
# 接口請(qǐng)求處理垮刹,數(shù)據(jù)處理/規(guī)則處理
def __requestprocess(self, path_a):
logger.info(
'{}\n請(qǐng)求類(lèi)型={},地址={},報(bào)文={}'.format(80 * '=', self.request.method, self.request.path, self.request.data))
# 提取及處理request數(shù)據(jù)
if self.request.method == 'GET':
request_data = dict()
for key, value in dict(self.request.GET).items():
request_data[key] = value[0]
else:
request_data = dict(self.request.data)
# 預(yù)留一個(gè)后門(mén)屬性达吞,如果有此test_data字段直接返回
if Currency.test_data(request_data) != None:
if self.request.method == 'GET':
re_data = eval(request_data['test_data'])
else:
re_data = request_data['test_data']
return re_data
# 獲取路由id、條件匹配規(guī)則荒典、必填及數(shù)據(jù)類(lèi)型規(guī)則
try:
re_dataid, conditionValue, apirequired = self.__getroute(path_a, UrlType=self.request.method)
except:
msg = '路由表未配置該url:' + self.request.path
self.response_data[self.msg[0]] = msg
logger.error('{},請(qǐng)求返回值={}'.format(msg, self.response_data))
return self.response_data
# 對(duì)傳入?yún)?shù)進(jìn)行必填項(xiàng)校驗(yàn)和數(shù)據(jù)類(lèi)型的校驗(yàn)酪劫,校驗(yàn)不通過(guò)為False,返回Response錯(cuò)誤的響應(yīng)體
re_data = self.DataCheck.check(eval(apirequired), request_data)
if re_data != True:
return re_data
# 依據(jù)條件匹配規(guī)則寺董,找到入?yún)?duì)應(yīng)的條件查詢(xún)值
QueryValue = Querymethod().match_value(request_data, conditionValue, self.request.method)
logger.info('條件匹配規(guī)則結(jié)果:' + str(QueryValue))
# 如無(wú)條件匹配規(guī)則覆糟,取該接口下最新mock數(shù)據(jù)
if QueryValue == False:
try:
getdb_data = APIResponseMock.objects.filter(CustomUrl_id=re_dataid)
db_data = list(getdb_data.values())[-1]
ResponseData, ResponseCode = self.re_variable(db_data['ResponseData']), db_data['ResponseCode']
re_data = json.loads(ResponseData), ResponseCode
except IndexError:
msg = 'mock表未配置該url{}對(duì)應(yīng)的數(shù)據(jù)'.format(self.request.path)
self.response_data[self.msg[0]] = msg
logger.error(msg)
# 如果有條件匹配規(guī)則,對(duì)應(yīng)的匹配值遮咖;如果存在多個(gè)匹配值滩字,取第一個(gè)匹配的mock值
else:
try:
con = self.Q_model(QueryValue, re_dataid)
getdb_data = APIResponseMock.objects.filter(con)
db_data = list(getdb_data.values())[-1]
ResponseData, ResponseCode = self.re_variable(db_data['ResponseData']), db_data['ResponseCode']
re_data = json.loads(ResponseData), ResponseCode
except Exception as ex:
msg = '未找到匹配值:' + str(QueryValue)
re_data = {self.code[0]: self.code[1], self.msg[0]: msg, self.data[0]: self.data[1]}
logger.error(msg)
logger.info('請(qǐng)求返回值=' + str(re_data))
return re_data
# __接口狀態(tài)碼處理
def __request(self, response_data):
if type(response_data) == type(tuple()):
if str(response_data[1]) in ('None', ''):
response_data = response_data[0], 200
re_data, status_code = response_data
else:
re_data, status_code = response_data, 200
return re_data, status_code
# post請(qǐng)求方式
def post(self, request, *args):
re_data, status_code = self.__request(self.__requestprocess(args))
return Response(re_data, status=status_code)
# Get請(qǐng)求方式
def get(self, request, *args):
re_data, status_code = self.__request(self.__requestprocess(args))
return Response(re_data, status=status_code)
# Put請(qǐng)求方式
def put(self, request, *args):
re_data, status_code = self.__request(self.__requestprocess(args))
return Response(re_data, status=status_code)
*主要邏輯流介紹(不具體分解,詳見(jiàn)上述代碼內(nèi)注釋?zhuān)?br> 1、視圖第一層處理:后門(mén)屬性的判斷麦箍,以及提取和返回處理
2漓藕、視圖第二層處理:截取url后綴,得到url后找到路由表下對(duì)應(yīng)的字段規(guī)則及匹配規(guī)則挟裂;
3享钞、視圖第三層處理:得到接口字段規(guī)則后,對(duì)路由進(jìn)行必傳和數(shù)據(jù)類(lèi)型規(guī)則校驗(yàn)诀蓉;
4栗竖、視圖第四層處理:依據(jù)接口匹配規(guī)則,取入?yún)⒅档絤ock數(shù)據(jù)表查找匹配值并響應(yīng)數(shù)據(jù)渠啤;
class DataCheck(object):
'''數(shù)據(jù)必填+類(lèi)型校驗(yàn)'''
def __init__(self, code=['code', '-1'], msg=['msg'], data=['data', None]):
self.key_list = []
self.code = code
self.msg = msg
self.data = data
# 校驗(yàn)必填項(xiàng)以及數(shù)據(jù)類(lèi)型埃篓,如果不匹配拋出異常
def __value_type(self, dict_b, type_a):
dict_data = dict_b
try:
for keys in self.key_list:
dict_data = dict_data[keys]
if type(dict_data) != type(type_a) and type_a != True:
raise ValueError('[{}]字段類(lèi)型校驗(yàn)失敗处坪,期望傳入類(lèi)型為:{}'.format(keys, type(type_a)))
except KeyError:
if type_a == True:
raise ValueError('必填校驗(yàn)失敗,缺少必要參數(shù):' + str(keys))
def check(self, check_a, dict_b):
try:
# 為空不做校驗(yàn)根资,與頁(yè)面規(guī)則一致
if check_a == {}:
return True
re_data = self.get_dict_allkeys(check_a, dict_b)
except Exception as ex:
# 校驗(yàn)檢查不通過(guò),返回的報(bào)文架专;可通過(guò)類(lèi)屬性修改自定義key
ex = str(ex).strip('()')
re_data = {self.code[0]: self.code[1], self.msg[0]: ex, self.data[0]: self.data[1]}
logger.error('入?yún)⒉环闲r?yàn)規(guī)則={},請(qǐng)求返回值={}'.format(check_a, re_data))
return re_data
# 無(wú)限遍歷dict所有key,遞歸方式調(diào)用
def get_dict_allkeys(self, check_a, dict_b):
# 使用isinstance檢測(cè)數(shù)據(jù)類(lèi)型,dict遞歸
if isinstance(check_a, dict):
for x in range(len(check_a)):
temp_key = list(check_a.keys())[x]
temp_value = check_a[temp_key]
if type(temp_value) in (type({}), type([])):
self.key_list.append(temp_key)
self.get_dict_allkeys(temp_value, dict_b) # 遞歸遍歷
# 回退dict的上一層結(jié)構(gòu)
self.key_list = self.key_list[:-1]
elif isinstance(check_a, list):
for key_a in check_a:
if isinstance(key_a, dict):
# for x in range(len(key_a)):
# 遞歸到列表+元素位置0,即只支持列表第一個(gè)作為校驗(yàn)點(diǎn)
temp_key = list(key_a.keys())[0]
temp_value = key_a[temp_key]
self.key_list.append(0)
self.key_list.append(temp_key)
self.get_dict_allkeys(temp_value, dict_b)
# 必填校驗(yàn),兼容true,True
if str(key_a) in ('true', 'True'):
self.__value_type(dict_b, True)
continue
# 數(shù)據(jù)類(lèi)型校驗(yàn)玄帕,目前校驗(yàn)str/int/bool(兼容縮寫(xiě)及全拼)
if str(key_a) in ('str', 'string', "<class 'str'>"):
self.__value_type(dict_b, str())
elif str(key_a) in ('int', 'integer', "<class 'int'>"):
self.__value_type(dict_b, int())
elif str(key_a) in ('bool', 'boolean', "<class 'bool'>"):
self.__value_type(dict_b, bool())
# 回退dict的上一層結(jié)構(gòu)
self.key_list = self.key_list[:-1]
return True
*主要邏輯處理介紹:
1部脚、無(wú)限遍歷,如為dict既遞歸裤纹,如為list且再找到必填和數(shù)據(jù)類(lèi)型的輸入枚舉值既做規(guī)則校驗(yàn)處理
2委刘、如果不符合校驗(yàn)規(guī)則,既響應(yīng)對(duì)應(yīng)的錯(cuò)誤信息.
*備注:說(shuō)實(shí)話(huà)鹰椒,這塊的邏輯太復(fù)雜了锡移,主要場(chǎng)景太多了;自己測(cè)的時(shí)候改bug改到崩潰漆际,思路進(jìn)入了死循環(huán)淆珊。也體會(huì)了下開(kāi)發(fā)改bug的痛苦了;
class Querymethod(object):
def match_value(self, databody, conditionValue, req_type):
'''
依據(jù)接口設(shè)置匹配規(guī)則,獲取請(qǐng)求參數(shù)中的value擂找;以此作為查詢(xún)條件
:param databody: 接口請(qǐng)求參數(shù)數(shù)據(jù)-type=dict()
:param conditionValue: 接口配置的匹配規(guī)則條件-type=dict()
:return: 根據(jù)匹配規(guī)則,返回請(qǐng)求體中value
'''
# 遞歸引用列表取key,直至到value為'True', 'true'結(jié)束
def key_list(key_data, lista=list()):
for keys, values in key_data.items():
if type(values) == type({}) or str(values) in ('True', 'true'):
lista.append(keys)
True if str(values) in ('True', 'true') else key_list(values)
elif type(values) == type([]):
lista.append(keys)
lista.append(0)
key_list(values[0], lista)
else:
if lista == []:
lista.append(keys)
break
return lista
# 依據(jù)分號(hào)切割規(guī)則數(shù)據(jù)戳吝,再依次根據(jù)規(guī)則匹配databody對(duì)應(yīng)值
if type(conditionValue) == type([]):
return conditionValue
elif str(conditionValue).replace(' ', '') != '{}':
re_listdata = []
for key_data in str(conditionValue).split(";"):
re_data = databody
try:
lista = key_list(eval(key_data))
for value in lista:
re_data = re_data[value]
except KeyError:
if len(str(conditionValue).split(";")) == 1:
return False
lista.clear()
else:
re_listdata.append(re_data)
lista.clear()
re_data = re_listdata
else:
logger.warning('未找到到匹配的數(shù)據(jù)值,匹配規(guī)則=' + str(conditionValue))
re_data = False
return re_data
*主要邏輯處理介紹:
1、先判斷是否空字典“{}”贯涎,如是返回False,既后續(xù)其他邏輯返回最新的一條數(shù)據(jù)听哭;
2、依據(jù)分號(hào)" ; "切割,依據(jù)接口定義的匹配規(guī)則陆盘,依次找出入?yún)⒅邢鄳?yīng)的值且警;
2、匹配值以列表形式存儲(chǔ),多個(gè)匹配值情況后續(xù)邏輯將依據(jù)正序條件命中并響應(yīng)對(duì)應(yīng)的數(shù)據(jù)礁遣;
3斑芜、從models模型方面:
class RouteTabMock(models.Model):
"""Mock-路由URL表模型 """
re_type = [("POST", "Post"), ("GET", "Get"), ("PUT", "Put")]
APIOnOff = models.BooleanField(default=True, verbose_name='接口啟用狀態(tài)')
UrlType = models.CharField(max_length=6, choices=re_type, default="Post", verbose_name='請(qǐng)求方式')
UrlName = models.CharField(max_length=20, db_index=True, unique=True, verbose_name='接口名稱(chēng)')
CustomUrl = models.CharField(max_length=50, db_index=True, unique=True, verbose_name='url地址')
UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新時(shí)間')
ConditionValue = models.TextField(default='{}', verbose_name='條件匹配規(guī)則')
APIRequired = models.TextField(default='{}', verbose_name='字段要求規(guī)則')
Remarks = models.CharField(max_length=200, null=True, blank=True, default='-', verbose_name='備注說(shuō)明')
class Meta:
verbose_name = 'Mock/路由配置表'
verbose_name_plural = 'Mock/路由配置表'
# json格式校驗(yàn)祟霍,避免輸入錯(cuò)誤格式:字段要求規(guī)則杏头、條件匹配規(guī)則
def remarksdisplay(self):
re_data = models_fun().list_setting(self.Remarks, intdata=20)
return re_data
remarksdisplay.allow_tage = True
remarksdisplay.short_description = '備注說(shuō)明'
def __str__(self):
return '{}:[{}]'.format(self.UrlName, self.CustomUrl)
*主要邏輯處理介紹:
- 1、本次數(shù)據(jù)存儲(chǔ)引用django默認(rèn)配置的sqlite3方式,為文件形式存儲(chǔ)沸呐;主要是輕量級(jí)醇王、且便捷,如后期mock數(shù)據(jù)量過(guò)大時(shí)崭添,可替換專(zhuān)業(yè)的數(shù)據(jù)庫(kù)(mysql或oracle這些寓娩,修改setting中DATABASES字段即可)
- 2、各字段的設(shè)計(jì)呼渣、定義棘伴,class Meta為后端顯示的名稱(chēng)(也可自定義表名,不添加DB的表名:既為子項(xiàng)目名稱(chēng)+類(lèi)型)屁置;
- 3焊夸、clean既對(duì)頁(yè)面添加路由時(shí),對(duì)字段要求規(guī)則及條件匹配規(guī)則做json格式校驗(yàn)蓝角;
4阱穗、apirequired是解決后臺(tái)列表字段內(nèi)容超長(zhǎng)時(shí),列表高度被拉長(zhǎng)情況(調(diào)用后使鹅,當(dāng)輸入內(nèi)容超出設(shè)置值時(shí)揪阶,超長(zhǎng)部分將顯示"....";詳解如下圖)
class APIResponseMock(models.Model):
"""Mock-接口數(shù)據(jù)響應(yīng)表模型 """
CustomUrl = models.ForeignKey(RouteTabMock, on_delete=models.PROTECT, verbose_name='關(guān)聯(lián)接口')
UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新時(shí)間')
QueryValue = models.CharField(max_length=50, db_index=True, verbose_name='查詢(xún)值')
ResponseData = models.TextField(verbose_name='響應(yīng)信息數(shù)據(jù)')
ResponseCode = models.PositiveIntegerField(null=True, blank=True, default=200, verbose_name='接口狀態(tài)碼')
Remarks = models.CharField(max_length=200, null=True, blank=True, default='-', verbose_name='備注說(shuō)明')
def clean(self):
try:
json.loads(self.ResponseData)
except json.JSONDecodeError:
#兼容~{variable}變量形式寫(xiě)入患朱,即轉(zhuǎn)為空字典在做json檢查
try:
str_data = re.findall(r"~{(.+?)}", self.ResponseData)
replace_data = self.ResponseData.replace("~{" + str_data[0] + "}", "{}")
json.loads(replace_data)
except IndexError:
raise ValidationError('輸入的json數(shù)據(jù)變量不符合規(guī)則鲁僚,請(qǐng)檢查')
except json.JSONDecodeError:
raise ValidationError('輸入的json數(shù)據(jù)不合法,請(qǐng)檢查')
class Meta:
unique_together = ('CustomUrl', 'QueryValue',)
verbose_name = 'Mock/接口響應(yīng)數(shù)據(jù)表'
verbose_name_plural = 'Mock/接口響應(yīng)數(shù)據(jù)表'
def reprofile(self):
re_data = models_fun().list_setting(self.ResponseData)
return re_data
reprofile.allow_tage = True
reprofile.short_description = '響應(yīng)信息數(shù)據(jù)'
*主要邏輯處理介紹:
- 1麦乞、如上一個(gè)節(jié)點(diǎn)的路由配置表同理蕴茴,相同的邏輯;就不再單獨(dú)說(shuō)明了姐直;
4倦淀、admin后臺(tái)管理頁(yè)面設(shè)置方面(以路由表舉例):
class RouteTabMockAdmin(admin.ModelAdmin):
def time_seconds(self, obj):
return obj.UpdateTime.strftime("%Y-%m-%d %H:%M:%S")
# 時(shí)間格式化處理
time_seconds.admin_order_field = 'UpdateTime'
time_seconds.short_description = '更新時(shí)間'
list_display = ['id', 'UrlName', 'CustomUrl', 'UrlType', 'apirequired', 'routeprofile', 'time_seconds',
'remarksdisplay',
'APIOnOff']
list_filter = ['CustomUrl']
search_fields = ['CustomUrl', 'UrlName']
list_per_page = 15 # 分頁(yè)展示
actions_on_top = False # 頁(yè)面刪除觸發(fā)指令
actions_on_bottom = True # 頁(yè)面Action觸發(fā)指令
fieldsets = [
("接口信息", {"fields": ['UrlName', 'CustomUrl', 'UrlType']}),
("接口規(guī)則", {"fields": ['APIOnOff', 'APIRequired', 'ConditionValue', 'Remarks']})
]
admin.site.register(RouteTabMock, RouteTabMockAdmin)
五、日志模塊方面:
class Log(object):
level_relations = { 'debug': logging.DEBUG, 'info': logging.INFO, 'warning': logging.WARNING, 'error': logging.ERROR, 'crit': logging.CRITICAL} # 日志級(jí)別關(guān)系映射
def __init__(self, filename, level='info', when='D', backCount=3,
fmt='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s'):
self.logger = logging.getLogger(filename)
format_str = logging.Formatter(fmt) # 設(shè)置日志格式
self.logger.setLevel(self.level_relations.get(level)) # 設(shè)置日志級(jí)別
sh = logging.StreamHandler() # 往屏幕上輸出
sh.setFormatter(format_str) # 設(shè)置屏幕上顯示的格式
th = handlers.TimedRotatingFileHandler(filename=filename, when=when, backupCount=backCount,
encoding='utf-8') # 往文件里寫(xiě)入#指定間隔時(shí)間自動(dòng)生成文件的處理器
# 實(shí)例化TimedRotatingFileHandler
# interval是時(shí)間間隔声畏,backupCount是備份文件的個(gè)數(shù)撞叽,如果超過(guò)這個(gè)個(gè)數(shù)姻成,就會(huì)自動(dòng)刪除,when是間隔的時(shí)間單位愿棋,單位有以下幾種:
# S 秒\M 分\H 小時(shí)\D 天\W 每星期(interval==0時(shí)代表星期一)\midnight 每天凌晨
th.setFormatter(format_str) # 設(shè)置文件里寫(xiě)入的格式
self.logger.addHandler(sh) # 把對(duì)象加到logger里
self.logger.addHandler(th)
def create_file():
filepath = os.path.realpath(__file__)
output_dir = os.path.abspath(os.path.join(filepath, "../../logs"))
if not os.path.exists(output_dir):
os.makedirs(output_dir)
log_name = '{}.log'.format(time.strftime('%Y-%m-%d'))
filename = os.path.join(output_dir, log_name)
return filename
filename = create_file()
log = Log(filename, level='debug')
logger = log.logger
*主要邏輯處理介紹:
1科展、引用的話(huà),直接logger.info('輸入打印內(nèi)容')或者其他等級(jí)的即可糠雨。簡(jiǎn)單實(shí)用才睹,可收藏為敬;
2甘邀、此日志引用的是logging庫(kù)琅攘,百度一下一大推;能力有限松邪,寫(xiě)不出那么強(qiáng)大的坞琴。是直接復(fù)制過(guò)來(lái)改一改逗抑,達(dá)到我想要的效果邮府。
**備注:其他測(cè)試接口頁(yè)面邏輯流羞酗、自定義接口邏輯流、簡(jiǎn)易封裝欺嗤、及瑣碎配置就不再單獨(dú)列舉了;內(nèi)容太多,估計(jì)看都能看煩.
六吆玖、結(jié)束收尾語(yǔ):
- 1沾乘、這個(gè)小項(xiàng)目翅阵,寫(xiě)寫(xiě)停停倒騰了一個(gè)月也總算勉強(qiáng)的完成了滥崩;目前準(zhǔn)備在實(shí)際測(cè)試項(xiàng)目上應(yīng)用钙皮,將能滿(mǎn)足一些測(cè)試場(chǎng)景的需求以及提高測(cè)試效率;
- 2慌烧、因還有很多邏輯以及細(xì)節(jié)就不再單獨(dú)拿出來(lái)說(shuō)明了,具體都有對(duì)應(yīng)的注釋?zhuān)勺孕辛私獠榭?
- 3汹粤、目前構(gòu)思或者代碼中應(yīng)該還有很多優(yōu)化,會(huì)在下個(gè)版本進(jìn)行優(yōu)化芹壕;
- 4、下次版本新增功能點(diǎn):
--- 1)睁壁、接口支持表單形式潘明;
--- 2)、接口請(qǐng)求類(lèi)型支持:PUT牲阁、PATCH备燃、DELETE并齐;
--- 3)、接口測(cè)試頁(yè)面依據(jù)postman參考重新設(shè)計(jì)頁(yè)面和功能测垛;
--- 4)、接口不滿(mǎn)足規(guī)則時(shí)锯七,返回的錯(cuò)誤信息支持自定義結(jié)構(gòu)
mock系統(tǒng)使用說(shuō)明pdf鏈接: https://pan.baidu.com/s/1JV8O66WLJ4q8Oh8UdsC-_A?pwd=8888