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

本篇旨在通过一个基础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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356

推荐阅读更多精彩内容