一、前言
对于大多数的android商业项目,android开发人员大多都处于功能快速迭代的功能研发和bug修复的循环中,对功能的测试基本都交给专门的测试人员来进行测试,但是我认为,了解和学习android测试相关的技术,为核心代码编写单元测试和自动化测试还是很有必要,能够在版本发布前,代码重构时快速的回归测试,保证代码的正确运行,提高测试的效率。
对于开发人员常用的android测试技术,基本可以分为两类,一类是单元测试,一类是自动化测试。下面将分别对两类测试进行一次粗浅的介绍和分享。
二、单元测试
android中的单元测试基于JUnit,可分为本地测试和instrumented测试,在项目中对应以下两个目录:
1.module-name/src/test/java/.
该目录下的代码运行在本地JVM上,其优点是速度快,不需要设备或模拟器的支持,但是无法直接运行含有android系统API引用的测试代码。
2.module-name/src/androidTest/java/.
该目录下的测试代码需要运行在android设备或模拟器下面,因此可以使用android系统的API,速度较慢。
在实际开发过程中,我们应该尽量用JUnit实现本地JVM的单元测试,而项目中的代码大致可分为以下三类:
1.强依赖关系,如在Activity,Service等组件中的方法,其特点是大部分为private方法,并且与其生命周期相关,无法直接进行单元测试,可以进行Ecspreso等UI测试。
2.部分依赖,代码实现依赖注入,该类需要依赖Context等android对象的依赖,可以通过Mock或Robolectric等其它第三方框架实现JUnit单元测试或使用androidJunitRunner进行单元测试。
3.纯java代码,不存在对android库的依赖,可以进行JUnit单元测试。
android的单元测试框架,常用的有以下几个框架和工具:
1、JUnit4
2、AndroidJUnitRunner
3、Mockito
4、Espresso
5、Robolectric
关于单元测试框架的选择,可以参考如图:

自动化测试
android的自动化测试,可以使用appium来进行编写。使用appium来进行android自动化测试的编写有如下几个优点:
1、有图形化的操作界面,简单易用。
2、直接展示页面元素的层级,可以方便的找到对应控件的id。
3、可以进行操作录制,直接生成测试代码。
appium的自动化测试需要下载appium-desktop,测试代码可以使用python、java等语言进行编写(使用python需要安装python-client/使用java需要导入java-client)。
使用时,打开appium-desktop,点击启动服务器。


、
拉起会话器后,填写参数,必要的参数有platformName,deviceName,如果需要拉起对应app需要填写appPackage和appActivity参数。
其中deviceName可以通过命令行adb devices获取;
appPackage和appActivity有多种获取方式,包括使用adb shell pm list packages来获取包名列表,adb shell dumpsys package 包名 来获取包信息,里面有主活动的信息;也可以打开app后使用adb shell dumpsys window | findstr mCurrentFocus来获取当前应用包名和页面;如果有apk包,可以使用adb version 查找到android sdk目录,在build-tools下,使用aapt dump badging <file_path.apk> 命令来获取apk包名和活动名。

点击启动会话后,会拉起app,同时出现手机上画面的快照。在画面中点击页面中的元素,或者展开元素树,可以找到对应控件的id等属性,在编写自动化测试时,可以通过id找到对应的控件,进行操作。

使用appium-desktop有一个很便捷的能力,可以录制操作,直接产生对应的测试代码。使用时,点击开始录制,然后在边上的小屏中选中控件,在右侧进行点击或者输入等操作,完成录制后,就能产生对应的自动化测试代码。



如果是一些复杂的操作,appium提供了TouchAction来模拟界面操作,以java代码为例:
使用Appium需要引入java-client-xx.jar包,在java-client-5.0版本之前,Appium滑动使用的是swipe方法,从5.0之后,Appium已经取消swipe方法,交由TouchAction对象的方法来完成滑动操作。在java-client-5.0版本之前也有TouchAction对象,在java-client-5.0版本之后TouchAction对象的方法使用也有所变化。
使用TouchAction可实现的功能如下:
1.press短按
短按操作是通过入参元素坐标的x,y值完成的。在5.0版本之前直接入参坐标x,y值 press(int x,int y);在5.0版本开始入参是调用PointOption对象的point方法 press(PointOption.point(int x,int y))。
2.longPress长按
长按操作同样是通过入参元素坐标的x,y值完成的,长按操作比较常见的如元素拖动、元素滑动等。在5.0版本之前直接入参坐标x,y值longPress(int x,int y);在5.0版本开始入参是调用PointOption对象的point方法 longPress(PointOption.point(int x,int y))。
3.waitAction暂停
暂停等待。在5.0版本之前使用TouchAction的wait方法,入参是int类型,单位毫秒 wait(2000);在5.0版本开始,已经丢弃了wait方法,使用TouchAction的waitAction方法,入参WaitOptions对象,调用waitOptions方法的入参值是Duration.ofSeconds(1)单位是秒waitAction(WaitOptions.waitOptions(Duration.ofSeconds(1)))。
4.moveTo移动
移动到某坐标位置,与press、longPress配合一起使用。在5.0版本之前moveTo入参为坐标x,y值 moveTo(int x,int y);在5.0版本开始入参是调用PointOption对象的point方法 moveTo(PointOption.point(int x,int y))。
5.release释放
完成press短按、longPress长按、waitAction等待、moveTo移动完成操作后需要使用release方法释放
6.perform执行
调用perform方法表示要执行press短按、longPress长按、waitAction等待、moveTo移动、release释放这些操作
举例:
PointOption startPoint = PointOption.point(1200,1200);
PointOption endPointPoint = PointOption.point(1200,600);
WaitOptions waitOption = WaitOptions.waitOptions(Duration.ofSeconds(1));
new TouchAction<>(driver).longPress(startPoint).waitAction(waitOption).moveTo(endPointPoint).release().perform();
这是一个往上滑动的操作,保持x坐标的不变,y坐标减少,表示从下往上滑动。操作过程是长按--等待1S--滑动--释放--执行
另外元素坐标位置的绝对值因设备大小、分辨率不同,值会不一样(不建议直接使用坐标操作元素),所以要使用相对位置来操作,Appium提供以下方法获取元素相对坐标:
1.获取屏幕大小
int screenWidth = driver.manage().window().getSize().width;
int screenHeight = driver.manage().window().getSize().height;
2.获取元素开始坐标
WebElement element = driver.findElement(By.id("ymxh.main:id/spinner_button"));
Point p = element.getLocation();
//获取元素开始的x,y坐标
int startX = p.x;
int startY = p.y;
3.获取元素宽、高
WebElement element = driver.findElement(By.id("ymxh.main:id/spinner_button"));
//得到元素的宽、高
Dimension d = element.getSize();
int width = d.width;
int height = d.height;
4.得到元素坐标的结束坐标
//得到坐标结束坐标
int endX = startX+width;
int endY = startY+height;
5.得到元素中间坐标
//得到元素中心坐标
int centerX = (startX + endX)/2;
int centerY = (startY + endY)/2;