一直以来,安卓UI自动化测试都存在以下两个障碍,一是测试工具Mokey/Appium等的学习成本较高,不方便刚接触移动端自动化的新手入门;另一个是,在测试代码书写中耗费在控件元素查找上的时间太多,在一些稍微复杂的应用中尤其突出。LazyAndroid正是为了解决这些问题而诞生的一款UI自动化测试框架。它基于appium,封装了appiumDriver的设置、安卓基本控件的使用和手机的滑动、按键等基本操作,增加了元素查找的重试机制、异常处理截屏等。结合LazyUiautomaterViewer工具自动生成的bean层java代码,更可以使QA可以无需亲自动手完成具体页面中控件的抓取,无需关心appium api的使用,即可轻松完成测试逻辑代码的书写。
一.下载源码进行编译,将源码编译生成的jar包推送到目标maven 私服仓库或者直接将jar包导入到测试项目中。源码下载地址: http://github.com/lazytestteam/LazyAndroid
二.建立测试工程,引入上面的jar包,开始测试代码的书写。下面以测试京东钱包apk的登陆和转账功能为例,以Maven作为项目管理工具,结合LazyUiAutomaterViewer工具进行示范(LazyAndroid也可以不依赖LazyUiAutomaterViewer单独使用)。
建立maven测试工程(前提是已下载安装maven和IDE的maven插件),在pom文件中添加jar包依赖(jar包已推到我们公司maven私服)。
Pom文件如下:
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd"> 三.抓取页面元素导出java文件。 手机使用USB线连上电脑,启动LazyUiAutomaterViewer,进行截屏和抓取。LazyUiAutomaterViewer工具的获取及使用方法参见《LazyUiAutomatorViewer使用说明》。 四.开始测试代码的书写: 下面是京东钱包登录页使用LazyUiAutomaterViewer自动抓取、导出的java文件: package test.java.bean; import lazy.android.annotations.*; import lazy.android.bean.BaseBean; import lazy.android.controls.*; import io.appium.java_client.AppiumDriver; /** * Gennerated by lazyUiautomaterViewer. */ public classLoginBean extendsBaseBean{ @Xpath(xpath={"//android.widget.TextView[@resource-id='com.wangyin.payment:id/txt_main_title']"}) @Description(description="登录") public PlainText textView1; @Xpath(xpath={"//android.view.View[@resource-id='com.wangyin.payment:id/view_divider_line']"}) @Description(description="") public View view1; @Xpath(xpath={"//android.widget.ScrollView[@resource-id='com.wangyin.payment:id/fragment_container']"}) @Description(description="") public View scrollView2; @Xpath(xpath={"//android.widget.RadioGroup[@resource-id='com.wangyin.payment:id/main_footbar_menu']"}) @Description(description="") public View radioGroup3; @Xpath(xpath={"//android.widget.RadioButton[@resource-id='com.wangyin.payment:id/login_tab_phone']"}) @Description(description="钱包账户") public Click jdpayAccount; @Xpath(xpath={"//android.widget.RadioButton[@resource-id='com.wangyin.payment:id/login_tab_jd']"}) @Description(description="京东账户") public Click jdAccount; @Xpath(xpath={"//android.widget.LinearLayout[@resource-id='com.wangyin.payment:id/layout_login_jd']/android.view.View[1]"}) @Description(description="") public View view4; @Xpath(xpath={"//android.widget.EditText[@resource-id='com.wangyin.payment:id/cp_input_combox_jd']"}) @Description(description="京东商城手机号/用户名/邮箱") public Text editTextUserName; @Xpath(xpath={"//android.widget.TextView[@text='账号']"}) @Description(description="账号") public PlainText textView2; @Xpath(xpath={"//android.widget.ImageView"}) @Description(description="") public View imageView5; @Xpath(xpath={"//android.widget.EditText[@resource-id='com.wangyin.payment:id/cp_input_pwd']"}) @Description(description="") public Text editJDTextPwd; @Xpath(xpath={"//android.widget.TextView[@text='密码']"}) @Description(description="密码") public PlainText textView3; @Xpath(xpath={"//android.widget.LinearLayout[@resource-id='com.wangyin.payment:id/layout_login_jd']/android.view.View[2]"}) @Description(description="") public View view6; @Xpath(xpath={"//android.widget.Button[@resource-id='com.wangyin.payment:id/btn_login_jd']"}) @Description(description="登录") public Click buttonLogin; @Xpath(xpath={"//android.widget.TextView[@resource-id='com.wangyin.payment:id/txt_jd_register']"}) @Description(description="注册京东账户") public PlainText textView4; @Xpath(xpath={"//android.widget.TextView[@resource-id='com.wangyin.payment:id/txt_forget_pwd']"}) @Description(description="忘记密码?") public PlainText textView5; @Xpath(xpath={"//android.widget.EditText[@resource-id='com.wangyin.payment:id/cp_input_combox_wy']"}) @Description(description="请填写手机号") public Text editTextPhone; @Xpath(xpath={"//android.widget.TextView[@text='手机号']"}) @Description(description="手机号") public PlainText textViewPhone; @Xpath(xpath={"//android.widget.Button[@resource-id='com.wangyin.payment:id/btn_login']"}) @Description(description="下一步") public Click nextStep; @Xpath(xpath={"//android.widget.EditText[@resource-id='com.wangyin.payment:id/cp_input_pwd']"}) @Description(description="") public Text editjdPayTextPwd; @Xpath(xpath={"//android.widget.TextView[@text='密码']"}) @Description(description="密码") public PlainText textViewPwd; @Xpath(xpath={"//android.widget.Button[@resource-id='com.wangyin.payment:id/btn_login']"}) @Description(description="登录") public Click jdpayLogin; publicLoginBean(AppiumDriveraDriver){super(aDriver);} } package test.java.page; importorg.openqa.selenium.WebElement; importorg.slf4j.Logger; importorg.slf4j.LoggerFactory; import test.java.bean.LifeBean; import test.java.bean.LifeBean2; importtest.java.bean.LoginBean; importtest.java.bean.MineBean; importtest.java.bean.TodayBean; importio.appium.java_client.AppiumDriver; importlazy.android.common.LazyDriver; public class DemoPage { private Loggerlogger = LoggerFactory.getLogger(this.getClass()); private LazyDriverlazyDriver; private AppiumDriverdriver; private TodayBeantodayBean; private LifeBean2lifeBean2; private MineBeanmineBean; private LoginBeanloginBean; /** * 构造函数 * @param aLazyDriver */ public DemoPage(LazyDriveraLazyDriver) { lazyDriver = aLazyDriver; driver = lazyDriver.getDriver(); todayBean = new TodayBean(driver); new LifeBean(driver); lifeBean2 = new LifeBean2(driver); mineBean = new MineBean(driver); loginBean = new LoginBean(driver); } /** * 登陆操作 */ public void login() { logger.info("login by jdpay account!"); todayBean.textViewToday.expectElementExistOrNot(true); todayBean.textViewMine.expectElementExistOrNot(true); todayBean.textViewMine.click(); lazyDriver.handleFailure("screen test:"); mineBean.login.expectElementExistOrNot(true); mineBean.login.click(); loginBean.jdpayAccount.expectElementExistOrNot(true); loginBean.jdpayAccount.click(); loginBean.editTextPhone.input("13034631475"); loginBean.nextStep.click(); loginBean.editjdPayTextPwd.expectElementExistOrNot(true); loginBean.editjdPayTextPwd.input("haha123"); loginBean.jdpayLogin.click(); lazyDriver.swipeToLeft(); } /** * 转账操作 * @throws InterruptedException */ public void doTranAccount() throws InterruptedException { logger.info("transfer accounts!"); todayBean.textViewLife.click(); lazyDriver.swipeDown(); lazyDriver.swipeToLeft(); lifeBean2.textViewTransAccount.click(); WebElement wl = lazyDriver.findElementByText("转账给朋友"); wl.click(); Thread.sleep(1000); lazyDriver.goBack(); lazyDriver.goBack(); } } 其中,需要注意一下构造函数。在Page层的构造函数中,需要做两个事情。一个是给LazyDriver赋值(通过Test层传递过来的LazyDriver对象),另一个是通过LazyDriver对象获取appiumDriver,来构造Bean层的类。Page层用到的所有bean层类都需要传入appiumDriver进行构造,见下图: package test.java.test; importjava.net.MalformedURLException; importorg.testng.annotations.BeforeClass; importorg.testng.annotations.Test; importtest.java.page.DemoPage; importlazy.android.common.LazyDriver; public class DemoTest { private DemoPagedemoPage; @BeforeClass public void init() throws MalformedURLException { LazyDriver lazyDriver =new LazyDriver("jdpay.apk","com.wangyin.payment",".home.ui.MainActivity","4.2.2",false); demoPage = new DemoPage(lazyDriver); } /** * 登陆测试 */ @Test public void loginTest() { demoPage.login(); } /** * 转账测试 * @throws InterruptedException */ @Test public void tranAccount() throws InterruptedException { demoPage.doTranAccount(); } } 在test层代码的BeforeClass(这个demo使用了testng,但是不是必须的)中,构造了一个lazyDriver,然后通过它去构造page层的类。 和如下appiumDriver的繁琐设置相比较,是不是简化很多了? Bean、Page和Test层是我们在测试代码编写过程中,为了区分代码层次,根据代码功能定义的三个包名,不是必须的,大家可以灵活处理。 五.启动appium, 运行或调试测试代码,执行测试。 Demo工程源码:https://github.com/lazytestteam/LazyAndroidDemo 详细的使用说明请参考 http://blog.csdn.net/kaka1121/article/details/53301517