中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

UIAutomator源碼分析之啟動和運行

發布時間:2020-06-21 21:40:29 來源:網絡 閱讀:439 作者:zhukev 欄目:移動開發

通過上一篇《Android4.3引入的UiAutomation新框架官方簡介》我們可以看到UiAutomator其實就是使用了UiAutomation這個新框架,通過調用AccessibilitService APIs來獲取窗口界面控件信息已經注入用戶行為事件,那么今天開始我們就一起去看下UiAutomator是怎么運作的。

我們在編寫了測試用例之后,我們需要通過以下幾個步驟把測試腳本build起來并放到測試機器上面:

  • android create uitest-project -n AutoRunner.jar -t 5 -p D:\\Projects\UiAutomatorDemo
  • adb push e:\workspace\AutoRunner\bin\AutoRunner.jar data/local/tmp
然后通過以下命令把測試運行起來:
  • adb shell uiautomator runtest AutoRunner.jar -c majcit.com.UIAutomatorDemo.SettingsSample
那么我們就圍繞以上這個命令,從uiautomator這個命令作為突破口,看它是怎么跑起來的。開始之前我們先看下uiautomator的help幫助:
UIAutomator源碼分析之啟動和運行
  • 支持三個子命令:rutest/dump/events
  • runtest命令-c指定要測試的class文件,用逗號分開,沒有指定的話默認執行測試腳本jar包的所有測試類.注意用戶可以以格式$class/$method來指定只是測試該class的某一個指定的方法
  • runtest命令-e參數可以指定是否開啟debug模式
  • runtest命令-e參數可以指定test runner,不指定就使用系統默認。我自己從來沒有指定過
  • runtest命令-e參數還可以通過鍵值對來指定傳遞給測試類的參數

同時我們這里會涉及到幾個重要的類,我們這里先列出來給大家有一個初步的印象:

Class

Package

Description

Launcher

com.android.commands.uiautomator

uiautomator命令的入口方法main所在的類

RunTestCommand

com.android.commands

代表了命令行中‘uiautomator runtest'這個子命令

EventsCommand

com.android.commands

代表了命令行中‘uiautomator events’這個子命令

DumpCommand

com.android.commands

代表了命令行中‘uiautomator dump’這個子命令

UIAutomatorTestRunner

com.android.uiautomator.testrunner

默認的TestRunner,用來知道測試用例如何執行

TestCaseCollector

com.android.uiautomator.testrunner

用來從命令行和我們的測試腳本.class文件收集每個測試方法然后建立對應的junit.framework.TestCase測試用例的一個類,它維護著一個List<TestCase> mTestCases列表來存儲所有測試方法(用例)

UiAutomationShellWrapper

com.android.uiautomator.core

一個UiAutomationwrapper類,簡單的做了封裝,其中提供了一個setRunAsMonkey的方法來通過ActivityManagerNativeProxy來設置系統的運行模式

UiAutomatorBridge

com.android.uiautomator.core

相當于UiAutomation的代理,基本上所有和UiAutomation打交道的方法都是通過它來分發的

ShellUiAutomatorBridge

com.android.uiautomator.core

UiAutomatorBridge的子類,額外增加了幾個不需要用到UiAutomation的方法,getRotation


1.環境變量配置

和monkey以及monkeyrunner一樣,uiautomator其實也是一個shell腳本,我們看最后面的關鍵幾行:
CLASSPATH=${CLASSPATH}:${jars} export CLASSPATH exec app_process ${base}/bin com.android.commands.uiautomator.Launcher ${args}
我們先把這些變量打印出來,看都是些什么值:
  • CLASSPATH:/system/framework/android.test.runner.jar:/system/framework/uiautomator.jar::/data/local/tmp/AutoRunner.jar
  • base:/system
  • ${args}:runtest -c majcit.com.UIAutomatorDemo.SettingsSample -e jars :/data/local/tmp/AutoRunner.jar
如monkey一樣,這個shell腳本會:
  • 首先export需要的classpath環境變量,讓我們的腳本用到的jar包可以在目標設備上被正常的引用到(畢竟我們在客戶端開發的時候引用到的jar包是本地的,比如uiautomator.jar這個jar包。
  • 然后通過app_process來指定命令工作路徑為'/system/bin/'以啟動指定類com.android.commands.uiautomator.Launcher,啟動該類傳入的參數就是我們指定的測試用例類和我們build好的測試腳本jar包:runtest -c majcit.com.UIAutomatorDemo.SettingsSample -e jars :/data/local/tmp/AutoRunner.jar
那么現在我們就知道我們的入口就在com.android.commands.uiautomator.Launcher這個class里面了。

2. 子命令定位

打開com.android.commands.uiautomator.Launcher這個類的原文件,我們首先定位它的入口函數main:
/*     */   public static void main(String[] args) /*     */   { /*  74 */     Process.setArgV0("uiautomator"); /*  75 */     if (args.length >= 1) { /*  76 */       Command command = findCommand(args[0]); /*  77 */       if (command != null) { /*  78 */         String[] args2 = new String[0]; /*  79 */         if (args.length > 1) /*     */         { /*  81 */           args2 = (String[])Arrays.copyOfRange(args, 1, args.length); /*     */         } /*  83 */         command.run(args2); /*  84 */         return; /*     */       } /*     */     } /*  87 */     HELP_COMMAND.run(args); /*     */   }
里面主要做兩件事情:
  • 76行:根據輸入的第一個參數查找到Command,在我們的例子中第一個參數是runtest,所以要找到的就是runtest這個命令對應的Command
  • 83行:執行查找到的command的run方法開始執行測試
那么到了這里我們首先要搞清楚Command是怎么一回事。其實說白了一個Command就代表了我們命令行調用uiautomator輸入的第一個參數,也就是subcommand,比如我們這里就是runtest這一個命令,如果用戶輸入的是'uiautomator dump'去嘗試dump一個當前窗口界面的所有空間信息,那么該command就代表了dump這一個命令。uiautomator總共支持3種command(不連help):
  • runtest :對應RunTestCommand這個類,代表運行相應測試的命令
  • dump : 對應DumpCommand這個類,dump當前窗口控件信息,你在命令行運行‘uiautomator dump’就會把當前ui的hierarchy信息dump成一個文件默認放到sdcard上
  • events : 對應EventsCommand這個類,獲取accessibility events,你在命令行運行'uiautomator events'然后在鏈接設備上操作一下就會看到相應的事件打印出來
在Launcher里面有一個靜態預定義列表COMMANDS定義了這些Command:
/* 129 */   private static Command[] COMMANDS = { HELP_COMMAND, new RunTestCommand(), new DumpCommand(), new EventsCommand() }; 
這些命令,如我們的RunTestCommand類都是繼承與Command這個Launcher的靜態抽象內部類:
/*     */   public static abstract class Command /*     */   { /*     */     private String mName; /*     */      /*     */     public Command(String name) /*     */     { /*  40 */       this.mName = name; /*     */     } /*     */     public String name() /*     */     { /*  48 */       return this.mName; /*     */     } /*     */      /*     */     public abstract String shortHelp(); /*     */     public abstract String detailedOptions(); /*     */      /*     */     public abstract void run(String[] paramArrayOfString); /*     */   }
里面定義了一個mName的字串成員,其實對應的就是我們命令行傳進來的第一個參數,大家看下子類RunTestCommand這個類的構造函數就清楚了:
/*     */   public RunTestCommand() { /*  62 */     super("runtest"); /*     */   }
然后Command類還定義了一個run的方法,注意這個方法非常重要,這個就是我們剛才分析main函數看到的第二點,是開始運行測試的地方。
好,我們返回之前的main方法,看是怎么根據‘runtest'這個我們輸入的字串找到對應的RunTestCommand這個command的,我們打開findCommand這個方法:
/*     */   private static Command findCommand(String name) { /*  91 */     for (Command command : COMMANDS) { /*  92 */       if (command.name().equals(name)) { /*  93 */         return command; /*     */       } /*     */     } /*  96 */     return null; /*     */   }
跟我們預期一樣,該方法就是循壞COMMANDS這個預定義的靜態command列表,把上面提到的它們的nName取出來比較,然后找到對應的command對象的。

3. 準備運行

在獲取到我們對應的命令之后,下一步我們就需要根據命令行傳進來的參數來設置我們對應的command對象,以RunTestCommand為例,從main方法進入到run:
/*     */   public void run(String[] args) /*     */   { /*  67 */     int ret = parseArgs(args);                    ... /*  84 */     if (this.mTestClasses.isEmpty()) { /*  85 */       addTestClassesFromJars(); /*  86 */       if (this.mTestClasses.isEmpty()) { /*  87 */         System.err.println("No test classes found."); /*  88 */         System.exit(-3); /*     */       } /*     */     } /*  91 */     getRunner().run(this.mTestClasses, this.mParams, this.mDebug, this.mMonkey); /*     */   }
這里做了幾個事情:
  • 67行:根據命令行參數設置RunTestCommand的命令屬性
  • 84-85行:如果沒有-c參數指定測試類或者指定-e class,那么默認從指定的jar包里面獲取所有的測試class進行測試
  • 91行:獲取testrunner并執行run方法

3.1 設置命令運行參數

我們進入parseArgs里面看RunTestCommand是如何根據命令行參數來設置相應的變量的:
/*     */   private int parseArgs(String[] args) /*     */   { /* 105 */     for (int i = 0; i < args.length; i++) { /* 106 */       if (args[i].equals("-e")) { /* 107 */         if (i + 2 < args.length) { /* 108 */           String key = args[(++i)]; /* 109 */           String value = args[(++i)]; /* 110 */           if ("class".equals(key)) { /* 111 */             addTestClasses(value); /* 112 */           } else if ("debug".equals(key)) { /* 113 */             this.mDebug = (("true".equals(value)) || ("1".equals(value))); /* 114 */           } else if ("runner".equals(key)) { /* 115 */             this.mRunnerClassName = value; /*     */           } else { /* 117 */             this.mParams.putString(key, value); /*     */           } /*     */         } else { /* 120 */           return -1; /*     */         } /* 122 */       } else if (args[i].equals("-c")) { /* 123 */         if (i + 1 < args.length) { /* 124 */           addTestClasses(args[(++i)]); /*     */         } else { /* 126 */           return -2; /*     */         } /* 128 */       } else if (args[i].equals("--monkey")) { /* 129 */         this.mMonkey = true; /* 130 */       } else if (args[i].equals("-s")) { /* 131 */         this.mParams.putString("outputFormat", "simple"); /*     */       } else { /* 133 */         return -99; /*     */       } /*     */     } /* 136 */     return 0; /*     */   }
  • 106-117行:判斷是否有-e參數,有指定debug的話就啟動debug;有指定runner的就設置runner;有指定class的話就通過addTestClasses把該測試腳本類加入到mTestClasses列表;有指定其他鍵值對的就保存起來到mParams這個map里面,比如我們例子種是沒有指定debug和runner,但shell腳本自動會通過-e加上一個鍵值為jars的鍵值對,值就是我們的測試腳本jar包存放的路徑
  • 122-129行:判斷是否有-c參數,有的話就把對應的class加入到RunTestCommand對象的mTestClasses這個列表里面,注意每個class需要用逗號分開:
    /*     */   private void addTestClasses(String classes) /*     */   { /* 181 */     String[] classArray = classes.split(","); /* 182 */     for (String clazz : classArray) { /* 183 */       this.mTestClasses.add(clazz); /*     */     } /*     */   }
  • 其他參數處理...

3.2 獲取測試集(類)字串列表

處理好命令行參數后RunTestCommand的run方法下一個做的事情就是檢查mTestClasses這個字串類型列表是空的,根據上面的parseArgs方法的分析,如果命令行沒有指定-c或者沒有指定-e class,那么這個mTestClasses就為空,這種情況下就會把我們通過adb push進來的命令腳本jar包中的所有class加入到mTestClasses這個字串列表中,也就是說會執行里面的所有腳本。

3.3 獲取TestRunner

準備好命令參數和要執行的測試類后,下一步就要獲取對應的TestRunner來指導測試腳本的執行了,我們看下我們是怎么獲得TestRunner的:
/*     */   protected UiAutomatorTestRunner getRunner() { /* 140 */     if (this.mRunner != null) { /* 141 */       return this.mRunner; /*     */     } /*     */      /* 144 */     if (this.mRunnerClassName == null) { /* 145 */       this.mRunner = new UiAutomatorTestRunner(); /* 146 */       return this.mRunner; /*     */     } /*     */      /* 149 */     Object o = null; /*     */     try { /* 151 */       Class<?> clazz = Class.forName(this.mRunnerClassName); /* 152 */       o = clazz.newInstance(); /*     */     } catch (ClassNotFoundException cnfe) { /* 154 */       System.err.println("Cannot find runner: " + this.mRunnerClassName); /* 155 */       System.exit(-4); /*     */     } catch (InstantiationException ie) { /* 157 */       System.err.println("Cannot instantiate runner: " + this.mRunnerClassName); /* 158 */       System.exit(-4); /*     */     } catch (IllegalAccessException iae) { /* 160 */       System.err.println("Constructor of runner " + this.mRunnerClassName + " is not accessibile"); /* 161 */       System.exit(-4); /*     */     } /*     */     try { /* 164 */       UiAutomatorTestRunner runner = (UiAutomatorTestRunner)o; /* 165 */       this.mRunner = runner; /* 166 */       return runner; /*     */     } catch (ClassCastException cce) { /* 168 */       System.err.println("Specified runner is not subclass of " + UiAutomatorTestRunner.class.getSimpleName()); /*     */        /* 170 */       System.exit(-4); /*     */     } /*     */      /* 173 */     return null; /*     */   }
這個類看上去有點長,但其實做的事情重要的就那么兩點,其他的都是些錯誤處理:
  • 用戶有沒有在命令行通過-e runner指定TestRunner,有的話就用該TestRunner
  • 用戶沒有指定TestRunner的話就用默認的UiAutomatorTestRunner

3.4 每個方法建立junit.framework.TestCase

確定了UiAutomatorTestRunner這個TestRunner后的下一步就是調用它的run方法來指導測試用例的執行:
/*     */   public void run(List<String> testClasses, Bundle params, boolean debug, boolean monkey) /*     */   {                 ... /*  92 */     this.mTestClasses = testClasses; /*  93 */     this.mParams = params; /*  94 */     this.mDebug = debug; /*  95 */     this.mMonkey = monkey; /*  96 */     start(); /*  97 */     System.exit(0); /*     */   }
傳進來的參數就是我們剛才通過parseArgs方法設置的那些變量,run方法會把這些變量保存起來以便下面使用,緊跟著它就會調用一個start方法,這個方法非常重要,從建立每個測試方法對應的junit.framwork.TestCase對象到真正執行測試都在這個方法完成,所以也比較長,我們挑重要的部分進行分析,首先我們看以下代碼:
/*     */   protected void start() /*     */   { /* 104 */     TestCaseCollector collector = getTestCaseCollector(getClass().getClassLoader()); /*     */     try { /* 106 */       collector.addTestClasses(this.mTestClasses); /*     */     }         ... }
這里面調用了TestCaseCollector這個類的addTestClasses的方法,從這個類的名字我們可以猜測到它就是專門收集測試用例用的,那么我們往下跟蹤下看它是怎么收集測試用例的:
/*     */   public void addTestClasses(List<String> classNames) /*     */     throws ClassNotFoundException /*     */   { /*  52 */     for (String className : classNames) { /*  53 */       addTestClass(className); /*     */     } /*     */   }
這里傳進來的就是我們上面保存起來的收集了每個class名字的字串列表。里面執行了一個for循環來把每一個類的字串拿出來,然后調用addTestClass:
/*     */   public void addTestClass(String className) /*     */     throws ClassNotFoundException /*     */   { /*  66 */     int hashPos = className.indexOf('#'); /*  67 */     String methodName = null; /*  68 */     if (hashPos != -1) { /*  69 */       methodName = className.substring(hashPos + 1); /*  70 */       className = className.substring(0, hashPos); /*     */     } /*  72 */     addTestClass(className, methodName); /*     */   }
這里可能你會奇怪為什么會查看類名字串里面是否有#號呢?其實在文章開頭的時候我就有提出來,-c或者-e class指定的類名是可以支持 $className/$methodName來指定執行該className的methodName這個方法的,比如我可以指定-c majcit.com.UIAutomatorDemo.SettingsSample#testSetLanEng來指定只是測試該類里面的testSetLanEng這個方法。如果用戶沒有指定的話該methodName變量就設置成null,然后調用重載方法addTestClass方法:
/*     */   public void addTestClass(String className, String methodName) /*     */     throws ClassNotFoundException /*     */   { /*  84 */     Class<?> clazz = this.mClassLoader.loadClass(className); /*  85 */     if (methodName != null) { /*  86 */       addSingleTestMethod(clazz, methodName); /*     */     } else { /*  88 */       Method[] methods = clazz.getMethods(); /*  89 */       for (Method method : methods) { /*  90 */         if (this.mFilter.accept(method)) { /*  91 */           addSingleTestMethod(clazz, method.getName()); /*     */         } /*     */       } /*     */     } /*     */   }
  • 84行:最終會調用 java.lang.ClassLoader的loadClass方法,通過指定類的名字來把該測試腳本類裝載進來并賦予給clazz這個Class<?>變量,注意這里這個測試類還沒有實例化的,真正實例化的地方是在下面的addSingleTestMethod中
  • 85-86行:如果用戶用#號指定測試某一個類的某個方法,那么就直接傳入參數clazz和要測試的methodName來調用addSingleTestMehod來組建我們需要的TestCase
  • 88-91行:如果用戶沒用#號指定測試某個類的某個方法,那么就需要循環取出該類的所有測試方法,然后每個方法調用一次addSingleTestMethod.
好,終于來到的關鍵點,下面我們看addSingleTestMethod是如何根據測試類clazz和它的一個方法創建一個junit.framework.TestCase對象的:
/*     */   protected void addSingleTestMethod(Class<?> clazz, String method) { /* 106 */     if (!this.mFilter.accept(clazz)) { /* 107 */       throw new RuntimeException("Test class must be derived from UiAutomatorTestCase"); /*     */     } /*     */     try { /* 110 */       TestCase testCase = (TestCase)clazz.newInstance(); /* 111 */       testCase.setName(method); /* 112 */       this.mTestCases.add(testCase); /*     */     } catch (InstantiationException e) { /* 114 */       this.mTestCases.add(error(clazz, "InstantiationException: could not instantiate test class. Class: " + clazz.getName())); /*     */     } /*     */     catch (IllegalAccessException e) { /* 117 */       this.mTestCases.add(error(clazz, "IllegalAccessException: could not instantiate test class. Class: " + clazz.getName())); /*     */     } /*     */   }
  • 106-107行:這一個判斷非常的重要,我們的測試腳本必須都是繼承于UiAutomatorTestCase的,否則不支持!
  • 110行:把測試用例類進行初始化獲得一個實例對象,然后強制轉換成junit.framework.TestCase類型,這里要注意我們測試腳本的父類UiAutomationTestCase也是繼承與junit.framework.TestCase的
  • 111行:設置junit.framework.TestCase實例對象的方法名字,這個很重要,下一章節可以看到junit框架會通過它來找到我們測試腳本中要執行的那個方法
  • 112行:把這個TestCase對象增加到當前TestCaseCollector的mTestCases這個junit.framework.TestCase類型的列表里面
這個小節代碼稍微多了點,其實簡單來說就是UiAutomatorTestRunner在指導測試用例怎么跑的時候,會去請求TestCaseController去把用戶傳進來的測試類名字字串列表中的每個類對應的每個方法轉換成junit.framework.TestCase,并把這些TestCase保存在TestCaseCollector對象的mTestCases這個列表里面。
這里千萬要注意的一點是;并非一個測試腳本(類)一個TestCase,而是一個方法創建一個TestCase!

3.5 初始化UiAutomationShellWrapper并連接上AccessibilityService來設置Monkey模式

上面UiAutomatorTestRunner的start方法在調用完TestCaseCollector來建立TestCase列表后,會嘗試建立AccessibilityService的連接,來看是否應該把UiAutomation設置成Monkey運行模式:
/*     */   protected void start() /*     */   {         ... /* 117 */     UiAutomationShellWrapper automationWrapper = new UiAutomationShellWrapper(); /* 118 */     automationWrapper.connect(); /*     */              ... /*     */     try { /* 132 */       automationWrapper.setRunAsMonkey(this.mMonkey);                     ...               }     ... }
這里會初始化一個UiAutomationShellWrapper的類,其實這個類如其名,就是UiAutomation的一個Wrapper,初始化好后最終會調用UiAutomation的connect方法來連接上AccessibilityService服務,然后就可以調用AccessibilityService相應的API來把UiAutomation設置成Monkey模式來運行了。而在我們的例子中我們沒有指定monkey模式的參數,所以是不會設置monkey模式的。
至于什么是Monkey模式,我說了不算,官方說了算:
Applications can query whether they are executed in a "monkey" mode, i.e. run by a test framework, and avoid doing potentially undesirable actions such as calling 911 or posting on public forums etc.
也就是說設置了這個模式之后,一些應用會調用我們《Android4.3引入的UiAutomation新框架官方簡介》提到的isUserMonkey()這個著名的api來判斷究竟是不是一個測試腳本在要求本應用做事情,那么判斷如果是的話就不要讓它做一些意想不到的如撥打911的事情。不然你一個測試腳本寫錯了,一個死循環一個晚上在撥打911,保管警察第二天上你公司找你。

3.6 初始化UiDevice和UiAutomationBridge

在所有要運行的基于每個方法的TestCase都準備好之后,我們還不能直接去調用junit.framework.TestCase的run方法來執行該方法,我們還需要做幾個很重要的事情:
  • 初始化一個UiDevice對象
  • 每執行一個測試方法之前必須給該腳本傳入該UiDevice對象。大家寫過UiAutomator腳本的應該都知道UiDevce不是調用構造函數而是通過getUiDevice獲得的,而getUiDevice其實就是我們的測試腳本的父類UiAutomatorTestCase的方法,往后我們會看到它們是怎么聯系起來的
好,我們繼續分析上面UiAutomatorTestRunner的start方法,上面一小節它完成了測試用例每個方法對應的junit.framework.TestCase對象的建立,那么往下:
/*     */   protected void start() /*     */   {         ... /*     */     try { /* 132 */       automationWrapper.setRunAsMonkey(this.mMonkey); /* 133 */       this.mUiDevice = UiDevice.getInstance(); /* 134 */       this.mUiDevice.initialize(new ShellUiAutomatorBridge(automationWrapper.getUiAutomation())); /*     */                   ...        }     ... } 
在嘗試設置monkey模式之后,UiAutomatorTestRunner會去實例化一個UiDevice,實例化后會通過以下步驟對其進行初始化:
  • 首先獲取上一小節提到的UiAutomationShellWrapper這個Wrapper里面的UiAutomation實例,注意這個實例在上一小節中已經連接上AccessiblityService的了
  • 以這個連接好的UiAutomation為參數構造一個ShellUiAutomatorBridge,注意這里不是UiAutiomatorBridge。ShellUiAutomatorBridge時繼承于UiAutomatorBridge的一個子類,里面實現了額外的幾個不需要通過UiAutomation的操作,比如getRotation等是通過WindowManager來實現的
  • 最后通過調用UiDevice的initialize這個方法傳入ShellUiAutomatorBridge的實例來初始化我們的UiDevice
  • 完成以上的初始化后,我們就擁有了一個已經通過UiAutomation連接上設備的AccessibilityService的UiDevice了,這樣我們就可以隨意調用AccessibilityService API來為我們服務了
這里提到的一些類也許對你會有點陌生,本人接下來會另外開文章去進行描述。

4. 啟動junit測試

到現在位置似乎所有東西都準備好了:
  • 每個測試用例中的每個測試方法對應的junit.framework.TestCase建立好
  • 已經連接上AccessibilityService的UiDevice準備好
那么我們是不是就可以立刻直接調用junit.framework.TestCase的run開始執行測試方法呢?既然以這種調調來提問,答案可想而知肯定不是的了。那么為什么還不能運行呢?既然這些都準備好了。其實這里問題是UiDevice,確實,上面的UiDevice實例已經擁有一個UiAutomation對象,且該對象已經連接上AccessibilityService服務,但是你要知道這個UiDevice對象現在是UiAutomatorTestRunner這個類的對象擁有的,而我們的測試腳本并沒有繼承或者擁有這個類的變量。請看以下的測試腳本:
package majcit.com.UIAutomatorDemo;  import com.android.uiautomator.core.UiDevice; import com.android.uiautomator.core.UiObject; import com.android.uiautomator.core.UiObjectNotFoundException; import com.android.uiautomator.core.UiScrollable; import com.android.uiautomator.core.UiSelector; import com.android.uiautomator.testrunner.UiAutomatorTestCase;   public class UISelectorFindElementTest extends UiAutomatorTestCase { 	 	 public void testDemo() throws UiObjectNotFoundException {   	        UiDevice device = getUiDevice(); 	        device.pressHome();  
既然測試腳本中的getUiDevice方法不是直接從UiAutomatorTestRunner獲得,那么是不是從它繼承下來的UiAutomatorTestCase中獲得呢?答案是肯定的,我們繼續看那個UiAutomatorTestRunner中很重要的start方法:
/*     */  /*     */   protected void start() /*     */   {                 ... /* 158 */       for (TestCase testCase : testCases) { /* 159 */         prepareTestCase(testCase); /* 160 */         testCase.run(testRunResult); /*     */       }                 ... }
一個for循環把我們上面創建好的所有junit.framework.TestCase對象做一個遍歷,在執行之前先調用一個prepareTestCase:
/*     */   protected void prepareTestCase(TestCase testCase) /*     */   { /* 427 */     ((UiAutomatorTestCase)testCase).setAutomationSupport(this.mAutomationSupport); /* 428 */     ((UiAutomatorTestCase)testCase).setUiDevice(this.mUiDevice); /* 429 */     ((UiAutomatorTestCase)testCase).setParams(this.mParams); /*     */   }
這個方法所做的事情就解決了我們剛才的疑問:第428行,把當前UiAutomatorTestRunner擁有的這個已經連接到AccessibilityService的UiObject對象,通過我們測試腳本的父類的setUiDevice方法設置到我們的TestCase腳本對象里面
/*     */   void setUiDevice(UiDevice uiDevice) /*     */   { /* 100 */     this.mUiDevice = uiDevice; /*     */   }
這樣我們測試腳本每次執行getUiDevice的時候就能直接取得該對象了:
/*     */   public UiDevice getUiDevice() /*     */   { /*  72 */     return this.mUiDevice; /*     */   }
從整個過程可以看到,UiObject的對象我們在測試腳本上是不用初始化的,它是在運行時由我們默認的TestuRunner -- UiAutomatorTestRunner 傳遞進來的,這個我們作為測試人員是不需要知道這一點的。
好了,到了現在就真的可以直接觸發junit.framework.TestCase的run方法來讓測試跑起來了,這里要注意我們之前的分析,并不是測試腳本的所有方法都同時調用run執行的,而是一個方法調用一次run方法。

5. 擴展閱讀:junit框架如何通過方法名執行測試方法

下面如果有興趣知道juint框架是如何通過3.4節建立junit.framework.TestCase時調用setName方法設置的測試方法名字來調用執行對應方法的可以繼續往下跟蹤run方法,它最終會進入到junit.framework.TestCase的runTest方法
	protected void runTest() throws Throwable { 		assertNotNull(fName); // Some VMs crash when calling getMethod(null,null); 		Method runMethod= null; 		try { 			// use getMethod to get all public inherited 			// methods. getDeclaredMethods returns all 			// methods of this class but excludes the 			// inherited ones. 			runMethod= getClass().getMethod(fName, (Class[])null); 		} catch (NoSuchMethodException e) { 			fail("Method \""+fName+"\" not found"); 		} 		if (!Modifier.isPublic(runMethod.getModifiers())) { 			fail("Method \""+fName+"\" should be public"); 		}  		try { 			runMethod.invoke(this, (Object[])new Class[0]); 		} 		catch (InvocationTargetException e) { 			e.fillInStackTrace(); 			throw e.getTargetException(); 		} 		catch (IllegalAccessException e) { 			e.fillInStackTrace(); 			throw e; 		} 	}
從中可以看到它會嘗試通過getClass().getMethod方法獲得這個junit.framework.TestCase所代表的測試腳本的于我們設置的fName一致的方法,然后才會去執行。


 

作者

自主博客

微信

CSDN

天地會珠海分舵

http://techgogogo.com


服務號:TechGoGoGo

掃描碼:

UIAutomator源碼分析之啟動和運行

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

湖南省| 四平市| 福安市| 嘉黎县| 伊吾县| 额尔古纳市| 桃园县| 轮台县| 白城市| 吉林市| 迭部县| 长汀县| 保定市| 梁平县| 双辽市| 靖西县| 贡嘎县| 黄大仙区| 庆阳市| 庆云县| 冕宁县| 德格县| 淳化县| 滦南县| 静海县| 翼城县| 鄂托克前旗| 乡城县| 巢湖市| 尚志市| 台北市| 德阳市| 通山县| 延吉市| 兴化市| 云林县| 黑龙江省| 滦南县| 应城市| 亚东县| 曲周县|