随着敏捷的普及BDD已经越来越受到大家的认识了,BDD目前比较常用的技术是Cucumber框架,而在Python语言,Cucumber主要包括lettuce、Behave和Freshen。而lettuce是大家用的比较多的,但是要测试APP或者WEB就需要在lettuce上安装一个lub插件。由于BDD、Cucumber、lettuce技术在网上的资料非常多,所以本文不进行介绍,本文主要介绍Appium+Python+lub,而对于如何使用lub测试WEB,请参看http://www.diggerplus.org/archives/3623。
要使用lub,必须进行如下操作:
- 下载Python 2.X版本(建议2.7,lettuce不支持Python 3.X)
- 在https://github.com/qddegtya/Lub下载Lub
- 进入文件夹安装相关插件“>pip install -r requirements.txt”
- 安装lub,同时也安装了lettuce:“>python setup.py install”
- 在工作区域运行命令“>lub -i gen”
经过上述的操作在工作区域生成了如图一的三个文件夹。
图一 lub工作区域的文件夹
其中:
- apptc用于测试APP程序;
- webtc用于测试WEB程序。
不管在apptc还是webpc下都包含以下三个文件夹
- features:该文件夹下的all.feature为特性描述文件;
- step_definition:该文件夹下的steps.py为步骤文件;
- support:该文件夹下的terrain.py为全局控制。
在这里我们先来介绍一下我们这个被测APP产品。
图二:登录首页 图三:注册页
图四:登录成功页
图二为登录首页,当输入用户名和密码后,点击【登录】按钮,如果用户名密码正确进入图四登录成功页面,否则弹出错误消息;点击【进行注册】按钮进入图三进行注册;点击【清除】按钮,清除数据库中的所有数据,这个按钮是为测试而临时设置的,正式产品中将会取消。
图三为注册页,当输入用户名和密码后,点击【注册】按钮,当输入的用户名在数据库中不存在,注册成功,返回图二的登录页面,否则弹出错误消息。
图四为登录成功页,当在图二中输入正确的用户名和密码后,进入这个页面,这里的“Hello world”将变为“Welcome ”+用户名。
首先让我们来看一下描述文件,features文件夹下的all.feature文件。
Feature: testcase Scenario: Demo4 APP Give click at "id" for "com.example.demo4:id/button3" to clear database And click at "id" for "com.example.demo4:id/button2" to prepare rangiest And at "id" for "com.example.demo4:id/username1" enter username "" to clear rangiest username edit field And at "id" for "com.example.demo4:id/username1" enter username "Jerry" And at "id" for "com.example.demo4:id/password1" enter password "" to clear rangiest password edit field And at "id" for "com.example.demo4:id/password1" enter password "123456" Then I finish to regiest,then click at "id" for "com.example.demo4:id/button11" Given at "id" for "com.example.demo4:id/username" enter username "" to clear login username edit field And at "id" for "com.example.demo4:id/username" enter username "Jerry" And at "id" for "com.example.demo4:id/password" enter password "" to clear login password edit field And at "id" for "com.example.demo4:id/password" enter password "123456" And click at "id" for "com.example.demo4:id/button1" Then I have been logined,and can click at "id" for "com.example.demo4:id/logout" click at "id" for "com.example.demo4:id/button3" to clear database fannily
其中Feature、Scenario、Given、Then、And为BDD的关键字,包括以下几个:
- Given假设
- When时间
- Then下一步
- And与
- Feature特性:
- Background背景:
- Scenario场合大纲:
然后我们来看一下support文件夹下的下的terrain.py文件。
# -*- coding:utf-8 -*- # controls from lettuce import * from nose.tools import assert_equals from appium import webdriver @before.each_scenario def startupdriver(scenario): world.desired_caps = {} world.desired_caps['platformName'] = 'Android' world.desired_caps['platformVersion'] = '6.0' world.desired_caps['deviceName'] = 'Android Emulator' world.desired_caps['appPackage'] = 'com.example.demo4' world.desired_caps['appActivity'] = '.MainActivity' world.driver = webdriver.Remote('http://localhost:4723/wd/hub', world.desired_caps)
world.XXX:表示这个变量是个全局变量,他可以跨越terrain.py文件和step_definition文件夹下的下的steps.py文件进行参数传递。
在这里@before.each_scenario表示在每个场景执行前进行,有点像JUnit框架中的@before,或者更像TestNG,因为它包含以下一系列标签:
- @before.all
- @after.all
- @before.each_feature
- @after.each_feature
- @before.each_scenario
- @after.each_scenario
- @before.each_outline
- @after.each_outline
- @before.each_step
- @after.each_step
最后我们来看一下step_definition文件夹下的下的steps.py文件。
# -*- coding:utf-8 -*- # step definitions from lettuce import * from nose.tools import assert_equals from appium import webdriver from Lub.webtools.actions import * @step(u'click at "(.*)" for "(.*)" to clear database') def exemplify_world(step,typename,typekey): lubclick(lubfind(world.driver,typename,typekey)) @step(u'click at "(.*)" for "(.*)" to prepare regiest') def exemplify_world(step,typename,typekey): lubclick(lubfind(world.driver,typename,typekey)) @step(u'at "(.*)" for "(.*)" enter username (.*) to clear regiest username edit field') def enter_username(step,typename,typekey,typeval): lubinput(lubfind(world.driver,typename,typekey),typeval) @step(u'at "(.*)" for "(.*)" enter username "(.*)"') def enter_username(step,typename,typekey,typeval): lubinput(lubfind(world.driver,typename,typekey),typeval) @step(u'at "(.*)" for "(.*)" enter password "(.*)" to clear regiest password edit field') def enter_username(step,typename,typekey,typeval): lubinput(lubfind(world.driver,typename,typekey),typeval) @step(u'at "(.*)" for "(.*)" enter password "(.*)"') def enter_username(step,typename,typekey,typeval): lubinput(lubfind(world.driver,typename,typekey),typeval) @step(r'I finish to regiest,then click at "(.*)" for "(.*)"') def exemplify_world(step,typename,typekey): lubclick(lubfind(world.driver,typename,typekey)) @step(u'at "(.*)" for "(.*)" enter username "(.*)" to clear login username edit field') def enter_username(step,typename,typekey,typeval): lubinput(lubfind(world.driver,typename,typekey),typeval) @step(u'at "(.*)" for "(.*)" enter username "(.*)"') def enter_username(step,typename,typekey,typeval): lubinput(lubfind(world.driver,typename,typekey),typeval) @step(u'at "(.*)" for "(.*)" enter password "(.*)" to clear login password edit field') def enter_password(step,typename,typekey,typeval): lubinput(lubfind(world.driver,typename,typekey),typeval) @step(u'at "(.*)" for "(.*)" enter password "(.*)"') def enter_password(step,typename,typekey,typeval): lubinput(lubfind(world.driver,typename,typekey),typeval) @step(u'click at "(.*)" for "(.*)"') def login(step,typename,typekey): lubclick(lubfind(world.driver,typename,typekey)) @step(u'I have been logined,and can click at "(.*)" for "(.*)"') def login(step,typename,typekey): lubclick(lubfind(world.driver,typename,typekey)) @step(r'at "(.*)" for "(.*)" to clear database fannily') def exemplify_world(step,typename,typekey): lubclick(lubfind(world.driver,typename,typekey))
@step(u'clickat "(.*)" for "(.*)" to prepare regiest')
u后引号中的为feature中对应的一个Give、And或Then语句,在语句中用(.*)代表变量,在函数中用一个变量代替,比如:
@step(u'click at "(.*)" for "(.*)" to prepare regiest') def exemplify_world(step,typename,typekey):
第一个(.*)代表变量typename(比如id),第二个(.*)代表变量typekey(比如com.example.demo4:id/button2)
lubclick(lubfind(world.driver,typename,typekey))
代表在typename下寻找值为,typekey,然后进行点击操作
lubinput(lubfind(world.driver,typename,typekey),typeval)
代表在typename下寻找值为,typekey,然后在这个控件下输typeval
关于这方面的资料国内非常奇缺,大家可以到http://lettuce.it/网上去了解详细全面的资料。
在这里大家是否觉得all.feature文件不太适合专业的业务人员来书写,因为它与UI进行了捆绑。对的,大家如果想到了这一点是对BDD有了比较深刻的了解,我之所以这样写是为了让大家对lub有个深刻的印象,现在我们来改造一下这个文件。
Feature: testcase Scenario: Demo4 APP Give click reset button to clear database And click register button prepare register And clear register username edit field And enter username "Jerry" at username register field And clear register password edit field And enter password "123456" at password register field Then click register field after finish to register Give enter username "Jerry" at username login field And clear login password edit field And enter password "123456" at password login field Then click login field after finish to register And click login button And I have been logined, and I can click logout button Then click reset button to clear database for finish test
这样写就比较正常了,但是我们又应该如何书写对应的steps.py文件呢?在这里给大家一个小小的提示。
step(u'enter username "(.*)" at username register field') def enter_username(step,typeval): lubinput(lubfind(world.driver,"id","com.example.demo4:id/username1"),typeval)
这样我们由feature对应UI变成了steps.py文件对应UI了。由于all.feature文件是由业务人员所写的,所以后者比较可靠。
顾翔凡言:
软件的基本功能质量应该主要由开发人员负责,测试人员的主要责任在于非功能质量,比如:性能、易用性、可靠性。以及边缘问题及深层问题,边缘问题及深层问题主要靠探索式测试完成。