译者序:Google在Android 4.3发布时提供了一套新的UiAutomation框架来支持用户界面自动化测试,该框架通过运用已有的Accessibility APIs来模拟用户跟设备用户界面的交互:比如获取窗口界面控件和注入事件等。如在4.3之前UiAutomator工具是通过InputManager或者更早的WindowManager来注入KeyEvent等,4.3之后用的就是新框架UiAutomation使用的Accessibility APIs来注入事件了。
Class Overview/概览
Class for interacting with the device's UI by simulation user actions and introspection of the screen content. It relies on the platform accessibility APIs to introspect the screen and to perform some actions on the remote view tree. It also allows injecting of arbitrary raw input events simulating user interaction with keyboards and touch devices. One can think of a UiAutomation as a special type of AccessibilityService which does not provide hooks for the service life cycle and exposes other APIs that are useful for UI test automation.
这是一个通过模拟用户操作来与设备用户界面交互以及获取屏幕内容的类。它依赖于平台的辅助功能APIs来在远程的控件树上获取屏幕内容以及执行一些操作。同时它也允许通过注入原生事件(译者注:指的就是InputEvent. KeyEvent也是继承于InputEvent的,所以说它是原生事件)来模拟用户的按键和触屏操作。我们可以认为UiAutomation就是一个特殊类型的AccessibilityService,其既不会为控制服务的生命周期而提供钩子函数,也不会暴露任何其他可以直接用于用户界面测试自动化的APIs.
The APIs exposed by this class are low-level to maximize flexibility when developing UI test automation tools and libraries. Generally, a UiAutomation client should be using a higher-level library or implement high-level functions. For example, performing a tap on the screen requires construction and injecting of a touch down and up events which have to be delivered to the system by a call to injectInputEvent(InputEvent, boolean).
这个类暴露出来的APIs是很低层的,目的就是为了在开发用户界面测试自动化框架和库时提供最大的弹性。总的来说,一个UiAutomation客户端应该使用一些(基于UiAutomation的)更高层次的库或者实现更高层次的方法。比如,模拟一个用户在屏幕上的点击事件需要构造并注入一个按下和一个弹起事件,然后必须调用UiAutomation的一个injectInputEvent(InputEvent, boolean)的调用来发送给操作系统。
The APIs exposed by this class operate across applications enabling a client to write tests that cover use cases spanning over multiple applications. For example, going to the settings application to change a setting and then interacting with another application whose behavior depends on that setting.
这个类暴露出来的APIs可以跨应用,这样用户就可以编写可以跨越多个应用的测试用例脚本了。比如,打开系统的设置应用去修改一些设置然后再与另外一个依赖于该设置的应用进行交互(译者注:这个在instrumentation这个框架可以做不到的)。
Testing and Debugging(来自android 4.3的APIs官方改动文档)
Automated UI testing/用户界面测试自动化
The new UiAutomation class provides APIs that allow you to simulate user actions for test automation. By using the platform's AccessibilityService APIs, the UiAutomation APIs allow you to inspect the screen content and inject arbitrary keyboard and touch events.
新的UiAutomation这个类提供了一系列的APIs来允许你在测试自动化时模拟用户的操作。通过封装使用了平台上的AccessibilityService APIs, UiAutomation APIs允许你获取窗口(控件)内容并且注入按键和触屏事件。
To get an instance of UiAutomation, call Instrumentation.getUiAutomation(). In order for this to work, you must supply the -w option with the instrument command when running your InstrumentationTestCase from adb shell.
你可以通过调用Instrumentation.getUiAutomation()来获得UiAutomation的一个实例。为了让它工作起来,当你在adb shell上运行你的InstrumentationTestCase的时候你还需要为instrument命令提供-w这个选项。
With the UiAutomation instance, you can execute arbitrary events to test your app by callingexecuteAndWaitForEvent(), passing it a Runnable to perform, a timeout period for the operation, and an implementation of the UiAutomation.AccessibilityEventFilter interface. It's within yourUiAutomation.AccessibilityEventFilter implementation that you'll receive a call that allows you to filter the events that you're interested in and determine the success or failure of a given test case.
通过UiAutomation的实例,你可以调用其executeAndWaitForEvent()对你的应用注入不同的事件来进行测试:该函数会接受一个可执行 Runnable线程对象用来执行事件注入操作,一个操作超时,以及一个实现了UiAutomation.AccessibilityEventFilter的类的实例。正是在这个UiAutomation.AccessibilityEventFilter实现类里面你会收到一个回调来让你过滤那些你喜欢的事件并决定你的测试用例是否通过。
To observe all the events during a test, create an implementation of UiAutomation.OnAccessibilityEventListenerand pass it to setOnAccessibilityEventListener(). Your listener interface then receives a call toonAccessibilityEvent() each time an event occurs, receiving an AccessibilityEvent object that describes the event.
如果要在测试时监控所有的事件,你需要创建一个UiAutomation.OnAccessibilityEventListener的实现类然后把它的实例传递给setOnAccessibilityEventListener()。你的监听接口将会在每次有事件触发的时候接收到一个发送给onAccessibilityEvent()的回调,里面的参数就是一个描述该事件的AccessibilityEvent 的对象
There is a variety of other operations that the UiAutomation APIs expose at a very low level to encourage the development of UI test tools such as uiautomator. For instance, UiAutomation can also:
UiAutomation APIs还暴露了很多其他的低层次的操作来鼓励大家去开发如uiautomator这样的用户界面测试工具。比如UiAutomation还可以做以下事情:
- Inject input events/注入事件
- Change the orientation of the screen/改变屏幕的方向
- Take screenshots/截屏
And most importantly for UI test tools, the UiAutomation APIs work across application boundaries, unlike those inInstrumentation.
最为用户界面自动化测试工具,最重要的一点是,UiAutomation APIs 可以跨应用工作,而不像Instrumentation提供了的那些APIs.
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
- 支持三个子命令: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 |
一个UiAutomation的wrapper类,简单的做了封装,其中提供了一个setRunAsMonkey的方法来通过ActivityManagerNativeProxy来设置系统的运行模式 |
|
UiAutomatorBridge |
com.android.uiautomator.core |
相当于UiAutomation的代理,基本上所有和UiAutomation打交道的方法都是通过它来分发的 |
|
ShellUiAutomatorBridge |
com.android.uiautomator.core |
UiAutomatorBridge的子类,额外增加了几个不需要用到UiAutomation的方法,如getRotation |
1.环境变量配置
- 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
- 首先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
2. 子命令定位
- /* */ 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方法开始执行测试
- runtest :对应RunTestCommand这个类,代表运行相应测试的命令
- dump : 对应DumpCommand这个类,dump当前窗口控件信息,你在命令行运行‘uiautomator dump’就会把当前ui的hierarchy信息dump成一个文件默认放到sdcard上
- events : 对应EventsCommand这个类,获取accessibility events,你在命令行运行'uiautomator events'然后在链接设备上操作一下就会看到相应的事件打印出来
- /* 129 */ private static Command[] COMMANDS = { HELP_COMMAND, new RunTestCommand(), new DumpCommand(), new EventsCommand() };
- /* */ 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);
- /* */ }
- /* */ public RunTestCommand() {
- /* 62 */ super("runtest");
- /* */ }
- /* */ private static Command findCommand(String name) {
- /* 91 */ for (Command command : COMMANDS) {
- /* 92 */ if (command.name().equals(name)) {
- /* 93 */ return command;
- /* */ }
- /* */ }
- /* 96 */ return null;
- /* */ }
- /* */ 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 设置命令运行参数
- /* */ 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 获取测试集(类)字串列表
3.3 获取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
- /* */ 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);
- /* */ }
- /* */ protected void start()
- /* */ {
- /* 104 */ TestCaseCollector collector = getTestCaseCollector(getClass().getClassLoader());
- /* */ try {
- /* 106 */ collector.addTestClasses(this.mTestClasses);
- /* */ }
- ...
- }
- /* */ public void addTestClasses(List<String> classNames)
- /* */ throws ClassNotFoundException
- /* */ {
- /* 52 */ for (String className : classNames) {
- /* 53 */ addTestClass(className);
- /* */ }
- /* */ }
- /* */ 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);
- /* */ }
- /* */ 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.
- /* */ 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类型的列表里面
- /* */ protected void start()
- /* */ {
- ...
- /* 117 */ UiAutomationShellWrapper automationWrapper = new UiAutomationShellWrapper();
- /* 118 */ automationWrapper.connect();
- /* */
- ...
- /* */ try {
- /* 132 */ automationWrapper.setRunAsMonkey(this.mMonkey);
- ...
- }
- ...
- }
也就是说设置了这个模式之后,一些应用会调用我们《Android4.3引入的UiAutomation新框架官方简介》提到的isUserMonkey()这个著名的api来判断究竟是不是一个测试脚本在要求本应用做事情,那么判断如果是的话就不要让它做一些意想不到的如拨打911的事情。不然你一个测试脚本写错了,一个死循环一个晚上在拨打911,保管警察第二天上你公司找你。
- 初始化一个UiDevice对象
- 每执行一个测试方法之前必须给该脚本传入该UiDevice对象。大家写过UiAutomator脚本的应该都知道UiDevce不是调用构造函数而是通过getUiDevice获得的,而getUiDevice其实就是我们的测试脚本的父类UiAutomatorTestCase的方法,往后我们会看到它们是怎么联系起来的
- /* */ protected void start()
- /* */ {
- ...
- /* */ try {
- /* 132 */ automationWrapper.setRunAsMonkey(this.mMonkey);
- /* 133 */ this.mUiDevice = UiDevice.getInstance();
- /* 134 */ this.mUiDevice.initialize(new ShellUiAutomatorBridge(automationWrapper.getUiAutomation()));
- /* */
- ...
- }
- ...
- }
- 首先获取上一小节提到的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准备好
- 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();
- /* */
- /* */ protected void start()
- /* */ {
- ...
- /* 158 */ for (TestCase testCase : testCases) {
- /* 159 */ prepareTestCase(testCase);
- /* 160 */ testCase.run(testRunResult);
- /* */ }
- ...
- }
- /* */ protected void prepareTestCase(TestCase testCase)
- /* */ {
- /* 427 */ ((UiAutomatorTestCase)testCase).setAutomationSupport(this.mAutomationSupport);
- /* 428 */ ((UiAutomatorTestCase)testCase).setUiDevice(this.mUiDevice);
- /* 429 */ ((UiAutomatorTestCase)testCase).setParams(this.mParams);
- /* */ }
- /* */ void setUiDevice(UiDevice uiDevice)
- /* */ {
- /* 100 */ this.mUiDevice = uiDevice;
- /* */ }
- /* */ public UiDevice getUiDevice()
- /* */ {
- /* 72 */ return this.mUiDevice;
- /* */ }
5. 扩展阅读:junit框架如何通过方法名执行测试方法
- 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;
- }
- }
浙公网安备 33010602011771号