说明

  在项目配置完基于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. Python操作Zip文件

    Python操作Zip文件 需要使用到zipfile模块 读取Zip文件 随便一个zip文件,我这里用了bb.zip,就是一个文件夹bb,里面有个文件aa.txt. import zipfile # ...

  2. Windows环境下安装scikit-learn

    scikit-learn是Python的一个机器学习库,请按照以下步骤进行安装. 1.首先确保你的机器安装了Python并且配置好了环境变量. 2.安装pip 下载地址:https://pypi.py ...

  3. react-router 离开路由前确认

    react路由在做离开前确认时,有两种方法 第一种是我们写的是动态路由,可以做一个简单的离开前确认 path: '/association/administration', component: Ad ...

  4. 勤快的love枫[ZJOI2007]

    题目描述 小绝恋love 枫是一个出纳,经常需要做一些统计报表的工作.今天是绝恋love 枫的生日,小绝恋love 枫希望可以帮爸爸分担一些工作,作为他的生日礼物之一.经过仔细观察,小绝恋love 枫 ...

  5. 一个UITableViewCell的简单动画效果

    实现下面UITableViewDelegate的方法: forRowAtIndexPath:(NSIndexPath *)indexPath{ CATransform3D rotation; rota ...

  6. 扩展Python模块系列(一)----开发环境配置

    本系列将介绍如何用C/C++扩展Python模块,使用C语言编写Python模块,添加到Python中作为一个built-in模块.Python与C之间的交互目前有几种方案: 1. 原生的Python ...

  7. 在CentOS 6上安装Apache和PHP

    本文演示如何在CentOS 6上安装Apache和PHP.CentOS 6自带的是Apache 2.2.3和PHP 5.1.6,您可以使用默认的CentOS包管理器进行安装yum.使用yum(而不是使 ...

  8. 疯狂的采药 洛谷p1616

    题目背景 此题为NOIP2005普及组第三题的疯狂版. 题目描述 LiYuxiang是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师.为此,他想拜附近最有威望的医师为师.医师为了判断他的资质,给他 ...

  9. 从理解开始 谈谈px rem 和 em 的区别与联系

    概述 古语有云,没有规矩则不成方圆.秦灭六国之后为了促进国内生产力的发展,也是大力推进全国度量衡的统一.车同轨,书同文.与"尺寸"相关的问题(手动滑稽),从古至今一直为人们所关注. ...

  10. 重新认识alias:通过alias让rm更安全

    rm的悲剧总是发生在不经意之间,所以无论是在shell脚本中还是交互式bash环境下,执行rm命令时总应该三思三思再三思.也因此,很多人想尽办法防止文件误删除,方法也各种各样. 1.1.1 alias ...