自动化测试可行性分析

针对android-nova项目,每个新版本发布之前,RD 都需要进行主流程回归,这是一项重复且耗时的工作,“重复”是指每2~3周发版时就要进行一次,“耗时”则因为平均一条业务线要用一个小时时间——这还只是单android平台。
实现自动化后,可以大大减轻 RD 负担,并且可在迭代过程中建立 daily build 机制,及早发现问题,提高提测质量。


对自动化测试的理解

这里只针对 UI 方面的白盒测试

  1. 自动化测试的输入是用户操作(输入、点击、滑动)、网络请求返回
  2. 自动化测试的输出是 UI 控件显示

这两步都可以通过现有的自动化工具完成


自动化工具选取

之前组内分享了2个自动化工具,Robutium 和 Appium。查阅相关资料后,发现网络上比较流行的不外乎也就是这两个。简单罗列出各自的特点:

Robotium Appium
是否开源
测试类型 黑盒 黑盒
支持平台 Android Android, iOS, FirefoxOS
apk是否需要再编译 需要,测试代码必须打入apk包 无需,测试代码单独存放
可测App类型 原生,hybrid 原生,hybrid,甚至可以通过Safari/Chrome来测试web页面
安全性 “The most notable limitation”, 操作仅限本 app 未知
用例语言 Java Any WebDriver-compatible language: Java, Python, PHP, C#, OC…
参考资料 Android Robotium自动化测试使用手册 by 楼赟程 Android Appium自动化测试指导手册 by 覃少强

比较之后,选用Appium作为实践手段


难点

如果要实现自动化测试,总结下来,以下几个方面是难点,也是必须解决的问题

  1. 预设数据保存、读取
  2. 预设数据填入组件
  3. 模拟请求发送接收
  4. 检验接收后的界面展示

其中2&4可以由测试框架帮助完成,1&3比较棘手,详见本文讲述。


Appium配置与运行

历时一天才跑通了Appium的demo,不得不再次感慨,中文博客的帮助的确是太太太太少了。首先要确认平台是OS X,其次还要是为android进行测试,好不容易找到了一些,发现大家都是一模一样的,不知道是谁抄谁。关键是你抄来抄去,抄的净是些错误内容……

自己途中遇到一些曲折,记录如下

  1. 理解Appium所使用的client-server-app的结构,client的代码不应该写在app
    我是这么处理的:
    • client:maven项目,用IntelliJ编写,测试代码位于test/java/com.leili.demo.Demo,注意maven项目要在pom里处理好依赖(见代码)
    • server:Appium的GUI界面,完成配置即可使用,不需二次开发
    • app:gradle项目,用AndroidStudio编写,不含测试代码
  2. 配置ANDROID_HOME时,记得配至到系统默认的bash配置文件中。刚开始时自己只写到了~/.zshrc里(因为平时用的是zsh),结果运行测试用例时报错找不到ANDROID_HOME,上网查找后也没有结果(baidu检索出的中文博客大多是讲路径中不要有空格…),最后醍醐灌顶恍然大悟,改了~/.bash_profile,才生效
  3. 在写Appium测试用例(Java)时,在EditText进行输入,直接element.sendKeys("foooooo")即可,不要想当然在前面加上element.click(),否则会有奇怪的事情发生

maven依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<dependencies>

<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>2.48.2</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>

<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.1</version>
</dependency>

<dependency>
<groupId>io.appium</groupId>
<artifactId>java-client</artifactId>
<version>3.3.0</version>
</dependency>

<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>

<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>

</dependencies>

启动 Activity

由于Appium目前只能用包名+类名的方式启动,不支持通过intent启动Activity。如果需要在启动中携带参数,这里提供一个解决方法:在debug面板中通过scheme直接进入目标Activity。这种做法过于依赖Debug面板,其实并不是一种好的实现方式。

希望Appium团队能够在后续更新中提供更多的启动Activity途径。

Appium中启动Activity的方法,代码位于AndroidDriver.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @param appPackage
* The package containing the activity. [Required]
* @param appActivity
* The activity to start. [Required]
* @param appWaitPackage
* Automation will begin after this package starts. [Optional]
* @param appWaitActivity
* Automation will begin after this activity starts. [Optional]
* @param stopApp
* If true, target app will be stopped. [Optional]
* @example driver.startActivity("com.foo.bar", ".MyActivity", null, null, true);
*
* @see StartsActivity#startActivity(String, String, String, String)
*/
public void startActivity(String appPackage, String appActivity,
String appWaitPackage, String appWaitActivity, boolean stopApp)
throws IllegalArgumentException;

测试数据存取

对于如何读取mock数据,有两种思路

  1. 手机/模拟器连接mock服务器后,在mock服务器上配置,客户端每次请求会发送出去,但不是发送到真实的api后端,而是发送到mock服务器

    • app代码无需变动
    • 实现简单
    • android/iOS两个平台可以共用同一套数据、同一个mock服务器
    • 依赖mock服务器,甚至可能需要自己搭建mock服务器
    • 要考虑如何使模拟器在mock服务器上进行注册。已有的扫二维码方式显然不现实
  2. 将mock数据存入手机/模拟器,本地读取

    • 不依赖mock服务器
    • 需要app内部支持,实现DB、Service等功能,较复杂
    • android/iOS各自都需要改动底层代码以进行支持
    • 仍然需要考虑正常开启的app如何自动切换到测试模式

比较后,选用1作为最后的实现方式,同时基于mobile-httpwatch搭建了自己的mock服务器,提供预加载配置的接口。


Show Time

主流程测试用例(预订):test case

这条用例的操作过程中,涉及到调用以下3个接口:

  1. getbookingcontext.yy,获取商户预订配置
  2. getbookingholidays.yy,获取节假日信息(商户无关)
  3. book.yy,下订单

出于简化的考虑,我们本次只对接口3进行mock。

截图应如下所示:

输入

输出


Q: 预设的mock数据要保存在TestCase中还是保存在Server里?

有两种方案

  1. mock data 保存在 TestCase 侧,每次用例执行前,TestCase 把数据传给 Server(这种方案每次传递数据量过大)
  2. mock data 保存在 Server 侧,每次用例执行前,TestCase 把 mock data id 传给 Server

每个 case 对应一套 mock data ,可以是一个接口或者多个接口的数据。


功能点开发进度表

||状态|功能点|备注|
|1|done|test engine 发送 pre-req ,告知 server caseid||
|2|done|server 收到请求||
|3|done|server 解析请求中的 caseid||
|4|done|server 根据 caseid ,加载本地对应 mock data ,加载完成后告知 test engine||
|5|done|test engine 驱动 client 进行注册||
|6|done|test engine 驱动 client 发送真实请求||
|7|done|server 根据真实请求,返回已经准备好的 mock data||
|8|done|client 获得 mock data||
|9|done|test engine 进行自动化 UI 检验||
|10||test engine UI 校验完成后,发送 post-req,告知 server 清除 mock data|可选|
|11||server 接收到 post-req 后,清除 mock data|可选|


一些nodejs函数方法

处理get请求

1
2
3
4
5
6
<!-- http://appmock.dp/mockconfig.do?caseid=1024 -->
console.log(url.parse(request.url, true).query.caseid); // log '1024' here
response.writeHead(200, {
"Content-Type": "text/plain;charset=utf-8"
});
response.end();

处理post请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var postData = "";
request.addListener("data", function(postDataChunk) {
postData += postDataChunk;
});
request.addListener("end", function () {
console.log('数据接收完毕');
var params = querystring.parse(postData);
console.log(params);
console.log(params["num"]); /// {num=1024, name=lilei}, log '1024' here
response.writeHead(500, {
"Content-Type": "text/plain;charset=utf-8"
});
response.end("数据提交完毕");
});

读取文件(同步)

1
2
3
var content = fs.readFileSync('/Users/leili/Downloads/7');
config = parseJson(content);
user.setMockConfig(config);

读取文件(异步)

1
2
3
4
5
6
7
fs = require("fs");
fs.readFile('/Users/leili/Downloads/2', 'utf8', function(err, data) {
if (err) {
return console.log(err);
}
console.log(data);
});

遇到的问题

  • 预订可能对已过时间有一些判断,导致在线预订页无法使用mock数据,原因未明

反过来,一些思考

真的需要自动化吗?UI 本身做的逻辑就很少,如果返回数据是正确的,UI 显示也几乎不会出现问题,为何要如此多精力来写自动化 case

  • 准确度:每一个控件,展示什么内容?
  • 数量:case 数量大,记不住

投入到生产中,会遇到的问题

  • 前期准备量大:扒测试数据,造各种case(定金、预订成功、失败,商家营业中、暂未营业等各种状态)
  • 难以对布局之间的相对位置、缩进等进行测试
  • 即使是使用Appium进行自动化测试,也是需要rd做支持的,qa必须从rd处了解到诸如控件id等属性,方可以定位到目标控件进行操作。
  • 视觉上看是一个TextView,而实际上是两个TextView,作为QA难以判断(可能要求理解功能具体实现)

mobile-httpwatch 项目阅读笔记

记录的内容比较琐碎

web.js 注册,设置mock数据
mockconfig.js mock设置界面
User.js 用户数据结构,每个用户有一个MockConfig
mock_config.js 默认的mock数值
html5 的 localStorage
ProxyFactory.js mock rule,重点看handleProxyRequest和mockResponse两个方法
mockConfig = user.getMockRuleForUrl(req.url)
Utils.js


参考资料

http://www.cnblogs.com/lori/p/3437562.html
http://www.mkyong.com/java/how-to-send-http-request-getpost-in-java


===Ending===