说明

  在项目配置完基于robotium框架下的自动化测试用例后发现虽然用代码配置测试用例虽然较为灵活,但是如果编写较为全面的测试用例则必然会消耗大量开人员的精力,并且对于用例的后期维护也是很大一部分投入,使开发人员无法更为专注于项目构建,如此萌生了将测试用例以HTML脚本方式进行表述,可让测试人员进行配置测试脚本,测试人员在进行全流程业务测试过程中进行自动化测试脚本的编写,并且可进行增量维护,在项目上线前可以用最小的时间代价进行最全面的测试工作,对于项目质量的把控有不可忽视的作用。

  本自动化测试项目需要开发人员针对项目进行一定的配置,当配置完成后可对自动化测试项目进行打包,与被测项目安装在同一设备内,通过adb执行指定命令完成自动化测试的流程。除非有重大变,自动化测试项目仅需开发一次,可针对同一被测项目重复使用,所有测试流程的设备行为由HTML标签进行约束,测试人员以约定HTML标签编写测试用例,可动态指定执行的测试用例,便于测试用例的维护。

执行环境:

1、被测试项目安装包

2、自动化测试项目安装包

3、Android开发环境(示例开发工具为eclipse)

4、Android设备

自动化测试项目初始构建

1、新建被测试项目,并编写相关功能。

2、创建测试项目,关联被测试项目。

3、测试项目导入robotium-solo jar包(可于官网下载)

4、编写代码自动化测试用例,测试robotium自动化测试是否可正常执行

开发工具中执行测试用例:光标放在当前类中,点击鼠标右键 --> RunAs --> Android Junit Test


1、创建一个Android项目,编辑项目清单文件 <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.walker.autotest"
android:versionCode="1"
android:versionName="1.0" > <uses-sdk android:minSdkVersion="9" />
<!-- 必须指定,并设置目标项目包名:com.waitingfy.iosunlock 替换成目标项目的包名-->
<instrumentation
android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.waitingfy.iosunlock" /> <application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" > <!-- 必须指定所用library(拷贝即可) -->
<uses-library android:name="android.test.runner" /> </application> </manifest>

2、 增加自动化测试用例执行入口,代码如下:
/**
* @Title: HtmlScariptTest.java
* @Package com.walker.autotest
* @Description: TODO
* @author A18ccms A18ccms_gmail_com
* @date 2017年9月4日 下午2:00:09
* @version V1.0
*/
package com.walker.autotest; import com.robotium.solo.Solo; import android.os.Environment;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Xml; /** @ClassName: HtmlScariptTest
*
* @Description: TODO
*
* @author walker
*
* @date 2017年9月4日 下午2:00:09
*
*/
public class HtmlScariptTest extends ActivityInstrumentationTestCase2{
Solo solo;
String logFileName = "testLog.txt";
private static Class<?> launchActivityClass;
//被测试应用入口界面Activity全名
private static String mainActiviy = "com.waitingfy.iosunlock.MainActivity";
//被测试应用包名
private String packageName = "com.waitingfy.iosunlock";
//加载入口Activity,获取Activity的类对象
static {
try {
launchActivityClass = Class.forName(mainActiviy);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
} /**
* 默认构造函数,以入口Activity对象为参数调用父类构造,设定测试用例入口执行逻辑
*/
public HtmlScariptTest() {
super(launchActivityClass);
}
/**
* 测试用例初始化时设置solo对象,对设备的所有操作通过solo对象进行。
*/
@Override
protected void setUp() throws Exception {
super.setUp();
solo = new Solo(getInstrumentation(),getActivity());
}
//必须覆写
@Override
protected void tearDown() throws Exception {
super.tearDown();
}
/**
* Add by walker Date 2017年9月4日
* @Description: TODO
* 测试用例执行入口,测试用例方法名以test开头命名
*/
public void test0(){
solo.sleep(10000);
}

标签设计

标签 动作 属性
testCase 指定测试用例 测试用例文件名
data 标记内容为测试数据 dataType 各标签值
clickView 点击指定ID的View控件 ID,hasText
inputText 指定编辑框录入内容 ID 输入框录入内容
checkBox 设置指定选择框的勾选属性 ID 值为1勾选,否则不勾选
spinner 列表选择 ID 被选择内容的文本值
sleep 延迟时间后再继续执行 延时时长(毫秒)
onKeyDown 键盘按键事件 按键值,支持范围如下
clickOnText 点击显示的文本 hasText 被点击的文本字符串
takeScreenshot 截屏并保存图片
waitForActivity 等待指定页面 指定页面的activity值
checkData 校验数据标签 tableName
filed 数据校验字段 filedName 指定字段的值

testCase:仅在EMS.xml中起作用,且Main.xml文件中只识别testCase标签

data:包裹测试数据,被包裹内容为测试脚本数据,属性dataType来设定数据类型。

clickView:点击页面View控件,属性有ID、hasText。ID属性不可为空,以ID值进行索引View控件并进行点击;hasText属性值可以为空,如果hasText为空或者在当前可见页面可以所搜到此文本则执行点击事件。

clickOnText:点击页面上指定的文本,属性有hasText。如果hasText为空或者在当前可见页面可以搜索到此文本则执行点击事件。

waitForActivity:值为页面Activity名称,等待指定的Activity,如果不是指定activity则结束测试流程

spinner:点击控件后显示候选列表,并从列表中选择标签指定的值。

takeScreenshot:截图文件保存路径(/sdcard/Robotium-Screenshots)

checkData:用于指定校验数据,属性tableName为被查询的表名,标签包裹内容为字段属性值

filed:用于指定被校验表的字段属性,属性filedName指定字段名称,值为字段的值。此标签应被checkData标签包裹。

支持的按键:back(回退键)、enter(确认键)、left(左键)、right(右方向键)、up(上方向键)、down(下方向键)、数字(1-9);
ID属性:为项目中xml文件中配置id值,可通过项目源码获取

代码解析HTML标签配置的测试用例

  如下代码为带有HTML标签解析的代码,在执行测试用例时首先对指定路径下的配置文件main.xml进行读取,根据其配置的脚本文件加载对应的执行脚本,然后依次执行所配置的脚本,完成自动化测试流程。


/**
* @Title: HtmlScariptTest.java
* @Package com.walker.autotest
* @Description: TODO
* @author A18ccms A18ccms_gmail_com
* @date 2017年9月4日 下午2:00:09
* @version V1.0
*/
package com.walker.autotest; import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; import org.xmlpull.v1.XmlPullParser; import com.robotium.solo.Solo; import android.os.Environment;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Xml;
import android.view.KeyEvent;
import android.view.View;
import android.widget.CheckBox;
import android.widget.EditText; /**
* @ClassName: HtmlScariptTest
*
* @Description: TODO
*
* @author walker
*
* @date 2017年9月4日
*
*/
public class HtmlScariptTest extends ActivityInstrumentationTestCase2 {
Solo solo;
String logFileName = "testLog.txt";
private static Class<?> launchActivityClass;
// 被测试应用入口界面Activity全名
private static String mainActiviy = "com.waitingfy.iosunlock.MainActivity";
// 被测试应用包名
private String packageName = "com.waitingfy.iosunlock"; // 加载入口Activity,获取Activity的类对象
static {
try {
launchActivityClass = Class.forName(mainActiviy);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
} /**
* 默认构造函数,以入口Activity对象为参数调用父类构造,设定测试用例入口执行逻辑
*/
public HtmlScariptTest() {
super(launchActivityClass);
} /**
* 测试用例初始化时设置solo对象,对设备的所有操作通过solo对象进行。
*/
@Override
protected void setUp() throws Exception {
super.setUp();
solo = new Solo(getInstrumentation(), getActivity());
} @Override
protected void tearDown() throws Exception {
super.tearDown();
} /**
* Add by walker Date 2017年9月4日
*
* @Description: TODO 测试用例执行入口,测试用例方法名以test开头命名
*/
public void test0() {
loadScriptList();
} String configPath = ""; /**
* Add by walker Date 2017年8月29日
*
* @Description: TODO 加载解析脚本清单配置文件配置脚本文件
*/
private void loadScriptList() {
configPath = Environment.getExternalStorageDirectory() + "/Test/";
File file = new File(configPath + "Main.xml");
try {
InputStream in = new FileInputStream(file);
XmlPullParser parser = Xml.newPullParser();
parser.setInput(in, "utf-8");
int type = parser.getEventType();
while (type != XmlPullParser.END_DOCUMENT) {
String value = "";
switch (type) {
case XmlPullParser.START_TAG:
value = parser.nextText();
if ("testCase".equals(parser.getName())) {
// 读取脚本文件,加载执行所配置的脚本文件
xmlLoad(value);
}
break;
case XmlPullParser.END_TAG:
break;
}
type = parser.next();
}
} catch (Exception e) {
e.printStackTrace();
} } /** 数据标志:1-data类型; 2-checkData */
int dataFlag = 0;
HashMap<String, String> dataMap = new HashMap<String, String>();
HashMap<String, String> lableAction = new HashMap<String, String>(); /**
* Add by walker Date 2017年8月29日
*
* @Description: TODO 加载自动化测试脚本,并执行自动化测试
* @param fileName
* 自动化测试脚本名称
*/
public void xmlLoad(String fileName) {
File file = new File(configPath + fileName);
try {
InputStream in = new FileInputStream(file);
// 创建xmlPull解析器
XmlPullParser parser = Xml.newPullParser();
/// 初始化xmlPull解析器
parser.setInput(in, "utf-8");
// 读取文件的类型
int type = parser.getEventType();
int depth = parser.getDepth();
String id = "";
String value = "";
String hasText = "";
String dataType = "";
String filedName = "";
String tableName = "";
ArrayList<Map<String, String>> actionList = new ArrayList<Map<String, String>>();
while ((type != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
id = "";
value = "";
hasText = "";
switch (type) {
// 开始标签
case XmlPullParser.START_TAG:
id = parser.getAttributeValue(null, "id");
hasText = parser.getAttributeValue(null, "hasText");
filedName = parser.getAttributeValue(null, "filedName");
if ("data".equals(parser.getName())) {
actionList = new ArrayList<Map<String, String>>();
dataFlag = 1;
dataType = parser.getAttributeValue(null, "dataType");
break;
}
if ("checkData".equals(parser.getName())) {
actionList = new ArrayList<Map<String, String>>();
dataMap = new HashMap<String, String>();
tableName = parser.getAttributeValue("", "tableName");
dataMap.put("dataName", tableName);
dataFlag = 2;
break;
}
value = parser.nextText();
lableAction = new HashMap<String, String>();
lableAction.put("dataType", dataType);
lableAction.put("ID", id);
lableAction.put("hasText", hasText);
lableAction.put("tableName", tableName);
lableAction.put("filedName", filedName);
lableAction.put("value", value);
lableAction.put("actionName", parser.getName());
actionList.add(lableAction);
if (dataFlag == 2 && !isEmptyUnNull(filedName) && !dataMap.containsKey(filedName)) {
dataMap.put(filedName, value);
} else {
} if ("clickView".equals(parser.getName())) {// 按钮
if (isEmptyUnNull(hasText) || solo.searchText(hasText, true)) {
clickOnView(id);
}
} else if ("inputText".equals(parser.getName())) {// 文本输入框
enterText(id, value);
} else if ("checkBox".equals(parser.getName())) {// 复选框
if ("1".equals(value)) {
chkSelect(id, true);
} else {
chkSelect(id, false);
}
} else if ("spinner".equals(parser.getName())) {// 下拉选择
selectData(value, id);
} else if ("sleep".equals(parser.getName())) {// 睡眠时间
try {
sleep(Integer.parseInt(value) / 1000);
} catch (Exception e) {
e.printStackTrace();
}
} else if ("onKeyDown".equals(parser.getName())) {// 按键事件
sendKey(value);
} else if ("clickOnText".equals(parser.getName())) {// 点击文本
solo.clickOnText(value + "");
} else if ("takeScreenshot".equals(parser.getName())) {// 屏幕拍照
solo.takeScreenshot();
solo.sleep(300);
} else if ("waitForActivity".equals(parser.getName())) {
sleep(2);
if (solo.waitForActivity(value)) {
break;
} else {
return;
}
}
break;
case XmlPullParser.END_TAG:
if ("data".equals(parser.getName())) {
dataType = "";
} else if ("checkData".equals(parser.getName())) {
sleep(1);
checkData();
}
break;
}
type = parser.next();
}
} catch (Exception e) {
e.printStackTrace();
}
} /**
* Add by walker Date 2017年9月1日
*
* @Description: TODO 根据配置文件校验数据
*/
private void checkData() {
StringBuilder sql = new StringBuilder();
sql.append("select * from ");
if (dataMap != null && dataMap.size() > 1 && !isEmptyUnNull(dataMap.get("dataName"))) {
sql.append(dataMap.get("dataName") + " where ");
Set<Entry<String, String>> set = dataMap.entrySet();
Iterator<Entry<String, String>> it = set.iterator();
Map.Entry<String, String> me;
while (it.hasNext()) {
me = it.next();
if ("dataName".equals(me.getKey())) {
} else {
sql.append(me.getKey() + "='" + me.getValue() + "' and ");
}
}
if ((sql + "").endsWith("and ")) {
sql.delete(sql.lastIndexOf("and"), sql.length());
}
// 校验数据逻辑,可根据具体项目进行配置,执行sql查询语句,获取查询结果,如在指定条件下获取查询结果为空,那么
HashMap<String, String> data = null;
// data = DbUtils.getInstance().queryFirstData(sql +"");
if (data != null && data.size() > 0) {
} else {
}
} else {
return;
}
} /**
* Add by walker Date 2017年9月4日
*
* @Description: TODO 延时器
* @param time
* 延时时间,单位为秒
*/
private void sleep(int time) {
long start = System.currentTimeMillis();
while ((System.currentTimeMillis() - start) < time * 1000) {
}
} /**
* Add by walker Date 2017年9月4日
*
* @Description: TODO 判断字符串是否为空
* @param value
* 字符串的值
* @return 如果字符串为null或""或"null"或者全部为空白字符则返回true,否则返回false。
*/
public static boolean isEmptyUnNull(String value) {
if (value != null && !"".equalsIgnoreCase(value.trim()) && !"null".equalsIgnoreCase(value.trim())
&& value.trim().length() != 0) {
return false;
} else {
return true;
}
} /**
* Add by walker Date 2017年9月4日
*
* @Description: TODO 模拟按键事件,按键前后进行延时
* @param keyStr
* 按键字符
* 支持的按键:back(回退键)、enter(确认键)、left(左键)、right(右方向键)、up(上方向键)、down(
* 下方向键)、数字(1-9)
*/
public void sendKey(String keyStr) {
int key = 0;
if ("1".equals(keyStr)) {
key = KeyEvent.KEYCODE_1;
} else if ("3".equals(keyStr)) {
key = KeyEvent.KEYCODE_3;
} else if ("4".equals(keyStr)) {
key = KeyEvent.KEYCODE_4;
} else if ("5".equals(keyStr)) {
key = KeyEvent.KEYCODE_5;
} else if ("6".equals(keyStr)) {
key = KeyEvent.KEYCODE_6;
} else if ("7".equals(keyStr)) {
key = KeyEvent.KEYCODE_7;
} else if ("8".equals(keyStr)) {
key = KeyEvent.KEYCODE_8;
} else if ("9".equals(keyStr)) {
key = KeyEvent.KEYCODE_9;
} else if ("back".equals(keyStr)) {
key = KeyEvent.KEYCODE_BACK;
} else if ("enter".equals(keyStr)) {
key = KeyEvent.KEYCODE_DPAD_CENTER;
} else if ("left".equals(keyStr)) {
key = KeyEvent.KEYCODE_DPAD_LEFT;
} else if ("right".equals(keyStr)) {
key = KeyEvent.KEYCODE_DPAD_RIGHT;
} else if ("up".equals(keyStr)) {
key = KeyEvent.KEYCODE_DPAD_UP;
} else if ("down".equals(keyStr)) {
key = KeyEvent.KEYCODE_DPAD_DOWN;
}
solo.sleep(30);
solo.sendKey(key);
solo.sleep(30);
} /**
* Add by walker Date 2017年9月4日
*
* @Description: TODO 选择对应信息
* @param selectedStr
* 选择的类型(文本内容)
* @param id
* 下拉选择控件ID
* @param title
* 选择框的标题字符串
* @param ranges
* 被选择范围-所选择的字符串必须在此范围内
*/
public void selectData(String selectedStr, String id) {
clickOnView(id);
solo.sleep(1000);
solo.clickOnText(selectedStr);
} /**
* Add by walker Date 2017年9月4日
*
* @Description: TODO 点击ID所指定的控件
* @param viewId
* 视图控件ID
*/
public void clickOnView(String viewId) {
try {
View view = getView(solo, viewId);
solo.clickOnView(view);
} catch (Exception e) {
e.printStackTrace();
}
} public View getView(Solo solo, String idStr) {
int id = solo.getCurrentActivity().getResources().getIdentifier(idStr, "id", packageName);
View v = solo.getView(id);
return v;
} /**
* Add by walker Date 2017年9月4日
*
* @Description: TODO 勾选框勾选功能
* @param id
* checkBox控件ID
* @param checked
* 设置是否选择此控件
*/
public void chkSelect(String id, final boolean checked) {
CheckBox chk = (CheckBox) solo.getView(id);
if (!chk.isChecked() && checked) {
solo.clickOnView(chk);
}
} /**
* Add by walker Date 2017年9月4日
*
* @Description: TODO 为文本录入框录入字符串,文本录入完成后延迟指定时间
* @param id
* 文本录入框ID
* @param msg
* 文本录入信息
*/
public void enterText(String id, String msg) {
EditText editText = (EditText) getView(solo, id);
if (editText != null) {
solo.clickOnView(editText);
solo.clearEditText(editText);
solo.enterText(editText, msg + "");
}
solo.sleep(200);
}
}

脚本编写

1、新建main.xml配置文件,根据标签testCase指定测试脚本文件。

2、根据动作标签编写测试脚本用例,以main.xml中指定的文件名为名。

3、将main.xml文件及脚本配置文件放到Android设备sd卡下Test文件夹下(如果文件夹不存在,那么新建此文件夹,文件名区分大小写)

执行脚本

  在目标Android设备安装上测试apk及被测试apk签名后在电脑命令行上执行如下命令即可实现自动化测试启动流程。


adb shell am instrument -e class com.walker.autotest.HtmlScariptTest -w com.walker.autotest/android.test.InstrumentationTestRunner

注意事件

1、测试apk安装包与被测试项目安装包apk的应用签名要一致。

2、测试项目如果引用库工程或者V4包时需要注意有无重复的包,本用例编写时测试工程引用了V4包,导致被测应用启动失败。

HTML脚本配置Android自动化测试的更多相关文章

  1. 解放双手——Android自动化测试

    解放程序猿宝贵的右手(或者是左手) http://blog.csdn.net/eclipsexys/article/details/45622813 --Android自动化测试技巧 Google大神 ...

  2. Android 自动化测试框架

    Android常用的自动化测试工具框架: Monkey,MonkeyRunner,UIAutomator,Robotium,Appium,Monkey Talk...... 但这些工具框架都是什么呢有 ...

  3. MonkeyRunner原理初步--Android自动化测试学习历程

    章节:自动化基础篇——MonkeyRunner原理初步 主要讲解内容及笔记: 一.理论知识和脚本演示 最佳方式是上官网文档去查看monkeyrunner的介绍,官网上不去,就找了一个本地的androi ...

  4. 【Mac + Appium + Python3.6学习(五)】之常用的Android自动化测试API总结

    Github测试样例地址:https://github.com/appium-boneyard/sample-code/tree/master/sample-code/examples ①定位text ...

  5. [技术博客] Android 自动化测试

    [技术博客] Android 自动化测试 安卓自动化测试工具与平台的搭建 类似于网页端自动化,安卓测试的自动化也主要是针对控件的自动化.其原理就是通过python(其他语言) 的脚本来代替我们手动完成 ...

  6. Android自动化测试--monkey总结

    什么是 Monkey Monkey 是一个 Android 自动化测试小工具.主要用于Android 的压力测试, 主要目的就是为了测试app 是否会Crash. Monkey 特点 顾名思义,Mon ...

  7. 使用 flow.ci 实现 Android 自动化测试与持续集成

    在上篇文章--如何实现 Android 应用的持续部署中,我们使用的是 flow.ci + Github + fir.im 实现 Android 应用的持续部署.对于 Android 开发者,他们可能 ...

  8. Android自动化测试-Robotium(一)简介

    一.Robotium原理 Robotium是一款Android自动化测试框架,主要针对Android平台的应用进行黑盒自动化测试,它提供了模拟各种手势操作(点击.长按.滑动等).查找和断言机制的API ...

  9. 转:Android开发实践:用脚本编译Android工程

    转自: http://ticktick.blog.51cto.com/823160/1365947 一般情况下,我们都是使用Eclipse+ADT插件或者Android studio软件来编译Andr ...

随机推荐

  1. (转)Windows7下命令行使用MySQL

    1 安装 我在Win7下安装的MySQL版本是mysql-5.0.22-win32 1.在Win7环境下安装MySQL,关于安装方法可以参考文章: Win7系统安装MySQL5.5.21图解教程.wi ...

  2. 为什么Java大数据是最火爆的编程语言?

    未来10年将是大数据,人工智能爆发的时代,到时将会有大量的企业需要借助大数据,而Java最大的优势就是它在大数据领域的地位,目前很多的大数据架构都是通过Java来完成的. 在这个Android.iOS ...

  3. oracle 审计日志清理

      --进入审计日志目录: cd $ORACLE_BASE/admin/$ORACLE_SID/adump --删除3个月前的审计文件: find ./ -type f -name "*.a ...

  4. HDU 6043 KazaQ's Socks (规律)

    Description KazaQ wears socks everyday. At the beginning, he has nn pairs of socks numbered from 11  ...

  5. Making the Grade (bzoj1592)

    题目描述       FJ打算好好修一下农场中某条凹凸不平的土路.按奶牛们的要求,修好后的路面高度应当单调上升或单调下降,也就是说,高度上升与高度下降的路段不能同时出现在修好的路中. 整条路被分成了N ...

  6. 最小截断[AHOI2009]

    [题目描述] 宇宙旅行总是出现一些意想不到的问题,这次小可可所驾驶的宇宙飞船所停的空间站发生了故障,这个宇宙空间站非常大,它由N个子站组成,子站之间有M条单向通道,假设其中第i(1<=i< ...

  7. Hibernate批量操作(二)

    Hibernate提供了一系列的查询接口,这些接口在实现上又有所不同.这里对Hibernate中的查询接口进行一个小结. 我们首先来看一下session加载实体对象的过程:Session在调用数据库查 ...

  8. Uva 10142 Australia Voting

    水题 模拟 大意就是模拟一个选举的系统 认真读题,注意细节,耐心调试 #include<cmath> #include<math.h> #include<ctype.h& ...

  9. 笨办法用js屏蔽被http劫持的浮动广告

    最近发现网站经常在右下角弹出一个浮动广告,开始的时候以为只是浏览器的广告. 后来越来越多同事反映在家里不同浏览器也会出现广告.然后深入检查了下,发现网站竟然被劫持了. 然后百度了一大堆资料,什么htt ...

  10. java正则表达式提取地址中的ip和端口号

    由于我需要用到java正则表达式提取地址中的ip和端口号,所以我就写了一个demo,测试一下,下面是demo public class Test0810_1 { public static void ...