赞
踩
framework层测试也是android 移动端测试的领域,但是和更上层的应用测试不同,应用测试更偏重于应用是否正确实现了业务逻辑;而framework层测试更偏重于能否正确向上层输出能力。
做移动测试的,android整体框架图肯定是了然于心的,从底层往上的顺序,Android系统架构由5部分组成,分别是:Linux Kernel、Android Runtime、Libraries、Application Framework、Applications;Framework层正处于应用层之下,这也可以看出它的作用:为应用层输出能力。
输出的能力包括但不限于:为上层应用提供各种api、提供各种组件和服务、管理应用的活动生命周期等等。
既然framework是为上层提供能力的,作为我们的测试对象,我们的测试内容自然也和这些息息相关,包括但不限于:framework层接口的测试(功能、稳定性、安全性等等)、底层能力测试(比如私有的按键功能、自定义的输出日志等等)、系统修改测试(比如裁剪系统)等等。
实际上,尽管framework层的需求种类繁多,但是在测试方法上,无非也就是两个维度来处理:自上而下的测试,或者是自下而上的测试。
自上而下的测试方法,其实也就是站在顶层的视角看需求;无论framework新增或者修改了什么,总归是要给上层输出能力的,或者是在上层有自己的表现方式。要么是上层可以使用到提供的能力,要么是你的修改在上层有直接或者间接体现,那么我们就直接对其“表象”进行验证,间接测试framework层的能力。
如果是对系统底层能力或者对系统修改的验证的话,其实和传统的app测试差不多,因为他们都有表现的实体;app的测试可以直接从ui层看到结果,而对系统能力或者系统修改的测试,一般也可以在系统的ui上看到结果,或者一些是隐形的修改,也可以通过adb命令直接看到结果。
而在framework为上层提供的能力上,往往并没有实体ui可以看到表现,直接使用业务的应用作为载体的话,其复杂度太高,也不符合分层测试的理念,出现问题难以判断是业务应用的问题还是framework层的问题;因此,我们采用“自造”载体的形式,即自己开发一个app作为载体,和业务app不同的是,自造的app保持最小功能,仅通过ui或者广播等形式,将待测framework层的能力暴露出去,通过在ui上直接操控framework层的接口,然后观察其结果,间接测试到framework层的能力。
自下而上的测试方法,就比较直接了当了,就是直接针对提供的底层能力测试,例如对framework层的接口测试,通过单元测试的方式,对接口进行各方面的测试,从而在底层保障framework层的能力。
这种方式可能传统的功能测试同学不太熟悉,因此下面着重的介绍一下framework层接口测试的流程和方法。
在framework测试中,最为原始的测试需求应该就是对新增或者修改framework层接口的测试,本质上,对framework接口的测试也是接口测试的一种,他可以类比于web的接口测试,更容易让人理解;但和单元测试更为接近。
和web接口测试的相同点在于,二者都是对输入输出的校验,web接口是在网络协议的基础上,对服务器进行请求,可以想象成网络协议是高速公路,请求则是奔驰的汽车,对汽车来说,高速公路是公共建设,很多协议如https、dubbo等都是公共基础,不需要自己再去施工的;而对于framework接口来说,这条路就未必是统一的,因为对于rom级的产品来说,会涉及到很多在framework层新增或者修改的东西,这部分不在android的官方sdk里,因此,需要一些手段自己构造测试条件,也就是自己去把路修好。
如何去修路?首先我们需要地基,也就是请求的环境基础,web接口可以直接借由网络通道去请求到服务器,而framework接口的请求,需要请求端本身在含有修改后的framework层的android环境里。
我们采用的方式,是自己开发一个app,安装于待测的android环境里,通过android junit 或者实现按钮去请求(调用)接口。
这里有个问题,就是我们在本地编译环境下,直接调用新增或者修改的framework层接口的话,是没法调用的,因为本地sdk是android官方sdk,是不含我们私有内容的,因此,我们首先解决本地的编译问题。
一般来说,有两种方法:
由开发直接提供给你接口方法所在的jar包,你在需要调用的地方引用该jar包,直接调用jar包内开发好的接口;
开发如果没有给jar包的话,可以我们自己按照接口的设计说明文档,实现一个同名的接口类/方法,这样编译可以通过,而在实际环境执行的时候,是会优先找系统内实际的方法的。
我们预备了几个途径验证接口:
在开发的app内,采用界面ui形式,例如提供表单和按钮,来进行接口参数的输入和验证,这个途径一般用于给功能测试人员,进行快速简单的验证,或者充当工具的角色,通过调用接口快速开启系统提供的某种功能;
在开发的app内,预留广播,这样可以通过外部shell发送广播来调起相关的接口,而无需在界面打开;这个途径一般用于给其他类型的自动化测试提供接口的使用途径;
是借用android的单元测试框架,android junit,直接在代码层进行接口测试。
下面主要说一下如何用junit进行framework层的接口测试。
新建Android工程
和web接口测试不同,framework层的接口测试首先需要一个测试环境,这个环境一般使用是新建一个Android工程,也就是创建一个app;这个app将成为测试工程和framework层接口沟通的桥梁,因为android junit测试工程就是在应用的子线程下执行的(@UiThreadTest 时,测试case将在ui线程中执行)。
集成待测接口
新建完Android应用工程后,我们需要把我们自己开发或者修改的framework接口集成在工程内方便测试时调用;这里就使用上面介绍的两种方法,即新建待测试接口同名的类/方法,或者直接引用sdk(jar)包调用。
新建junit测试工程
android工程新建完成并集成了待测接口后,接下来直接新建junit测试工程即可。
首先是基础语法,junit和其他的测试框架基本规则都很相似,下面说一下大体的操作。
@RunWith(AndroidJUnit4.class)
public class TestClass001 {
...
}
@Test
public void testSomething() {
...
}
@Before
public void setUp() throws Exception {
...
}
@After
public void tearDown() throws Exception {
...
}
@RunWith(Suite.class)
@Suite.SuiteClasses({
TestClass001.class,
TestClass002.class,
...
})
public class ExampleInstrumentedTest {
...
}
在注解里添加你需要运行的测试类,然后直接运行该suite类即可。
framework接口测试用例的设计和web接口测试用例基本思路都是一致的,大约从以下几个方向考虑:
这里非常推荐设计之前借鉴一下腾讯移动品质中心(TMQ)写的一篇接口测试用例设计【点我打开链接】的文章,虽然不是特别针对framework层接口的,但基本思路总结的非常全面了。
基于junit框架进行的接口测试是纯代码型的,不像普通的excel或者word文档那样天然具备良好的可读性,因此在用例管理和规范上,需要遵循一定的方式。
类名
按照【Test】【测试对象】【场景】的结构
例如:
测试softsim的性能,类名可以为:TestSoftSimPerformance();
方法名
按照【test】【用例描述(测试目的)】的结构
例如:
测试插拔sim卡,方法名可以为testPlugSimCard();
注释
对于场景流程较长或复杂的用例,建议增加注释,用例内容为步骤描述以及其他须注意点。
单接口测试用例结构设计
单接口测试时,建议尽量使用参数化的形式进行测试,以期减少测试用例代码的冗余。
详细来说,我们推荐先把入参进行归类处理,在测试方法内进行分支判断,然后使用@RunWith(Parameterized.class)注解装饰测试类,在测试类中通过@Parameterized.Parameters注解装饰数据构造方法,给测试用例执行。
多接口组合测试用例结构设计
非单接口测试时,测试用例的代码设计结构推荐按照分层测试的思想,即积木式的堆叠组合,以期达到最大可复用状态。
详细来说,我们把api作为最小执行单位,多个api之间关联执行的最小业务逻辑集合封装为步骤(step),在step中,需要包含该业务逻辑或api调用后的基础检查点(Assert);我们把step组合而成的业务逻辑封装为testcase,testcase中包含了该业务逻辑的最终检查点。
我们把testcase按照测试对象和场景归类,归属到同属性下作为一个测试类(TestClass),由suite执行器执行。
在编写接口测试代码的时候,不止是调用待测的接口,很多时候还需要自己写方法提供测试参数或者测试环境,又或者需要和android的环境进行交互,下面阐述一下常用的编码时用到的工具。
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); intentFilter=new IntentFilter(); intentFilter.addAction(Intent.ACTION_SCREEN_ON); intentFilter.addAction(Intent.ACTION_SCREEN_OFF); intentFilter.addAction(Intent.ACTION_USER_PRESENT); mScreenReceiver=new ScreenBroadcastReceiver(); registerReceiver(mScreenReceiver,intentFilter); } private class ScreenBroadcastReceiver extends BroadcastReceiver{ private String action=null; @Override public void onReceive(Context context, Intent intent) { action=intent.getAction(); if(Intent.ACTION_SCREEN_ON.equals(action)){ Toast.makeText(context,"屏幕开屏",Toast.LENGTH_SHORT).show(); }else if(Intent.ACTION_SCREEN_OFF.equals(action)){ Toast.makeText(context,"屏幕关屏",Toast.LENGTH_SHORT).show(); }else if(Intent.ACTION_USER_PRESENT.equals(action)){ Toast.makeText(context,"屏幕解锁",Toast.LENGTH_SHORT).show(); } } } }
判断系统电池状态
判断系统电池状态可以直接使用BatteryManager.getLongProperty()获取,其入参是规定的常量,主要有以下参数:
BATTERY_PROPERTY_CHARGE_COUNTER: 剩余电池容量,单位为微安时
BATTERY_PROPERTY_CURRENT_NOW: 瞬时电池电流,单位为微安
BATTERY_PROPERTY_CURRENT_AVERAGE: 平均电池电流,单位为微安
BATTERY_PROPERTY_CAPACITY: 剩余电池容量,显示为整数百分比
BATTERY_PROPERTY_ENERGY_COUNTER: 剩余能量,单位为纳瓦时
获取系统属性
获取系统属性可以直接通过android.os.SystemProperties获取,示例代码如下:
private String getAndroidOsSystemProperties(String key) {
String ret;
try {
systemProperties_get = Class.forName("android.os.SystemProperties").getMethod("get", String.class);
ret = (String) systemProperties_get.invoke(null, key);
} catch (Exception e) {
return null;
}
return ret;
}
}
2.模拟系统交互
在测试中往往需要模拟系统交互或者更改系统属性,如模拟按键输入、模拟按键点击、开启设备节点等等;一般而言shell的执行可以模拟大部分情况,或者使用Instrumentation也可以达成目的,下面举例几个常见使用方式。
执行shell命令
在android工程内,可以通过Runtime.getRuntime().exec(shell命令)的方式直接执行shell命令;因此可以利用这一点,通过shell模拟系统交互,例如,通过shell发送keyevent,模拟按键操作、发送input text输入文本等等。
使用Instrumentation框架
Instrumentation框架是是Android自带一个单元测试框架,在这个框架下,你的测试应用程序可以精确控制应用程序。
使用Instrumentation, 你可以在主程序启动之前,创建模拟的系统对象,如Context;控制应用程序的多个生命周期;发送UI事件给应用程序;在执行期间检查程序状态。 Instrumentation框架通过将主程序和测试程序运行在同一个进程来实现这些功能。
下面以模拟点击按键示例,代码如下:
private void sendKeyCode(final int keyCode) throws InterruptedException {
Thread t1 = new Thread () {
public void run() {
try {
Instrumentation inst = new Instrumentation();
inst.sendKeyDownUpSync(keyCode);
} catch (Exception e) {
Log.e("Exception when sendPointerSync", e.toString());
}
}
} ;
t1.start();
t1.join();
}
谈及测试方法之后,当然免不了对测试后问题的记录和分析,虽然往往最终的bug修复工作都是开发来做,但我们仍然可以力所能及的承担问题前期分析工作。在android framework层的测试中,除了用例本身的断言提示,我们还要借助很多log和工具进行辅助分析,下面介绍一下这些。
Android环境中,存在各种各样的log,下面介绍一下它们的用法。
logcat是最基础的android log,基本上最常用的也是它,由于设备的缓冲区有限,出了问题如果没有及时的记录就会被冲刷掉,一般而言,我们会在测试开始前就开启log输出并转储到本地。
logcat存储内容
logcat主要有四个缓冲区,分别存储了Radio:输出通信系统的log、System:输出系统组件的log、Event:输出event模块的log、Main:所有java层的log,以及不属于上面3层的log。
由于我们往往是测试系统层api,因此一般这四个缓冲区都需要记录下来。
logcat日志等级
logcat一般分为V –Verbose(最低优先级)、D – Debug、I – Info、W – Warning、E – Error、F – Fatal、S – Silen;排名越后优先级越高,在实际的分析里,我们可以优先按照Fatal过滤日志查看。
logcat输出记录
logcat可以直接通过adb命令输出并记录,例如“adb logcat -b radio -b main -b system -b events -b kernel -v time> E:%filename%”这样的形式,如果想要特别只记录测试时间段内的数据,可以先执行 “adb logcat -c”,清除缓冲区日志。
在应用发生anr时,ActivityManagerService的appNotResponding方法就会被调用,然后在/data/anr/traces.txt文件中写入ANR相关信息,因此对traces.txt的分析可以得出anr时的过程。
traces.txt存储内容
traces.txt保存了发生ANR的进程id、时间和进程名称等;线程的调度信息、上下文信息、调用栈信息等;以及系统当时的整体使用情况等。
traces.txt输出记录
traces.txt可以直接通过adb命令输出,日志默认保存3天的信息,可以通过“adb pull data/anr/traces.txt d:\log”的形式拉取出来。
dmesg是内核的log信息。
dmesg存储内容
dmesg是用来显示内核相关信息的,它从内核环形缓冲区中获取数据的,主要存储硬件相关的error和warning、守护进程相关的信息、系统的启动信息等等。
dmesg抓取方式
dmesg可以直接通过adb命令输出,例如“adb shell dmesg >D:/Kernel.log”这样的形式;或者直接执行“adb shell”命令,在shell内执行“cat /proc/kmsg”。
bugreport是android上用于调试的、一个官方的调试信息聚合工具,它的内容包含了多种调试信息。
bugreport存储内容
bugreport包含了庞大的调试信息种类,实际上他本身是个工具,作用就是对各种信息进行聚合并形成一个统一文件;它包含了基本的logcat(包括各个缓冲区)、vm trace、system property、系统资源情况(dumpsys checkin相关、dumpsys app相关等)、system server crash 和 system app crash 信息等等。
bugreport抓取方式
bugreport可以直接通过adb命令抓取,例如“adb bugreport > bugreport_out.txt”这样的形式。
bugreport读取方式
bugreport往往是个庞大的文件,直接读的话可能会比较费时,可以使用Google官方的开源分析工具bettery historian进行分析,它会展现一个类似web的界面,更加简便易懂。
coredump是linux原生的记录系统产生异常的日志,一般在死机或者系统线程异常时记录,需要说明的是,并不是所有设备都有此日志,需要开启这个功能的日志才可以获取到,而且存储的方式和位置也是根据具体的实现方式而定的,因此这里不多作介绍。
ramdump指内存转储,也就是整个DRAM的运行时内容数据,当系统发生崩溃性异常时候,通过一种机制实现将DRAM中的数据保存起来,保留了异常现场,待离线分析用。
ramdump存储内容
ramdump中保留了异常时候的DRAM中的信息,包括各种全局变量、局部变量、进程状态等等。
ramdump抓取方式
由于ramdump是死机(崩溃)时日志,因此一般无法通过adb获取了;我们可以直接通过高通平台的qpst抓取,方式也很简单,安装qpst工具后,直接打开其下的QPST Configuration软件,在tab页切换到Ports即可,发生死机(崩溃)后,机器重启时,QPST会自动抓取日志。
除了Android内的各种日志类外,在我们进行长时间的测试类型例如压力或者稳定性测试的时候,不止是产生错误需要记录分析,对被测接口整体的资源占用情况更需要记录,这一块也有多种方式实现,下面挑几种常见的介绍。
如果是使用AS的方式进行的app开发和接口测试,无疑AS自带的Profiler页是最为结合紧密的,在运行测试之后,我们只需要切到在AS底部的Profiler页,在SEESION栏选择好需要监控的进程即可,一共分为四个可监控项,分别是CPU、Memory、NETWORK、ENERGY。
Android Studio Profiler 只提供对资源信息的大致预览,如果需要更细的分析,需要dump相关的heap。
DDMS是android sdk内自带的工具集,基本入口在sdk内tools目录下的monitor.bat,双击该文件即可打开。
相比于Android Studio Profiler,DDMS一般用于分析更细一层的东西, 他可以实时的看到heap、threads、network的详细使用情况; 并且在System Information内,可以看到cpu load、mermory usage、frame render的详细分配情况;同样,它也支持把资源相关的heap文件dump下来详细分析。
当我们更进一步的想要分析问题时,可以使用DDMS。
adb是android调试协议桥的简称,除了上面提到的可以记录log之外,也有很多命令可以查看资源使用情况;例如获取应用的堆内存文件“
adb shell am dumpheap /data/local/tmp/name.hprof”、获取应用的线程文件“
adb shell run-as kill -3 pid adb pull /data/anr/traces.txt”等等,更多用法,可以自行百度。
除了android生态内自带的工具集外,有很多大厂也开源了他们的资源监控方案,例如腾讯的GT、讯飞的Itest、蚂蚁金服的SoloPi等等,他们往往也都兼具了基本的资源监控,例如cpu、内存、fps、电量、温度、网络上下行等等;甚至可以进行简单的压力模拟,例如内存填充、cpu压力模拟等等。
如果对android生态内工具不熟悉的话,建议直接使用这些,更为简便易上手。
链接:https://www.jianshu.com/p/94eb72f91580
作者:Null_ice
如果你想要深入系统的学习Android Framework框架,这里可以分享一份《Android Framework源码开发揭秘》,其中记录了从系统启动流程到WMS全部源码解析,相信你能优秀地学习整个Framework框架。
因文章篇幅原因,只放了部分内容,完整版扫码免费领取~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。