在項目配置完基於robotium框架下的自動化測試用例後發現雖然用代碼配置測試用例雖然較爲靈活,可是若是編寫較爲全面的測試用例則必然會消耗大量開人員的精力,而且對於用例的後期維護也是很大一部分投入,使開發人員沒法更爲專一於項目構建,如此萌生了將測試用例以HTML腳本方式進行表述,可以讓測試人員進行配置測試腳本,測試人員在進行全流程業務測試過程當中進行自動化測試腳本的編寫,而且可進行增量維護,在項目上線前能夠用最小的時間代價進行最全面的測試工做,對於項目質量的把控有不可忽視的做用。html
本自動化測試項目須要開發人員針對項目進行必定的配置,當配置完成後可對自動化測試項目進行打包,與被測項目安裝在同一設備內,經過adb執行指定命令完成自動化測試的流程。除非有重大變,自動化測試項目僅需開發一次,可針對同一被測項目重複使用,全部測試流程的設備行爲由HTML標籤進行約束,測試人員以約定HTML標籤編寫測試用例,可動態指定執行的測試用例,便於測試用例的維護。java
執行環境:
一、被測試項目安裝包
二、自動化測試項目安裝包
三、Android開發環境(示例開發工具爲eclipse)
四、Android設備android
一、新建被測試項目,並編寫相關功能。
二、建立測試項目,關聯被測試項目。
三、測試項目導入robotium-solo jar包(可於官網下載)
四、編寫代碼自動化測試用例,測試robotium自動化測試是否可正常執行ios
開發工具中執行測試用例:光標放在當前類中,點擊鼠標右鍵 --> RunAs --> Android Junit Testsql
一、建立一個Android項目,編輯項目清單文件 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.walker.autotest" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="9" /> <!-- 必須指定,並設置目標項目包名:com.waitingfy.iosunlock 替換成目標項目的包名--> <instrumentation android:name="android.test.InstrumentationTestRunner" android:targetPackage="com.waitingfy.iosunlock" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <!-- 必須指定所用library(拷貝便可) --> <uses-library android:name="android.test.runner" /> </application> </manifest>
二、 增長自動化測試用例執行入口,代碼以下: /** * @Title: HtmlScariptTest.java * @Package com.walker.autotest * @Description: TODO * @author A18ccms A18ccms_gmail_com * @date 2017年9月4日 下午2:00:09 * @version V1.0 */ package com.walker.autotest; import com.robotium.solo.Solo; import android.os.Environment; import android.test.ActivityInstrumentationTestCase2; import android.util.Xml; /** @ClassName: HtmlScariptTest * * @Description: TODO * * @author walker * * @date 2017年9月4日 下午2:00:09 * */ public class HtmlScariptTest extends ActivityInstrumentationTestCase2{ Solo solo; String logFileName = "testLog.txt"; private static Class<?> launchActivityClass; //被測試應用入口界面Activity全名 private static String mainActiviy = "com.waitingfy.iosunlock.MainActivity"; //被測試應用包名 private String packageName = "com.waitingfy.iosunlock"; //加載入口Activity,獲取Activity的類對象 static { try { launchActivityClass = Class.forName(mainActiviy); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } /** * 默認構造函數,以入口Activity對象爲參數調用父類構造,設定測試用例入口執行邏輯 */ public HtmlScariptTest() { super(launchActivityClass); } /** * 測試用例初始化時設置solo對象,對設備的全部操做經過solo對象進行。 */ @Override protected void setUp() throws Exception { super.setUp(); solo = new Solo(getInstrumentation(),getActivity()); } //必須覆寫 @Override protected void tearDown() throws Exception { super.tearDown(); } /** * Add by walker Date 2017年9月4日 * @Description: TODO * 測試用例執行入口,測試用例方法名以test開頭命名 */ public void test0(){ solo.sleep(10000); }
標籤 | 動做 | 屬性 | 值 |
---|---|---|---|
testCase | 指定測試用例 | 測試用例文件名 | |
data | 標記內容爲測試數據 | dataType | 各標籤值 |
clickView | 點擊指定ID的View控件 | ID,hasText | |
inputText | 指定編輯框錄入內容 | ID | 輸入框錄入內容 |
checkBox | 設置指定選擇框的勾選屬性 | ID | 值爲1勾選,不然不勾選 |
spinner | 列表選擇 | ID | 被選擇內容的文本值 |
sleep | 延遲時間後再繼續執行 | 延時時長(毫秒) | |
onKeyDown | 鍵盤按鍵事件 | 按鍵值,支持範圍以下 | |
clickOnText | 點擊顯示的文本 | hasText | 被點擊的文本字符串 |
takeScreenshot | 截屏並保存圖片 | ||
waitForActivity | 等待指定頁面 | 指定頁面的activity值 | |
checkData | 校驗數據標籤 | tableName | |
filed | 數據校驗字段 | filedName | 指定字段的值 |
testCase:僅在EMS.xml中起做用,且Main.xml文件中只識別testCase標籤
data:包裹測試數據,被包裹內容爲測試腳本數據,屬性dataType來設定數據類型。shell
clickView:點擊頁面View控件,屬性有ID、hasText。ID屬性不可爲空,以ID值進行索引View控件並進行點擊;hasText屬性值能夠爲空,若是hasText爲空或者在當前可見頁面能夠所搜到此文本則執行點擊事件。app
clickOnText:點擊頁面上指定的文本,屬性有hasText。若是hasText爲空或者在當前可見頁面能夠搜索到此文本則執行點擊事件。框架
waitForActivity:值爲頁面Activity名稱,等待指定的Activity,若是不是指定activity則結束測試流程eclipse
spinner:點擊控件後顯示候選列表,並從列表中選擇標籤指定的值。ide
takeScreenshot:截圖文件保存路徑(/sdcard/Robotium-Screenshots)
checkData:用於指定校驗數據,屬性tableName爲被查詢的表名,標籤包裹內容爲字段屬性值
filed:用於指定被校驗表的字段屬性,屬性filedName指定字段名稱,值爲字段的值。此標籤應被checkData標籤包裹。
支持的按鍵:back(回退鍵)、enter(確認鍵)、left(左鍵)、right(右方向鍵)、up(上方向鍵)、down(下方向鍵)、數字(1-9); ID屬性:爲項目中xml文件中配置id值,可經過項目源碼獲取
以下代碼爲帶有HTML標籤解析的代碼,在執行測試用例時首先對指定路徑下的配置文件main.xml進行讀取,根據其配置的腳本文件加載對應的執行腳本,而後依次執行所配置的腳本,完成自動化測試流程。
/** * @Title: HtmlScariptTest.java * @Package com.walker.autotest * @Description: TODO * @author A18ccms A18ccms_gmail_com * @date 2017年9月4日 下午2:00:09 * @version V1.0 */ package com.walker.autotest; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.xmlpull.v1.XmlPullParser; import com.robotium.solo.Solo; import android.os.Environment; import android.test.ActivityInstrumentationTestCase2; import android.util.Xml; import android.view.KeyEvent; import android.view.View; import android.widget.CheckBox; import android.widget.EditText; /** * @ClassName: HtmlScariptTest * * @Description: TODO * * @author walker * * @date 2017年9月4日 * */ public class HtmlScariptTest extends ActivityInstrumentationTestCase2 { Solo solo; String logFileName = "testLog.txt"; private static Class<?> launchActivityClass; // 被測試應用入口界面Activity全名 private static String mainActiviy = "com.waitingfy.iosunlock.MainActivity"; // 被測試應用包名 private String packageName = "com.waitingfy.iosunlock"; // 加載入口Activity,獲取Activity的類對象 static { try { launchActivityClass = Class.forName(mainActiviy); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } /** * 默認構造函數,以入口Activity對象爲參數調用父類構造,設定測試用例入口執行邏輯 */ public HtmlScariptTest() { super(launchActivityClass); } /** * 測試用例初始化時設置solo對象,對設備的全部操做經過solo對象進行。 */ @Override protected void setUp() throws Exception { super.setUp(); solo = new Solo(getInstrumentation(), getActivity()); } @Override protected void tearDown() throws Exception { super.tearDown(); } /** * Add by walker Date 2017年9月4日 * * @Description: TODO 測試用例執行入口,測試用例方法名以test開頭命名 */ public void test0() { loadScriptList(); } String configPath = ""; /** * Add by walker Date 2017年8月29日 * * @Description: TODO 加載解析腳本清單配置文件配置腳本文件 */ private void loadScriptList() { configPath = Environment.getExternalStorageDirectory() + "/Test/"; File file = new File(configPath + "Main.xml"); try { InputStream in = new FileInputStream(file); XmlPullParser parser = Xml.newPullParser(); parser.setInput(in, "utf-8"); int type = parser.getEventType(); while (type != XmlPullParser.END_DOCUMENT) { String value = ""; switch (type) { case XmlPullParser.START_TAG: value = parser.nextText(); if ("testCase".equals(parser.getName())) { // 讀取腳本文件,加載執行所配置的腳本文件 xmlLoad(value); } break; case XmlPullParser.END_TAG: break; } type = parser.next(); } } catch (Exception e) { e.printStackTrace(); } } /** 數據標誌:1-data類型; 2-checkData */ int dataFlag = 0; HashMap<String, String> dataMap = new HashMap<String, String>(); HashMap<String, String> lableAction = new HashMap<String, String>(); /** * Add by walker Date 2017年8月29日 * * @Description: TODO 加載自動化測試腳本,並執行自動化測試 * @param fileName * 自動化測試腳本名稱 */ public void xmlLoad(String fileName) { File file = new File(configPath + fileName); try { InputStream in = new FileInputStream(file); // 建立xmlPull解析器 XmlPullParser parser = Xml.newPullParser(); /// 初始化xmlPull解析器 parser.setInput(in, "utf-8"); // 讀取文件的類型 int type = parser.getEventType(); int depth = parser.getDepth(); String id = ""; String value = ""; String hasText = ""; String dataType = ""; String filedName = ""; String tableName = ""; ArrayList<Map<String, String>> actionList = new ArrayList<Map<String, String>>(); while ((type != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { id = ""; value = ""; hasText = ""; switch (type) { // 開始標籤 case XmlPullParser.START_TAG: id = parser.getAttributeValue(null, "id"); hasText = parser.getAttributeValue(null, "hasText"); filedName = parser.getAttributeValue(null, "filedName"); if ("data".equals(parser.getName())) { actionList = new ArrayList<Map<String, String>>(); dataFlag = 1; dataType = parser.getAttributeValue(null, "dataType"); break; } if ("checkData".equals(parser.getName())) { actionList = new ArrayList<Map<String, String>>(); dataMap = new HashMap<String, String>(); tableName = parser.getAttributeValue("", "tableName"); dataMap.put("dataName", tableName); dataFlag = 2; break; } value = parser.nextText(); lableAction = new HashMap<String, String>(); lableAction.put("dataType", dataType); lableAction.put("ID", id); lableAction.put("hasText", hasText); lableAction.put("tableName", tableName); lableAction.put("filedName", filedName); lableAction.put("value", value); lableAction.put("actionName", parser.getName()); actionList.add(lableAction); if (dataFlag == 2 && !isEmptyUnNull(filedName) && !dataMap.containsKey(filedName)) { dataMap.put(filedName, value); } else { } if ("clickView".equals(parser.getName())) {// 按鈕 if (isEmptyUnNull(hasText) || solo.searchText(hasText, true)) { clickOnView(id); } } else if ("inputText".equals(parser.getName())) {// 文本輸入框 enterText(id, value); } else if ("checkBox".equals(parser.getName())) {// 複選框 if ("1".equals(value)) { chkSelect(id, true); } else { chkSelect(id, false); } } else if ("spinner".equals(parser.getName())) {// 下拉選擇 selectData(value, id); } else if ("sleep".equals(parser.getName())) {// 睡眠時間 try { sleep(Integer.parseInt(value) / 1000); } catch (Exception e) { e.printStackTrace(); } } else if ("onKeyDown".equals(parser.getName())) {// 按鍵事件 sendKey(value); } else if ("clickOnText".equals(parser.getName())) {// 點擊文本 solo.clickOnText(value + ""); } else if ("takeScreenshot".equals(parser.getName())) {// 屏幕拍照 solo.takeScreenshot(); solo.sleep(300); } else if ("waitForActivity".equals(parser.getName())) { sleep(2); if (solo.waitForActivity(value)) { break; } else { return; } } break; case XmlPullParser.END_TAG: if ("data".equals(parser.getName())) { dataType = ""; } else if ("checkData".equals(parser.getName())) { sleep(1); checkData(); } break; } type = parser.next(); } } catch (Exception e) { e.printStackTrace(); } } /** * Add by walker Date 2017年9月1日 * * @Description: TODO 根據配置文件校驗數據 */ private void checkData() { StringBuilder sql = new StringBuilder(); sql.append("select * from "); if (dataMap != null && dataMap.size() > 1 && !isEmptyUnNull(dataMap.get("dataName"))) { sql.append(dataMap.get("dataName") + " where "); Set<Entry<String, String>> set = dataMap.entrySet(); Iterator<Entry<String, String>> it = set.iterator(); Map.Entry<String, String> me; while (it.hasNext()) { me = it.next(); if ("dataName".equals(me.getKey())) { } else { sql.append(me.getKey() + "='" + me.getValue() + "' and "); } } if ((sql + "").endsWith("and ")) { sql.delete(sql.lastIndexOf("and"), sql.length()); } // 校驗數據邏輯,可根據具體項目進行配置,執行sql查詢語句,獲取查詢結果,如在指定條件下獲取查詢結果爲空,那麼 HashMap<String, String> data = null; // data = DbUtils.getInstance().queryFirstData(sql +""); if (data != null && data.size() > 0) { } else { } } else { return; } } /** * Add by walker Date 2017年9月4日 * * @Description: TODO 延時器 * @param time * 延時時間,單位爲秒 */ private void sleep(int time) { long start = System.currentTimeMillis(); while ((System.currentTimeMillis() - start) < time * 1000) { } } /** * Add by walker Date 2017年9月4日 * * @Description: TODO 判斷字符串是否爲空 * @param value * 字符串的值 * @return 若是字符串爲null或""或"null"或者所有爲空白字符則返回true,不然返回false。 */ public static boolean isEmptyUnNull(String value) { if (value != null && !"".equalsIgnoreCase(value.trim()) && !"null".equalsIgnoreCase(value.trim()) && value.trim().length() != 0) { return false; } else { return true; } } /** * Add by walker Date 2017年9月4日 * * @Description: TODO 模擬按鍵事件,按鍵先後進行延時 * @param keyStr * 按鍵字符 * 支持的按鍵:back(回退鍵)、enter(確認鍵)、left(左鍵)、right(右方向鍵)、up(上方向鍵)、down( * 下方向鍵)、數字(1-9) */ public void sendKey(String keyStr) { int key = 0; if ("1".equals(keyStr)) { key = KeyEvent.KEYCODE_1; } else if ("3".equals(keyStr)) { key = KeyEvent.KEYCODE_3; } else if ("4".equals(keyStr)) { key = KeyEvent.KEYCODE_4; } else if ("5".equals(keyStr)) { key = KeyEvent.KEYCODE_5; } else if ("6".equals(keyStr)) { key = KeyEvent.KEYCODE_6; } else if ("7".equals(keyStr)) { key = KeyEvent.KEYCODE_7; } else if ("8".equals(keyStr)) { key = KeyEvent.KEYCODE_8; } else if ("9".equals(keyStr)) { key = KeyEvent.KEYCODE_9; } else if ("back".equals(keyStr)) { key = KeyEvent.KEYCODE_BACK; } else if ("enter".equals(keyStr)) { key = KeyEvent.KEYCODE_DPAD_CENTER; } else if ("left".equals(keyStr)) { key = KeyEvent.KEYCODE_DPAD_LEFT; } else if ("right".equals(keyStr)) { key = KeyEvent.KEYCODE_DPAD_RIGHT; } else if ("up".equals(keyStr)) { key = KeyEvent.KEYCODE_DPAD_UP; } else if ("down".equals(keyStr)) { key = KeyEvent.KEYCODE_DPAD_DOWN; } solo.sleep(30); solo.sendKey(key); solo.sleep(30); } /** * Add by walker Date 2017年9月4日 * * @Description: TODO 選擇對應信息 * @param selectedStr * 選擇的類型(文本內容) * @param id * 下拉選擇控件ID * @param title * 選擇框的標題字符串 * @param ranges * 被選擇範圍-所選擇的字符串必須在此範圍內 */ public void selectData(String selectedStr, String id) { clickOnView(id); solo.sleep(1000); solo.clickOnText(selectedStr); } /** * Add by walker Date 2017年9月4日 * * @Description: TODO 點擊ID所指定的控件 * @param viewId * 視圖控件ID */ public void clickOnView(String viewId) { try { View view = getView(solo, viewId); solo.clickOnView(view); } catch (Exception e) { e.printStackTrace(); } } public View getView(Solo solo, String idStr) { int id = solo.getCurrentActivity().getResources().getIdentifier(idStr, "id", packageName); View v = solo.getView(id); return v; } /** * Add by walker Date 2017年9月4日 * * @Description: TODO 勾選框勾選功能 * @param id * checkBox控件ID * @param checked * 設置是否選擇此控件 */ public void chkSelect(String id, final boolean checked) { CheckBox chk = (CheckBox) solo.getView(id); if (!chk.isChecked() && checked) { solo.clickOnView(chk); } } /** * Add by walker Date 2017年9月4日 * * @Description: TODO 爲文本錄入框錄入字符串,文本錄入完成後延遲指定時間 * @param id * 文本錄入框ID * @param msg * 文本錄入信息 */ public void enterText(String id, String msg) { EditText editText = (EditText) getView(solo, id); if (editText != null) { solo.clickOnView(editText); solo.clearEditText(editText); solo.enterText(editText, msg + ""); } solo.sleep(200); } }
一、新建main.xml配置文件,根據標籤testCase指定測試腳本文件。
二、根據動做標籤編寫測試腳本用例,以main.xml中指定的文件名爲名。
三、將main.xml文件及腳本配置文件放到Android設備sd卡下Test文件夾下(若是文件夾不存在,那麼新建此文件夾,文件名區分大小寫)
在目標Android設備安裝上測試apk及被測試apk簽名後在電腦命令行上執行以下命令便可實現自動化測試啓動流程。
adb shell am instrument -e class com.walker.autotest.HtmlScariptTest -w com.walker.autotest/android.test.InstrumentationTestRunner
一、測試apk安裝包與被測試項目安裝包apk的應用簽名要一致。 二、測試項目若是引用庫工程或者V4包時須要注意有無重複的包,本用例編寫時測試工程引用了V4包,致使被測應用啓動失敗。