標籤:
簡介
項目一直是手工測試為主,加上一直是TV類應用,很多自動化工具都沒有針對TV類項目做很好的適配,所以只有自己動手了。主要針對項目的特殊性進行了部分改造,不一定適用於其他項目。(涉及隱私,就不提供json檔案和軟體名字啦)
痛點
1.非標準控制項的難處
通過uiautomatorviewer擷取到的不一樣的磁貼,屬性全部相同(除了座標點),意味著沒法通過id和class+index方式擷取,text屬性為空白,也就沒有辦法通過byText的方式擷取uiobject,高度定製的磁貼,讓自動化很為難,如果通過座標點,太坑爹,不能跨裝置,還是坑。
2.TV類應用沒有觸摸操作
TV類安卓程式,主要面向的是遙控器,也就是接收的是keyevent,所以touch事件顯得不這麼全面,為了最接近使用者,還是選擇用key來做自動化。
架構模組
解析模組
從伺服器解析json檔案格式,封裝成實體類CellInfo,返回一個包含磁鐵資訊的List。一個磁鐵對應一個CellInfo,一個CellInfo需要提取的資訊有
- x座標
- y座標
- 所屬的Tab分類頁
- 每個磁鐵的說明標籤
所以對應的定義如下:
package launcherClick.model;public class CellInfo { private String label; private String tab; public String getTab() { return tab; } public void setTab(String tab) { this.tab = tab; } private int x; private int y; public String getLabel() { return label; } public void setLabel(String label) { this.label = label; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; }}
解析用的是第三方開源庫org.json,對應的解析代碼如下:
package launcherClick;import java.util.ArrayList;import java.util.List;import org.json.JSONArray;import org.json.JSONObject;import Utils.IOUtils;import Utils.Println;import launcherClick.model.CellInfo;public class AMetroParse { public static List<CellInfo> startParse(String url) { String str = IOUtils.readFromNet(url); if(str==null){ return null; } JSONObject jsonObject = new JSONObject(str); List<CellInfo> list = new ArrayList<CellInfo>(); JSONArray _tabs = jsonObject.getJSONArray("tabs"); for(int i = 0;i<_tabs.length();i++){ String tab = _tabs.getJSONObject(i).getString("label"); JSONArray _cells = _tabs.getJSONObject(i).getJSONArray("cells"); for(int j=0;j<_cells.length();j++){ CellInfo cellInfo = new CellInfo(); cellInfo.setX(_cells.getJSONObject(j).getJSONObject("location").getInt("x")); cellInfo.setY(_cells.getJSONObject(j).getJSONObject("location").getInt("y")); cellInfo.setLabel(_cells.getJSONObject(j).getJSONObject("content").getString("label")); cellInfo.setTab(tab); list.add(cellInfo); } } return list; }}
座標轉換模組
這部分的工作是把上一步驟解析後的實體類進行提取和處理,主要處理的內容是根據x y座標計算磁鐵移動量,更具x的極大值和分類頁做跨分類移動的位移計算以及一些其他處理。
通過x y計算位移比較簡單,只是簡單計算距離:
for (CellInfo cellInfo : list) { if (cellInfo.getLabel().contains(label) && cellInfo.getTab().contains(tab)) { int x = cellInfo.getX(); int y = cellInfo.getY(); new Println("x:" + x + " " + "y:" + y + " " + "位移量" + _offset); // x方向 for (int i = x + _offset; i > 1; i--) { new Println("→" + " count is " + (x + _offset)); keyRight(); } // y方向 for (int i = y; i > 1; i--) { new Println("↓"); keyDown(); } } }
恩,裡面有個位移量,跨分類用的,位移量是通過每個分類下磁鐵最大值得出來的,每一個分類最後一個磁鐵的x值即為最大值,遍曆當前分類下的所有磁鐵的x,如果大於後面一個,則放在前面,下次再用這個值去比較下面的x值。忘記這是什麼排序演算法了…囧…當然,也可以用Collections的內建的演算法。
private int togicOffset(String tab) { int lineLenth = 0; List<CellInfo> list = AMetroParse .startParse("我是隱藏的介面"); for (CellInfo cellInfo : list) { if (cellInfo.getTab().contains(tab)) { int x = cellInfo.getX(); lineLenth = lineLenth >= x ? lineLenth : x; } } return lineLenth; }
那麼通過上面的排序之後,就可以得到每個分類的位移量,這樣對於後面的分類磁貼,就可以知道磁貼在整體的真正位置啦,於是就可以開始移動了。
for (CellInfo cellInfo : list) { if (cellInfo.getLabel().contains(label) && cellInfo.getTab().contains(tab)) { int x = cellInfo.getX(); int y = cellInfo.getY(); new Println("x:" + x + " " + "y:" + y + " " + "位移量" + _offset); // x方向 for (int i = x + _offset; i > 1; i--) { new Println("→" + " count is " + (x + _offset)); keyRight(); } // y方向 for (int i = y; i > 1; i--) { new Println("↓"); keyDown(); } } }
圖片比較模組
來源於monkeyrunner的思路,原理是計算兩個bitmap的長寬,然後提取出裡面每個像素的像素值,如果相同,相似性+1,最後再除以總像素值(比如1280x720),這樣就可以把相似程度轉為一個一個可以衡量的具體值了,那麼判斷這個介面是不是我需要點擊的時候,只需要截屏當前圖片和預期的圖片對比,相似性達到100的時候就認為是正確的。當然也提供了局部比較的功能,比如擷取不到資料的異常提示。那麼代碼如下,提供了多種重載的方法以及兩個assert判斷。
package Utils;import junit.framework.Assert;import android.graphics.Bitmap;import android.graphics.BitmapFactory;public class ImageCompare { public static void AssertBitmapEqual(Bitmap bitmap0, Bitmap bitmap1){ /** * 百分百圖片相同斷言-.- */ Assert.assertEquals(100, ImageCompare(bitmap0,bitmap1)); } public static void AssertBitmapNotEqual(Bitmap bitmap0, Bitmap bitmap1){ /** * 百分之零圖片相同斷言 */ Assert.assertEquals(0, ImageCompare(bitmap0,bitmap1)); } public static int ImageCompare(String path0,String path1) { /** * 提供根據路徑直接比較 */ Bitmap bitmap0 = BitmapFactory.decodeFile(path0); Bitmap bitmap1 = BitmapFactory.decodeFile(path1); return ImageCompare(bitmap0,bitmap1); } public static int ImageCompareChild(Bitmap bitmap0, Bitmap bitmap1,int x,int y,int width,int height) { /** * 裁剪子圖並比較,主要是為瞭解決拉取動態資料不同,但是局部提示不變的比較情境。 */ Bitmap bitmap00 = bitmap0.createBitmap(bitmap0, x, y, width, height); Bitmap bitmap01 = bitmap1.createBitmap(bitmap1, x, y, width, height); return ImageCompare(bitmap00,bitmap01); } public static int ImageCompare(Bitmap bitmap0, Bitmap bitmap1) { /** * 比較的主函數 * 只能比較相同長寬的圖片,不相等返回-1失敗 * 相似性為1~100 * 原理是提取每一個像素點比較,整張圖相似性取決於像素點相同個數,所以還是比較準確的 */ int picPct = 0; int picCount = 0; int picCountAll = 0; new Println("begin to compare"); if (bitmap0 == null || bitmap1 == null) { new Println("null bitmap"); return -1; } if (bitmap0.getWidth() != bitmap1.getWidth() || bitmap0.getHeight() != bitmap1.getHeight()) { return -1; } new Println("寬度為:" + bitmap1.getWidth() + "高度為:" + bitmap1.getHeight()); for (int j = 0; j < bitmap1.getWidth(); j++) { for (int i = 0; i < bitmap0.getHeight(); i++) { if (bitmap0.getPixel(j, i) == bitmap1.getPixel(j, i)) { picCount++; } picCountAll++; } } int result = (int) (((float) picCount) / picCountAll * 100); new Println(picCount + "/" + picCountAll); new Println("相似性為:" + result); return result; }}
異常&&其他模組
定義了一些異常類,主要功能用於提示,這個提示的作用後面會用到。
增加了啟動應用和關閉應用的方法,原理是用到了shell命令。
Runtime.getRuntime().exec("am start -n 我是包名隱藏者");
初始化方法,主要用於異常時能夠一鍵重新開始,以及磁貼複原功能,還有其他一些小的處理就不多說啦,下面開始持續構建。
自動化的持續整合
還是那句話,不持續整合的自動化不是自動化,所以這裡介紹的是基於jenkins的自動化整合,其實UiAutomator做整合還是很容易的,只要把jar包放在一個固定的目錄,然後shell命令執行就完事了。
然後定時任務自己選吧,構建失敗的寄件提醒這裡設定的是,上面我自訂的異常類,當控制台輸出我的異常類,那麼會認為不通過,然後觸發寄件提醒。
那麼至此,一個較為完整的流程就完成啦,那麼在此架構上組員們(就我一個)就可以更進一部去完善二級頁面的自動化用例了。
Android TV磁貼類app自動化架構二次改造(基於UiAutomator)