本篇讲解的是利用Instrumentation单元测试模块来控制以获取root权限的手机进行操作微信的简单基础讲解,以一个自己写的一个成品Demo(能够跑出想要的结果,但是博客无法上传视频文件所以只能你们将代码粘贴进你们自己的工程来测试了)作为核心进行介绍,想要实现的是通过使用通信Tcp或是Socket进行数据指令的收发,来进行相关控制微信或是手机的操作,本文不再累赘的讲解Socket或是其它通信方式,只针对控制端的指令进行基本的简单介绍,其实这里我也是接触不多,讲解的会很有局限性,只针对自己实现的这些功能做为引导,也没有时间对控制的所有指令进行总结,还望谅解...下面进入正题进行介绍:
我们都知道测试模块是写在androidTest的这个包下的..对于测试本工程的某个方法执行的是否正确,可以直接在哪个要测试的方法中右键生成测试类,通过编写代码来测试当前方法在使用上是否正确
举一个例子、写一个加法运算的方法,生成此方法的测试类如下所示:
public class Calculator { public double sum(double a, double b){ return a + b; } }上面是Calculator类有个加法运算的类,下面的是这个类生成的测试类,@Before中进行初始化和准备工作,@Test中写测试代码块儿,在下图中我对预期值和执行值进行对比,预期值和运行值相等则程序跑通,否则失败。
public class CalculatorTest { private Calculator mCalculator; @Before public void setUp() throws Exception { mCalculator = new Calculator(); } @Test public void sum() throws Exception { //expected: 6, sum of 1 and 5 assertEquals(6d, mCalculator.sum(1d, 5d), 0); } }已上是比较基本的东西,对没有接触过的人进行一个简单的介绍,了解这个东西大体是个什么样子做什么的;他还可以控制你应用UI界面的按钮调取手机输入法自行输入文字等操作,能够实现这样的操作后我就在想,他如果可以实现这样的功能,那它可不可以控制任意一台手机中的任意应用,来自动化的实现点击、后退、滚动屏幕、锁屏等等一系列的操作呢?答案是肯定的...接下来就是自己写的一个在代码中预先设置好执行什么操作的Test部分,准备工作是:一台root过的安卓手机;安装了一个微信并登录完成;使用android studio进行编程。(我会在最下面提供下载项目的地址,把项目下载下来自己把需要的jar包自己复制出来吧)
首先需要引入一些包...很抱歉的是因为自己测试的时候引入了挺多的jar包,最后测试成功后也没有把不需要的jar包剔除出去,所以就暂且都加进去吧~~
import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.support.test.uiautomator.By; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiObject; import android.support.test.uiautomator.UiObjectNotFoundException; import android.support.test.uiautomator.UiScrollable; import android.support.test.uiautomator.UiSelector; import android.support.test.uiautomator.Until; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import static android.support.test.espresso.matcher.ViewMatchers.assertThat; import static org.hamcrest.Matchers.notNullValue; /** * Created by sp01 on 2017/5/26. */ @RunWith(AndroidJUnit4.class) public class UUTest { private UiDevice uiDevice; @Before public void setUp(){ // 初始化 uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); String launcherPackageName = getLauncherPackageName(); assertThat(launcherPackageName,notNullValue()); uiDevice.wait(Until.hasObject(By.pkg(launcherPackageName).depth(0)),5000); } @Test public void testDemo() throws UiObjectNotFoundException{ uiDevice.pressHome(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } uiDevice.pressHome(); // 进入设置菜单 UiObject settingApp = new UiObject(new UiSelector().text("微信")); settingApp.click(); // 休眠三秒 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } try { new UiObject(new UiSelector().text("通讯录")).clickAndWaitForNewWindow(2000); UiScrollable scrollable = new UiScrollable(new UiSelector().scrollable(true)); UiObject uiObject = scrollable.getChildByText(new UiSelector().text("温油的老大"),"温油的老大",true); if (uiObject != null){ uiObject.clickAndWaitForNewWindow(2000); new UiObject(new UiSelector().text("发消息")).clickAndWaitForNewWindow(2000); // UiScrollable set = new UiScrollable(new UiSelector().scrollable(true)); // UiObject language = set.getChildByText(new UiSelector().text("Language & input"),"Language & input",true); // language.clickAndWaitForNewWindow(); uiDevice.pressBack(); new UiObject(new UiSelector().text("通讯录")).clickAndWaitForNewWindow(2000); } UiScrollable scrollable1 = new UiScrollable(new UiSelector().scrollable(true)); UiObject uiObject1 = scrollable1.getChildByText(new UiSelector().text("新的朋友"),"新的朋友",true); if (uiObject1 != null){ uiObject1.clickAndWaitForNewWindow(2000); } uiDevice.pressBack(); }catch (Exception e){ } try { new UiObject(new UiSelector().text("群聊")).clickAndWaitForNewWindow(2000); new UiObject(new UiSelector().text("铁铁铁")).clickAndWaitForNewWindow(2000); uiDevice.pressBack(); }catch (Exception e){ } } private String getLauncherPackageName(){ Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_HOME); PackageManager pm = InstrumentationRegistry.getContext().getPackageManager(); ResolveInfo resolveInfo = pm.resolveActivity(intent,PackageManager.MATCH_DEFAULT_ONLY); return resolveInfo.activityInfo.packageName; } }
需要注意的是有两个jar包的引包需要手动的去添加,不然敲不出来...后来我clean 加 rebuild project解决了问题,不知道你们会不会遇到相同的问题;不要一股脑的就复制粘贴,能自己敲敲尝试下最好,再就是里面的操作是根据显示的文本信息来进行搜索的,你的微信好友中可不会跟我有一样备注名的好友呦~~~之后右键点击Run xxTest 或是切换到你要运行的单元测试类点击小三角来运行~运行效果因为博客的条件不能展示出来,需要你自己测试看一下效果了。
如果能够成功运行之后我们就要想办法如何通过另一个App来控制这个应用执行这些操作了,因为预想的是通过socket通信接收指令来执行某些控制手机UI的操作,所以要摆脱单元测试传统的仅测试的目的,在成功运行后观察控制台会有如下显示:
其中的命令am instrument -w -r -e debug false -e class sq.test_instrumentation.UUTest sq.test_instrumentation.test/android.support.test.runner.AndroidJUnitRunner 是需要你记录下来的,这是你执行此测试模块的命令行,之后在通过调用root权限,用代码的模式写入命令来实现控制,下面的obj就是上面你记录的命令。
Runtime.getRuntime().exec(new String[]{"/system/bin/su","-c", obj});
当你收到一个指令,无论是Socket通信也好还是通过网络请求获取的操作信息也好,让你实现这一系列的操作时,你就可以通过各接口的判断来实现不同的操作,我做的是通过PC端ServerSocket发送指令,手机端socket接收到信息来实现以上的操作,实现了开始预想的可行性,只不过当收到指令退出了当前应用进入到微信界面的时候,socket就会断开连接,所以要保持常连接还需要写在service中,这不在本文章的讨论中,而service也不是一直不断开的,所以流氓点的开启两个service相互监听,除去卸载应用外,不然服务service不会因内存或其他原因而异常断开的。
自己简单的总结了一些东西不完整但是都是正确且手打的:
1)@before 准备初始化 @Test 测试代码块 @after 做例如关流等操作 (如此可以减少代码的冗余)
2)JUnit提供的assert方法,源于Assert这个类中,常用的方法包括:
assertEquals(exected,actual)预期 与 当前测试的对比
assertEquals(expected,actual,tolerance)传入float或double类型,有偏差的时候最后的参数表示误差范围
assertFalse与assertTure(boolean condition)验证condition的值为true或是false
assertNull与assertNotNull(object obj)验证obj值不为空或为空
assertSame与assertNotSame(expected,actual)判断是否为同一个对象,来指向相同或是不同对象
3)测试支持库特性:
Automator:Ui测试框架,使用于跨应用的功能UI测试及安装使用
UiDevice类:可以进行的操作包括例如-旋转设备-点击按钮-截图等操作
UiObject表示:在UI上可见的UI元素
UiScrollable表示:可滚动UI容器提供支持
UiSelector表示:查询一个或是多个UI元素
结语:自己总结的东西其实还有一部分,但是都比较杂乱且不能保证正确性就不发出来了,希望以上内容能够解决你们的问题,提供哪怕一点微不足道的帮助我都会很高兴,如果有任何问题或是错误请及时指出,我会尽我所能尽快修改,很久不写博客了终于这周6能有时间把学到的了解到的一些东西做出总结,假如你也有CSDN的博客麻烦给个赞呗~~~
点击此处下载项目(jar包都在里面)