Selenium 100 行代码打造关键字驱动的 ui 自动化测试框架

匿名 · 2018年07月26日 · 最后由 重来看雨 回复于 2021年05月08日 · 2946 次阅读

本篇旨在通过一个基础 demo 的实现讲解,提供一个关键字驱动框架实现方案的思路指引。

为什么要实现关键字驱动框架?

其实之前已经介绍过纯代码方式的自动化框架实现,那为什么还要去进行关键字框架的实现呢?

纯代码方式的话,门槛较高,用户需要先掌握对应的语法才可以进行编写,如果团队里缺乏这样的角色,自动化测试就会难以进行,因此如果我们做成关键字的形式,那么用例都只是关键字的拼装,就会大大减少学习门槛;同时,基于关键字的用例风格较为统一,方便用例阅读和管理。

当然市面上其实已经有一些成熟的关键字驱动框架了,较为人知的比如 robotframework,为什么不直接采用 rf 框架呢?

rf 框架的话,安装较为繁琐复杂,而且不方便扩展成 web 平台;编写用例依赖 Ride 不太方便管理和传阅;另一点的话,就是我个人希望重复造个轮子来感受一下(造轮子也是个很重要的学习过程)。

定义用例的形式

在我们实现这个框架之前,我们先来想一下这个用例的形式应该是什么?

传统的代码写用例,基本都是采用 po 模式,用例继承 maincase,maincase 继承 unittest.testcase,但无论如何,一个用例流程下来,不考虑复杂情况的话,无非只需要三个元素:动作,对象,以及参数;

这里我直接给出一个简单的用例构造,

cases =[
[
    {'name': 'test_baidu'},
    {'action': "打开网址", 'parmeters':['http://www.baidu.com/']},
    {'action':"输入", 'parmeters':["百度搜索框",'100行代码打造关键字驱动的ui自动化测试框架']},
    {'action': "点击", 'parmeters': ["百度搜索按钮"]},
    {'validate': "页面文本包含", 'parmeters': ["百度"]},
],

]

当然这个是抽象成 py 对象的方式展现的,在实际的工作里,你完全可以写的更加 “脱离代码化”,例如放到 yml 文件里,然后通过解析函数解析成以上构造,这样用例看起来就和代码完全不沾边了;这里为了不让情况变得复杂,所以直接采用 py 对象的方式来处理用例。

在用例的定义里,每个用例都是一个列表,由一个个字典对象组成;第一个字典存储该用例的名称,往下每个字典存储动作(action)或者是校验点(validate),而每个 action 和 validate 里,都含有一个 parmeters 的列表存储他们需要的参数。

因此不难看出,如果我们想要这个用例可以运行,那么可以把每个 action 或 validate 当做一个函数,而每个 parmeters 当做这个函数的参数,这样顺序的执行下来,就可以完成我们每个动作以及最后的校验。

那么具体我们要怎么做呢?

关键字映射和反射

前面说了,这个东西我最终是希望成为一个 web 平台的,因此在这个 demo 的实现里,我新建了一个叫"keyword_db.py"的文件模拟关键字映射数据库,这个文件用以存储中文的动作(action)和 校验点(validate)关键字,和这个关键字实际对应的函数名称,来实现动作关键字和函数的映射,这个文件的内容大概是这样:

keyword = {
    "输入": 'input',
    "点击": 'click',
    "打开网址": 'open_url',
    "等待":'wait_time',
    "元素文本包含": 'element_text_has',
    "页面文本包含": 'page_text_has',


}

字典的 key 实际上就是中文的动作或者校验点,而对应的 value 就是实际的函数名称,这样我们初步的映射就完成了,但是仅仅这样是无法让用例动起来的,我们还需要进行处理让程序处理用例的时候,可以执行到关键字对应的函数。

第一步,写出你关键字对应的函数:

既然要执行关键字对应的函数,那么第一步肯定是需要存在这么一个函数;我新建了一个"function.py"的文件用于存储我们的关键字对应的函数,在这个文件里写出实现函数的具体代码,截取其中的一些展示一下:

def find_element(element,driver):
    webelement = driver.find_element(*element)
    return webelement

def open_url(url,driver):
    driver.get(url)

def input(element,str,driver):
    find_element(element, driver).send_keys(str)

def page_text_has(str, driver):
    page_text = driver.page_source
    return unittest.TestCase().assertIn(str,page_text,"页面文本不包含预期值!")

这样,我们的第一步就完成了。

第二步,通过反射拿到函数地址:

当我们解析了用例,拿到关键字和函数的映射关系后,例如,通过解析关键字"输入",在 keyworddb 里找到了"输入"对应的函数名称 input(注意,这里其实是拿到了 input 这个字符串而不是真正的函数地址),这时候我们通过反射机制,在 function.py 里通过 getattr() 方法查找 input 这个字符串,可以拿到 input 这个字符串对应的 function 地址,我们就可以存下这个地址用来执行 input 这个函数了。

讲起来可能比较绕口,这里举一个例子,比如有一个 A.py 的模块里,有一个方法 printA:

def printA():
    print("AAAAAAAAA")

我们再新建一个 B.py 的模块,在里面用 getattr() 方法拿到 A 里的这个 printA 这个方法执行:

import A
func = getattr(A, "printA")
func()

输出结果是"AAAAAAAAA",可以看到,我们没有直接执行 printA 方法,而是通过在 A 模块里查找"printA"这个字符串对应的函数,存到 func 变量里执行,一样实现了执行 printA 函数,这就是反射机制。

回到我们的 demo 里来,通过反射机制在 function 模块里查找对应的函数名称,我们就把真正的函数地址存了起来,和上面反射的例子不同的是,我们的函数大多需要一个或者多个参数,如何处理参数呢?

元素仓库和函数参数处理

既然我们选择了关键字驱动,自然在元素的书写上肯定不能还是纯代码式的 find_element_by_XXX(xxx) 的方式,这里我建了一个"element_db.py"的文件模拟数据库存储元素作为元素仓库。

大概内容如下:

element = {
    '百度搜索框':('By.ID', 'kw'),
    '百度搜索按钮': ('By.ID', 'su'),
}

可以看出和关键字映射是一个套路,就是把中文的元素名称和一个查找关系做映射,key 是中文的元素名称,value 是一个元祖,包含查找方式和值。

这里有个问题,看到前面的用例构造可以看出,我们会用中文的元素名称作为函数的参数,这样就需要对参数进行处理,里面存在一个逻辑,如果这个参数是元素类型的参数,就需要先从元素仓库找出他的对应查找方式,再转换为真正的 element 对象作为函数的参数,而其他类型的参数就直接用原值,无需转换。

如何确定哪个参数是元素类型的参数呢?我们通过 inspect.signature() 方法,可以拿到函数的参数名称,例如,前面我们写了一个函数:

def find_element(element,driver):
    webelement = driver.find_element(*element)
    return webelement

那么执行以下代码,就可以拿到函数的参数名称:

parmeters = inspect.signature(find_element).parameters.values()

结果是 [element,driver],之后我们通过对参数名称进行分析,就可以知道哪些参数需要转换,哪些不用(当然这么做对函数参数的书写规则就要有要求)。

以上动作我们既拿到了函数地址,又处理了对应的函数参数,是不是就可以执行了呢?

测试框架和用例工厂

当然你直接执行是没问题的,但是如果不用测试框架处理,就无法得到测试框架的便利性例如自动生成的测试报告等等。

我们选用了 unittest 框架作为示例,unittest 框架写用例一般就是三部分,setup 准备,test_开头的方法进入测试步骤,然后结束用 teardown 清理,怎么把这些融入到我们的用例里呢?

这里我提出了一个用例工厂的概念,实际上是个方法,主要负责对我们从用例解析出的函数和对应参数进行包装,产生一个又一个符合 unittest 格式的用例。

这个也不复杂,主要使用了 type() 这个方法,type() 很多人可能用它去查看变量的数据类型,但其实他还可以创造类型比如创造一个类,例如如下代码:

testcase = type("TESTCASE",(unittest.TestCase,),{'tearDown':teardown,'setUp':setup},)

这段代码其实就是创造了一个叫做 TESTCASE 的类,继承自 unittest.TestCase,类里有两个事先定义好的方法 teardown 和 setup。

我们把这个类用 unittest 中 testsuite 的 add 方法处理一下,就可以得到一个标准的 unittest 的 testcase 了。

以上就是我们的全部内容,至此,这个关键字驱动的 ui 自动化测试框架 demo 就打造好了。

一些说明

这个 demo 仅仅是个思路指引,当然你也可以直接用,不过还有很多没有完善和很死板的地方需要去修补,之后 ok 了,我会再放上用这个框架为核心打造的 web 平台版。

想运行这个 demon,你需要:

py3 的环境

selenium 库

HTMLTestRunner_PY3(放到 py3 目录下的 lib 目录里)
适合你 chrome 版本的 chromedriver(放到 py3 的根目录下)

在 case 模块编写用例,执行 process 模块执行用例,测试报告在 report 目录下。

Git 地址

https://github.com/icesword0760/uitest-keyword

转载须注明出处

共收到 3 条回复 时间 点赞

太感谢楼主关于 type() 的使用分享了,最近用 unittest 写关键字驱动的时候也遇到了方法重复调用的问题,用 type() 解决了👍 感谢

请问在 web 平台上编写关键字用例后,如何调试。

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