使用装饰器设计模式改进测试代码

阿凡达2018-08-10 16:54

一. 测试代码的一些常见场景

1. 截屏与打印日志等

        熟悉自动化测试的同学经常会遇到这样的一种情形,需要在测试用例的一些执行步骤前后添加一些记录跟踪,比如截屏、打印时间戳、日志等,常见的代码可能是这样的:

def demo_test():
    print "demo test start"
    save_screenshot()
    do_something()   #测试逻辑动作
    verify_result()   #测试结果校验  
    logging.debug("balabala")   
    print "demo test end"

        随着测试中这种通用的辅助代码越来越多,你会发现整个测试方法中真正的业务逻辑测试代码逐渐被淹没,这从代码可读性上是件非常糟糕的事情,你要经过一些和当前业务逻辑测试没多大关系的代码后才能看到正在的业务逻辑测试代码的主体;另外,这种测试辅助代码在整个测试工程中是高频率出现的,甚至每个测试逻辑动作的前后都会发生,比如我个人之前做Android的UI自动化测试的时候,为了方便后期问题的诊断,在每个动作的前后放置了截屏动作,如果还是按照上述的编写方法,那么一旦我需要添加一个测试辅助代码,就会需要修改整个测试工程中的每个动作方法,这听着就是件让人崩溃的事情。

2. Web登录与Request参数校验

        在测试工作中,有时需要开发一些小型Web平台,方便某一方面的测试流程与技术在更多群体中使用。Web系统势必要处理登录与参数校验等,常见的代码可能是这样:

def add_user(request):
    '''这个request示例方法主要模拟处理一个user的添加行为'''
    if is_authed():
        do_something()  #业务逻辑代码
        return render_to_response("some.html")
    else:
        return render_to_response("login.html")  #没有登录过,跳转到登录页面

        Web中间层几乎都是这种request处理,很显然每个都需要这样登录校验处理,那么这种if/else存在大量重复,特别是校验失败后跳转到登录界面的代码行,千遍一律。同样的情况也出现在对request参数进行校验的操作上,在上述代码里的登录校验成功后,我们加入请求参数的校验处理代码:

def add_user(request):
    if is_authed():
        rh=RequestHandler(request,"name", "age")   #RequestHandler类负责处理对request参数名的合法性进行校验,以及得到每个参数的相应值
        if rh.veriy_params():   #校验request的传递参数名
            name, age=rh.get_params()   #得到request参数的值
            do_something()  #业务逻辑代码
            return render_to_response("some.html")
        else:
            return render_to_response("error.html", {"error_info":"传递的参数不正确"}) 
    else:
        return render_to_response("login.html")  #没有登录过,跳转到登录页面

        有没有感觉很崩溃,什么业务逻辑代码都还没有开始写,两层if/else代码嵌套就在那了,然后你就看见满屏的request请求都套这么两个东东,试想再多个其它什么校验需求,那估计真要无语了。

二. 使用装饰器设计模式改进测试代码

        下面我们通过装饰器设计模式来重构我们的代码,首先来看截屏与打印日志重构后的代码如下:

def test_trace(operate_func):
    '''通用测试辅助装饰器函数'''
    def _test_trace(*args, **kwargs):
        print "%s start" % operate_func.__name__
        save_screenshot()
        operate_func(*args, **kwargs)
        logging.debug("balabala")
        print "%s end" % operate_func.__name__
    return _test_trace

@test_trace
def demo_test():
    do_something()   #测试逻辑动作
    verify_result()   #测试结果校验

        可以看到测试函数主体只有测试业务的代码,从而提高了可读性和可维护性。
        登录校验的例子和上述测试函数重构方式类似,这里就不再累述,下面主要来看web的request参数校验的例子,与之上面的例子区别是这里的装饰器函数需要携带参数,改造之后的代码如下:

def verify_request_params(*expect_request_params):
    '''对request中参数进行校验的装饰器函数,expect_request_params参数是期望参数列表'''
    def _verify_request_params(operate_func):
        def decorators(request):
            rh=RequestHandler(request, *expect_request_params)
            if rh.verify_params():
                params_value=rh.get_params_value()
                return operate_func(request, *params_value)
            else:
                return rh.redirect_to_error_page()
        return decorators
    return _request_handler

@verify_request_params("name", "age")
@auth
def add_user(request, , *params_value):
     name, age=rh.get_params()   #得到request参数的值
     do_something()  #业务逻辑代码
     return render_to_response("some.html")

        如果大多数的request处理函数以后还需要添加新的校验规则,那么只需要编写对应校验函数,然后放置在request处理函数上方即可,处理函数的主体业务逻辑代码将不会有什么改变。

三. 后语

        有人说测试代码嘛,又不是产品代码,不用那么较真,能用就行,多点Ctrl+C和Ctrl+V没有多大关系。的确,在项目紧,任务重的情况,能快速完成任务是第一目标,但作为新时代的测试工程师,我们的测试代码要向开发的代码质量看齐,毕竟我们有时还用静态代码检查工具审核开发代码,严以待人必先严以律己。多思考,多练习,多重构。
        本篇的例子采用了Pyhon语言和与之对应的Web开发框架Django,可能更多测试同学熟悉的是Java语言,没有关系,设计模式是一种通用的代码设计方法,和语言相关性比较弱,Java的例子可以参考《HeadFirst设计模式》或《大话设计模式》两本畅销书。另外本篇没有讲述装饰器模式的相关基础概念,有兴趣的同学可以参考上述两本畅销书。


网易云新用户大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者葛峰授权发布。