专栏文章 必看!利用装饰器,帮你自动处理异常并优雅实现重跑 case

fishfish-yu · 2020年12月15日 · 最后由 Thirty-Thirty 回复于 2020年12月17日 · 4112 次阅读

此文章来源于项目官方公众号:“AirtestProject”
版权声明:允许转载,但转载必须保留原链接;请勿用作商业或者非法用途

前言

同学们是否遇到过这些场景?

case 因为网络波动而跑失败了?在最后时刻,系统弹出了一条骚扰短信遮挡了关键位置,脚本硬是没跑过去?又或者因为手机上的闹铃响起来,让脚本运行意外失败了?

相信很多同学都会想到用代码去处理这些异常,因为我们总不能时时刻刻盯着脚本运行,然后在出现异常时手动处理掉,但是代码可以帮我们实现自动处理异常。

但也有很多同学,因为处理异常复制粘贴了大量代码,用例脚本里面随处可见处理异常的代码。所以今天我们将教大家在不改动原有用例脚本的情况下,利用装饰器优雅地处理异常并实现 case 重试操作,同学们千万不能错过呀~

(PS:已会用装饰器的同学,可以直接拉到文末查看实操应用哦~)

1.装饰器的定义

装饰器可以在不改动原函数代码的情况下,添加其原本没有的功能。简单点说,就是修改其它函数的功能的函数。通过使用装饰器,我们可以让一个函数的功能变的更加强大,还可以让我们的代码更加简短整洁。

2.用简单的示例入门装饰器

首先我们定义了一个 test 函数,这个函数的作用就是打印出一句话:这是一个平平无奇的函数。

函数定义如下:

def test():
    print("这是一个平平无奇的函数")

test()

>>>输出:
# 这是一个平平无奇的函数

如果需要 print 两次,大家会怎么实现呢?有同学说,直接复制一行 print,也有同学说,直接在函数中增加一个 for 循环实现,其实都是可以的:

# 方式一
def test():
    print("这是一个平平无奇的函数")
    print("这是一个平平无奇的函数")

# 方式二
def test():
    for i in range(2):
        print("这是一个平平无奇的函数")

test()

>>>输出:
# 这是一个平平无奇的函数
# 这是一个平平无奇的函数

假设我们要重复 print 两次,但又不想修改原函数的代码,我们要怎么做呢?
没错啦,我们可以定义装饰器来实现:

# 原函数
def test():
    print("这是一个平平无奇的函数")

# 定义1个装饰器
def loop(func):
    def wrapper(*args, **kw):
        for i in range(2):
            func(*args, **kw)
    return wrapper

可以看到,我们并没有改变原有函数,但是我们用装饰器改变了原函数的功能,用上述装饰器帮助我们重复运行 2 次原函数。

那么接下来,我们继续来看看如何使用装饰器:

方式一:不使用语法糖 @ 符号的调用方法:
loop(test)()

>>>输出:
# 这是一个平平无奇的函数
# 这是一个平平无奇的函数
方式二:使用语法糖 @ 符号的调用方法:
@loop
def test():
    print("这是一个平平无奇的函数")

test()

>>>输出:
# 这是一个平平无奇的函数
# 这是一个平平无奇的函数

实际上装饰器可以多次叠加,比如:

@loop
@loop
def test():
    print("这是一个平平无奇的函数")

test()

>>>输出:
# 这是一个平平无奇的函数
# 这是一个平平无奇的函数
# 这是一个平平无奇的函数
# 这是一个平平无奇的函数

有一点值得注意的地方,同学们猜一猜,如果我们叠加三次装饰器,会出现什么结果呢?(看到这里的同学可以先思考一下,然后再动手跑一下看看是不是你预想的结果)

@loop
@loop
@loop
def test():
    print("这是一个平平无奇的函数")

test()

>>>输出:
# 这是一个平平无奇的函数
# 这是一个平平无奇的函数
# 这是一个平平无奇的函数
# 这是一个平平无奇的函数
# 这是一个平平无奇的函数
# 这是一个平平无奇的函数
# 这是一个平平无奇的函数
# 这是一个平平无奇的函数

可以看到,输出结果并不是 6 次,而是 8 次,为什么呢?我们用普通的调用方式来分析一下就知道了:

上述代码就相当于:loop(loop(loop(test)))(),真正调用的是最外面那层。
当你调用多次时,装饰器将函数连同装饰器自身作为参数一起循环执行了,所以实际执行次数为 2×(2×2),即 8 次。

如果你需要定义具体的执行次数,可以直接在装饰器中传入参数实现,具体如下:

def loop(times):
    def decorator(func):
        def wrapper(*args, **kw):
            for i in range(times):
                func(i, **kw)
        return wrapper
    return decorator

@loop(6)
def test(num):
    print(f"这是一个平平无奇的函数, 第{num+1}次运行")

test()

输出:
# 这是一个平平无奇的函数, 第1次运行
# 这是一个平平无奇的函数, 第2次运行
# 这是一个平平无奇的函数, 第3次运行
# 这是一个平平无奇的函数, 第4次运行
# 这是一个平平无奇的函数, 第5次运行
# 这是一个平平无奇的函数, 第6次运行

3.应用装饰器实现 Airtest 脚本失败自动重跑

举个例子,你正在跑一个自动化测试脚本,突然间设备的闹钟响了,而刚好闹钟的界面遮挡住了你要操作的那个按钮,最终导致脚本运行失败了。没办法,你只能关掉闹钟再重新运行一次脚本。

在这个测试场景中,我们如何应用装饰器帮助我们解决闹钟异常,然后重跑我们的测试用例呢?答案如下:

我们新建了 1 个装饰器,这个装饰器会尝试运行我们的用例,如果遇到闹钟遮挡,则会自动处理掉这个闹钟,然后再重跑用例,最终还会打印出重跑结果,效果如下:

可以看到,我们在没有改变用例函数的情况下,增加了装饰器,使得原函数有了处理异常和重跑的功能,这就是装饰器函数的魅力所在。

最后借用某位不知名大佬说的话:别妄想着看看就能理解,动手跑一跑代码,一切都豁然开朗了。


Airtest 官网:airtest.netease.com/
Airtest 教程官网:airtest.doc.io.netease.com/
搭建企业私有云服务:airlab.163.com/b2b

官方答疑 Q 群:654700783

呀~这么认真都看到这里啦,帮忙点击左下角的爱心,给我点个赞支持一下把,灰常感谢~

共收到 4 条回复 时间 点赞

这个思路好,但是有个问题:需要将所有的异常都捕获到,每捕获到一个,才能进行相对应的异常处理,说到底还是依赖经验,但是总是不会将所有的异常都捕获到的,会有遗漏;
我这边有个思路,不知道对不对,装饰器捕获到异常后,进行截屏操作,接着进行环境初始化操作,再运行脚本,之后有什么异常再往上加。

江涛依旧 回复

初始化操作,这里面会有很多情况吧,比如这个闹钟,如果第一次见,可能就不会有什么好办法初始化

我去催饭 回复

我的初始化操作是:杀后台,确实也不清楚是否会将闹钟杀掉。😂

稳定可靠的测试环境很重要。
要把异常场景的处理当做环境搭建的一部分。
搭建之前就应该考虑好大部分可能出现的异常场景并加以处理,比如删除不必要的闹钟。
然后在使用环境的过程中,逐渐加入其他没能考虑到的异常场景,比如如非必要则关闭短信功能。
最终就会得到稳定可靠的测试环境。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册