9.装饰器及 log 技术

一、装饰器

  • 装饰器本质:一个函数,该函数用来处理其他函数,使其他函数在不修改代码前提下增加额外的功能

    • 装饰器的返回值可以是一个函数对象
  • 使用场景:插入日志,事务处理,缓存,权限校验等

  • 可以抽离出大量与函数功能本身无关的雷同代码,并且可以继续复用

装饰器的使用

该测试用例执行,服务器的响应时间是多少,我们怎么实现该代码

1
2
3
def foo():
print('执行测试用例')
time.sleep(1

1. 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
# 自动化测试场景
import time
def foo():
print('执行测试用例')
time.sleep(1)

def show_time(func):
start_time = time.time() # 开始时间
func() # 函数调用
end_time = time.time() # 结束时间
print('服务器响应时间: ',end_timestart_time)

show_time(foo)

2. 使用 - 装饰器方案改造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import time
def show_time(func):
def inner():
start_time = time.time() # 开始时间
func() # 函数调用
end_time = time.time() # 结束时间
print('服务器响应时间: ',end_timestart_time)
return inner # 返回函数对象

def foo():
print('执行测试用例')
time.sleep(1)

foo=show_time(foo)
foo()
  • foo=show_time(foo) foo()
  • 但是每次执行一次都得重新赋值一次,麻烦,不够优雅
  • python针对这个情况,提供一个完美的解决方案:语法糖 @
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 自动化测试场景
import time

def show_time(func):
def inner():
start_time = time.time() # 开始时间
func() # 函数调用
end_time = time.time() # 结束时间
print('服务器响应时间: ',end_timestart_time)
return inner # 返回函数对象

@show_time # 语法糖 等价 foo=show_time(foo)
def foo():
print('执行测试用例')
time.sleep(1)

foo()


#输出结果
执行测试用例
服务器响应时间: 1.0

3. 带参数的函数装饰器

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
# 自动化测试场景
import time

def some_body_run(name):
def show_time(func):
def inner():
start_time = time.time() # 开始时间
func()#函数调用
end_time = time.time() # 结束时间
print('服务器响应时间:',end_time-start_time)
print('执行者:',name)
return inner # 返回函数对象
return show_time

@some_body_run('tom') # 语法糖 等价 foo=some_body_run('tom')
def foo():
print('执行测试用例')
time.sleep(1)

foo()


# 输出结果
执行测试用例
服务器响应时间: 1.0
执行者: tom

二、 log 技术

1. 日志级别

Fatal -> Error -> Warning -> Info -> Debug

级别描述
DEBUG细节信息,仅当诊断问题时适用。
INFO确认程序按预期运行
WARNING表明有已经或即将发生的意外(例如:磁盘空间不足)。程序仍按预期进行
ERROR由于严重的问题,程序的某些功能已经不能正常执行
CRITICAL严重的错误,表明程序已不能继续执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def logger():
'''
logging.basicConfig函数各参数:
filename:指定日志文件名;
filemode:和file函数意义相同,指定日志文件的打开模式,'w'或者'a';
format:指定输出的格式和内容,format可以输出很多有用的信息,
level logging.INFO,
'''
# 调用配置函数
logging.basicConfig(format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s:%(message)s',
filename=f'../logs/{datetime.datetime.now().strftime("%Y_%m_%d %H-%M-%S")}.log',
level=logging.INFO,
filemode='a'
)
return logging
  • logging.basicConfig : 函数各参数
  • filename : 指定日志文件名
  • level : 监控级别
  • filemode : 和 file 函数意义相同,指定日志文件的打开模式, “w” 或者 “a”
  • format : 指定输出的格式和内容,format 可以输出很多有用的信息
参数作用
%(levelno)s打印日志级别的数值
%(levelname)s打印日志级别的名称
%(pathname)s打印当前执行程序的路径,其实就 是sys.argv[0]
%(filename)s打印当前执行程序名
%(funcName)s打印日志的当前函数
%(lineno)d打印日志的当前行号
%(asctime)s打印日志的时间
%(thread)d打印线程ID
% (threadName)s打印线程名称
%(process)d打印进程ID

代码调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from tools.logBasic import logger

log = logger() # 调用自定义封装的log函数

class TestLogin:
# [({},{}),({},{})]
@pytest.mark.parametrize('inData,respData', get_yaml_data('../data/data.yaml'))
def test_login(self, inData, respData):
# 1- 调用--封装模块
res = Login().login(inData, getToken=False)
print(res)
log.info('------##############------------')
# 2- 断言 实际结果与预期的结果进行比较
try:
assert res['msg'] == respData['msg']
except Exception as err:
log.error(err)
raise err # 抛出异常