自動化測試中級篇——LazyAndroid UI自動化測試框架使用指南
簡介
一直以來,安卓UI自動化測試都存在以下兩個障礙,一是測試工具Mokey/Appium等的學習成本較高,不方便剛接觸移動端自動化的新手入門;另一個是,在測試程式碼書寫中耗費在控制元件元素查詢上的時間太多,在一些稍微複雜的應用中尤其突出。LazyAndroid正是為了解決這些問題而誕生的一款UI自動化測試框架。它基於appium,封裝了appiumDriver的設定、安卓基本控制元件的使用和手機的滑動、按鍵等基本操作,增加了元素查詢的重試機制、異常處理截圖等。結合LazyUiautomaterViewer工具自動生成的bean層java程式碼,更可以使QA可以無需親自動手完成具體頁面中控制元件的抓取,無需關心appium api的使用,即可輕鬆完成測試邏輯程式碼的書寫。
使用方法
二.建立測試工程,引入上面的jar包,開始測試程式碼的書寫。下面以測試京東錢包apk的登陸和轉賬功能為例,以Maven作為專案管理工具,結合LazyUiAutomaterViewer工具進行示範(LazyAndroid也可以不依賴LazyUiAutomaterViewer單獨使用)。
建立maven測試工程(前提是已下載安裝maven和IDE的maven外掛),在pom檔案中新增jar包依賴(jar包已推到我們公司maven私服)。
Pom檔案如下:
<projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>LazyAndroidTestDemo</groupId>
<artifactId>LazyAndroidTestDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<build>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.9.9</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.2</version>
</dependency>
<dependency>
<groupId>lazyAndroid</groupId>
<artifactId>lazyAndroid</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
三.抓取頁面元素匯出java檔案。
手機使用USB線連上電腦,啟動LazyUiAutomaterViewer,進行截圖和抓取。LazyUiAutomaterViewer工具的獲取及使用方法參見《LazyUiAutomatorViewer使用說明》。
四.開始測試程式碼的書寫:
- 匯入Bean層程式碼。將LazyUiAutomaterViewer自動生成的java程式碼匯入到專案bean層中。
下面是京東錢包登入頁使用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);}
}
- page層程式碼編寫。根據測試邏輯,完成page層程式碼的書寫。基於bean層定義的控制元件變數,完成基本操作。
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進行構造,見下圖:
- 呼叫page層方法,完成測試。
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, 執行或除錯測試程式碼,執行測試。