- 用户有没有在命令行通过-e runner指定TestRunner,有的话就用该TestRunner
- 用户没有指定TestRunner的话就用默认的UiAutomatorTestRunner
3.4 每个方法建立junit.framework.TestCase
确定了UiAutomatorTestRunner这个TestRunner后的下一步就是调用它的run方法来指导测试用例的执行:
传进来的参数就是我们刚才通过parseArgs方法设置的那些变量,run方法会把这些变量保存起来以便下面使用,紧跟着它就会调用一个
start方法,这个方法非常重要,从建立每个测试方法对应的junit.framwork.TestCase对象到真正执行测试都在这个方法完成,所以也比较长,我们挑重要的部分进行分析,首先我们看以下代码:
这里面调用了TestCaseCollector这个类的addTestClasses的方法,从这个类的名字我们可以猜测到它就是专门收集测试用例用的,那么我们往下跟踪下看它是怎么收集测试用例的:
这里传进来的就是我们上面保存起来的收集了每个class名字的字串列表。里面执行了一个for循环来把每一个类的字串拿出来,然后调用addTestClass:
这里可能你会奇怪为什么会查看类名字串里面是否有#号呢?其实在文章开头的时候我就有提出来,-c或者-e class指定的类名是可以支持 $className/$methodName来指定执行该className的methodName这个方法的,比如我可以指定-c majcit.com.UIAutomatorDemo.SettingsSample#testSetLanEng来指定只是测试该类里面的testSetLanEng这个方法。如果用户没有指定的话该methodName变量就设置成null,然后调用重载方法addTestClass方法:
- 84行:最终会调用 java.lang.ClassLoader的loadClass方法,通过指定类的名字来把该测试脚本类装载进来并赋予给clazz这个Class<?>变量,注意这里这个测试类还没有实例化的,真正实例化的地方是在下面的addSingleTestMethod中
- 85-86行:如果用户用#号指定测试某一个类的某个方法,那么就直接传入参数clazz和要测试的methodName来调用addSingleTestMehod来组建我们需要的TestCase
- 88-91行:如果用户没用#号指定测试某个类的某个方法,那么就需要循环取出该类的所有测试方法,然后每个方法调用一次addSingleTestMethod.
好,终于来到的关键点,下面我们看addSingleTestMethod是如何根据测试类clazz和它的一个方法创建一个junit.framework.TestCase对象的:
- 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运行模式:
这里会初始化一个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对象的建立,那么往下:
在尝试设置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方法:
好了,到了现在就真的可以直接触发junit.framework.TestCase的run方法来让测试跑起来了,这里要注意我们之前的分析,并不是测试脚本的所有方法都同时调用run执行的,而是一个方法调用一次run方法。
下面如果有兴趣知道juint框架是如何通过3.4节建立junit.framework.TestCase时调用setName方法设置的测试方法名字来调用执行对应方法的可以继续往下跟踪run方法,它最终会进入到junit.framework.TestCase的runTest方法