01 | 自动遍历测试方法与常见技术介绍
背景
- 自动化测试成本高,周期长,只能覆盖主场景
- 业务量大,手工测试无法覆盖所有功能点
自动化遍历测试
- code less:用例维护成本降低到最低
- automate:尽可能的自动化覆盖回归业务
常见遍历工具与技术
- google android原生monkey、app crawler
- 百度smartmonkey
- 腾讯newmonkey
- vigossjjj smart_monkey
- macaca的NoSmoke
- 头条zhangzhao maxim
- seveniruby appcrawler
02 | android monkey测试工具
优点
- 速度非常快
- 编码特别少
缺点
- 就像一只猴子不受控制,随机发生、随机点击、随机输入
安装
- android sdk自带
使用
- 基本配置(设置事件数量)
- 操作约束(指定app)
- 设置事件类型和频率
- 调试选项
- adb shell monkey [options] 事件计数
- 命令
1.adb shell monkey 100
对所有包随机操作
2.adb shell monkey -p com.xueqiu.android 100
对指定包
3.adb shell monkey -p com.xueqiu.android -s 20 80
重复刚才的操作
4.adb shell monkey -p com.xueqiu.android -vv -s 20 80
详细日志
5.adb shell monkey -p com.xueqiu.android --throttle 5000 100
每个事件延迟5000ms执行
6.adb shell monkey -p com.xueqiu.android --pct-touch 10 1000
时间百分比 - 常用事件
1.--pct-touch
触摸事件(点击)
2.--pct-motion
动作时间(直线滑动)
3.--pct-trackball
轨迹事件(移动、点击、曲线滑动)
4.--pct-majornav
主要导航事件(回退按键、菜单按键)
03 | AppCrawler跨平台自动遍历测试
与其他框架额关系
- appcrawler底层引擎
1.appium
2.adb
3.macaca
4.selenium
AppCrawler环境要求
- appcrawler2.5
1.java8 appium 1.8.x - 安装方法
1.直接下载https://github.com/seveniruby/AppCrawler
2.从源码编译
快速启动
- 启动appium
- 启动模拟器或连接真机
- 开始自动遍历
appcrawler --capability "appPackage=com.xueqiu.android,appActivity=.view.WelcomeActivityAlias"
执行参数与配置文件
- capbility设置:与appium完全一致
- testcase:用于启动app后的基础测试用例
- selectedList:遍历范围设定
- triggerActions:特定条件触发执行动作的设置(如关闭广告)
- 执行参数比配置文件优先级高
testcase
- testcase完整形态
1.given:所有的先决条件
2.when:先决条件成立后的行为
3.then:断言集合 - testcase的简写形态
1.xpath:对应when里的xpath
2.action:对应when的action
action
- back
- monkey随机事件
- 执行代码
1.Thread.sleep(3000)
2.driver.swipe(0.9,0.5,0.1,0.5)
3.click
4.longTap
自动遍历支持
- selectedList:需要被遍历的元素范围
- firstList:优先被选中并执行的元素
- lastList:最后被选中并执行的元素
- tagLimitMax:同祖先的元素最多点击次数
- backButton:当所有元素都被点击后默认后退控件定位
- blackList:黑名单
- maxDepth:遍历的最大深度(每点开一个activity,深度加1)
触发器triggerActions
- 需要特定次数的触发动作
- 通常用于处理弹框
1.xpath:指定具体按钮
2.action:动作
3.times:规则的使用次数
工作过程
- 信息的获取(把当前app的界面下载为xml结构)
- 获取待遍历元素
1.遍历范围 selectedList
2.过滤黑名单、小控件、不可见控件、blacklist
3.重排控件顺序 firstList lastList
4.跳过已点击+跳过限制点击的控件tagLimit
5.根据匹配的规则执行action - 循环上面的步骤
日志
java -jar appcrawler-2.4.0-jar-with-dependencies.jar --capability "appPackage=com.xueqiu.android,appActivity=.view.WelcomeActivityAlias"
- 当前目录生成大量丰富的图片日志
- appcrawler.log 搜索 current index
生成配置文件模板
-
java -jar appcrawler-2.4.0-jar-with-dependencies.jar --demo
生成配置文件模板 - demo.yml
---
pluginList: []
saveScreen: true
reportTitle: ""
resultDir: "20200518154719"
waitLoading: 500
waitLaunch: 6000
showCancel: true
maxTime: 10800
maxDepth: 10
capability:
noReset: "true"
fullReset: "false"
appium: "http://127.0.0.1:4723/wd/hub"
appPackage: "com.xueqiu.android"
appActivity: ".view.WelcomeActivityAlias"
testcase:
name: "TesterHome AppCrawler"
steps:
- xpath: "//*[@text='行情']"
action: click
selectedList:
- xpath: "//*[contains(@resource-id,'single_line_wrapper')]//*[@clickable='true']"
- xpath: "//*[contains(@resource-id,'ll_main')]//*[@clickable='true']"
firstList:
- xpath: "//*[contains(@resource-id,'ll_main')]//*[@clickable='true']"
lastList:
- given: []
when: null
then: []
xpath: "//*[@selected='true']/..//*"
action: null
actions: []
times: 0
- given: []
when: null
then: []
xpath: "//*[@selected='true']/../..//*"
action: null
actions: []
times: 0
backButton:
- given: []
when: null
then: []
xpath: "Navigate up"
action: null
actions: []
times: 0
triggerActions:
- given: []
when: null
then: []
xpath: "share_comment_guide_btn"
action: null
actions: []
times: 0
xpathAttributes:
- "name"
- "label"
- "value"
- "resource-id"
- "content-desc"
- "instance"
- "text"
sortByAttribute:
- "depth"
- "list"
- "selected"
findBy: "default"
defineUrl: []
baseUrl: []
appWhiteList: []
urlBlackList: []
urlWhiteList: []
blackList:
- given: []
when: null
then: []
xpath: ".*[0-9]{2}.*"
action: null
actions: []
times: 0
beforeRestart: []
beforeElement:
- given: []
when: null
then: []
xpath: "/*"
action: "Thread.sleep(500)"
actions: []
times: 0
afterElement: []
afterPage: []
afterPageMax: 2
tagLimitMax: 2
tagLimit:
- given: []
when: null
then: []
xpath: "??"
action: null
actions: []
times: 1000
- given: []
when: null
then: []
xpath: "?§Ý"
action: null
actions: []
times: 1000
- given: []
when: null
then: []
xpath: "share_comment_guide_btn_name"
action: null
actions: []
times: 1000
assertGlobal: []
04 | 多设备管理平台STF
简介
- https://github.com/openstf/stf
- OpenSTF是个手机设备管理平台
- 可以对手机进行远程管理、调试、远程手机桌面监控等操作
安装
- 拉取镜像
1.docker pull openstf/stf:lastest
2.docker pull sorccu/adb:lastest
3.docker pull rethinkdb:lastest
- 启动rethinkdb
docker run -d --name rethinkdb -v /srv/rethinkdb:/data --net host rethinkdb rethinkdb --bind all --cache-size 8192 --http-port 8090
- 启动stf
docker run -d --name stf --net host openstf/stf stf local --allow-remote
- 访问
http://localhost:7100
远程调试真机
- 真机打开调试模式,与STF在同一网段
-
adb -s 设备名 tcpip 5555
给设备开调试端口 -
adb -s RFCMC00LKZH shell ifconfig
查看设备的ip地址,若访问被拒绝,可在设备中直接查看 -
adb connect 192.168.0.xx:5555
重新连接设备 - 若设备没有连接可重启adb再连接
adb kill-server
adb start-server
adb connect 192.168.0.xx:5555
05 | slenium Grid方案
简介
- slenium grid远程运行selenium test
- 在多个机器上并行运行selnium
优点
- 所有测试的入口
- 管理和控制浏览器运行的notes/环境
- 扩展
- 并行运行测试
- 跨平台测试
- 负载均衡
流程图
下载与使用
- 官网:http://www.selenium.dev/downloads/
- github:https://github.com/SeleniumHQ/selenium/wiki/Grid2
- 注:使用前要配置好selenium、webdriver的环境变量
启动Hub
java -jar selenium-server-standalone-3.141.59.jar -role hub
启动Node
- 先配置nodeConfig.json文件
{
"capabilities":
[
{
"browserName": "firefox",
"marionette": true,
"maxInstances": 5,
"seleniumProtocol": "WebDriver"
},
{
"browserName": "chrome",
"maxInstances": 5,
"seleniumProtocol": "WebDriver"
},
{
"browserName": "internet explorer",
"platform": "WINDOWS",
"maxInstances": 1,
"seleniumProtocol": "WebDriver"
},
{
"browserName": "safari",
"technologyPreview": false,
"platform": "MAC",
"maxInstances": 1,
"seleniumProtocol": "WebDriver"
}
],
"proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
"maxSession": 5,
"port": -1,
"register": true,
"registerCycle": 5000,
"hub": "http://localhost:4444",
"nodeStatusCheckTimeout": 5000,
"nodePolling": 5000,
"role": "node",
"unregisterIfStillDownAfter": 60000,
"downPollingLimit": 2,
"debug": false,
"servlets" : [],
"withoutServlets": [],
"custom": {}
}
- 在不同窗口执行以下命令,可启动多个node
java -jar selenium-server-standalone.jar -role node -nodeConfig nodeConfig.json
代码
- 注:若想并发执行,需用多线程
from selenium.webdriver import DesiredCapabilities
from selenium.webdriver import Remote
class TestGrid:
def test_grid(self):
hub_url = "http://127.0.0.1:4444/wd/hub"
capability = DesiredCapabilities.CHROME.copy()
for i in range(1,5):
driver = Remote(command_executor=hub_url,desired_capabilities=capability)
driver.get("https://www.baidu.com")
06 | 基于jenkins的自动化调度
持续集成
- 自动化冒烟测试
- 自动化用例运行
- 自动化遍历测试运行
- 兼容性测试
核心依赖资源
- 设备集群:真机、模拟器、云端设备
- 管理平台:STF,jenkins,selenium Grid
- 测试用例:遍历工具,测试工具
07 | app自动化测试平台实战1
参考资料
https://android.googlesource.com/platform/system/core/+/refs/tags/android-6.0.0_r5/adb/
Monkey
- 官网 https://developer.android.com/studio/test/monkey
- 基本语法
adb shell monkey [option]
- monkey参数
monkey [-p ALLOWED_PACKAGE [-p ALLOWED_PACKAGE] ...]
[-c MAIN_CATEGORY [-c MAIN_CATEGORY] ...]
[--ignore-crashes] [--ignore-timeouts]
[--ignore-security-exceptions]
[--monitor-native-crashes] [--ignore-native-crashes]
[--kill-process-after-error] [--hprof]
[--pct-touch PERCENT] [--pct-motion PERCENT]
[--pct-trackball PERCENT] [--pct-syskeys PERCENT]
[--pct-nav PERCENT] [--pct-majornav PERCENT]
[--pct-appswitch PERCENT] [--pct-flip PERCENT]
[--pct-anyevent PERCENT] [--pct-pinchzoom PERCENT]
[--pct-permission PERCENT]
[--pkg-blacklist-file PACKAGE_BLACKLIST_FILE]
[--pkg-whitelist-file PACKAGE_WHITELIST_FILE]
[--wait-dbg] [--dbg-no-events]
[--setup scriptfile] [-f scriptfile [-f scriptfile] ...]
[--port port]
[-s SEED] [-v [-v] ...] # 伪随机数生成器的seed值,相同seed值再次执行monkey,产生相同的事件序列
[--throttle MILLISEC] [--randomize-throttle]
[--profile-wait MILLISEC]
[--device-sleep-time MILLISEC]
[--randomize-script]
[--script-log]
[--bugreport]
[--periodic-bugreport]
[--permission-target-system]
COUNT
- monkey命令
adb shell monkey -p com.xueqiu.android --throttle 500 -s 100 100
adb shell monkey -p com.xueqiu.android --throttle 500 --pct-touch 10 --pct-syskeys 90 100
maxim
https://github.com/zhangzhao4444/Maxim
- 下载安装
1.git clone https://github.com/zhangzhao4444/Maxim.git
2.将2个jar包推送到手机
adb push framework.jar /sdcard
adb push monkey.jar /sdcard
3.执行命令
adb shell CLASSPATH=/sdcard/monkey.jar:/sdcard/framework.jar exec app_process /system/bin tv.panda.test.monkey.Monkey -p com.panda.videoliveplatform --uiautomatormix --running-minutes 60 -v -v
4.调用关系cat /system/bin/monkey
# Script to start "monkey" on the device, which has a very rudimentary
# shell.
#
base=/system
export CLASSPATH=$base/framework/monkey.jar
trap "" HUP
exec app_process $base/bin com.android.commands.monkey.Monkey $*
appcrawler
- github地址:https://github.com/seveniruby/AppCrawler
- 生成配置文件
java -jar appcrawler-2.4.0-jar-with-dependencies.jar --demo
- 工作原理
1.从页面的中间元素开始遍历
2.然后从左往右遍历
3.遍历深度(打开的activity数量)超过最大值,执行返回操作
4.遍历同级元素(activity下的拥有相同父节点的元素)超过最大限定值,执行返回操作 - demo.yml
testcase(前置操作)
testcase:
name: "TesterHome AppCrawler"
steps:
- xpath: "我的"
action: click
- xpath: //*
action: driver.swipe(0.5,0.9,0.5,0.1)
- xpath: "设置"
action: click
triggeraction(去掉广告,处理弹窗)
triggerActions:
- xpath: "//*[@resource-id='com.xueqiu.android:id/iv_close']"
action: click
selectedList
capability:
noReset: "true"
fullReset: "false"
appium: "http://127.0.0.1:4723/wd/hub"
appPackage: "com.xueqiu.android"
appActivity: ".view.WelcomeActivityAlias"
testcase:
name: "TesterHome AppCrawler"
steps:
- xpath: "//*[@resource-id='com.xueqiu.android:id/tv_search']"
action: click
- xpath: "//*[@resource-id='com.xueqiu.android:id/search_input_text']"
action: "alibaba"
- xpath: "阿里巴巴-SW"
action: click
- xpath: "//*[@resource-id='com.xueqiu.android:id/stockCode' and @text='09988']"
action: click
selectedList:
- xpath: "//*[@resource-id='com.xueqiu.android:id/tab_indicator_view']//android.widget.TextView"
- xpath: "//*[@resource-id='com.xueqiu.android:id/small_period_container']//android.widget.TextView"
firstList:
- xpath: "//*[@resource-id='com.xueqiu.android:id/tab_indicator_view']//android.widget.TextView"
08 | app、web自动化测试平台实战2
selenium工具集
- selenium remote control:selenium 1.0版本
- selenium webdriver:selenium 2.0版本
- selenium server
- selenium client
- selenium IDE
- selenium Grid:分布式管理设备和脚本运行
环境搭建
- selenium-server
1.下载地址:https://www.selenium.dev/downloads/
2.启动hub
java -jar selenium-server-standalone-3.141.59.jar -role hub
3.访问http://192.168.1.100:4444/grid可在页面上查看注册到hub上的结点设备
4.配置appnode.json(配置说明可参考https://www.cnblogs.com/yyoba/p/11304034.html
)
{
"capabilities": [{
"deviceName": "127.0.0.1:62001",
"version": "4.4.2",
"maxInstances": 1,
"platform": "ANDROID",
"browserName": ""
}],
"configuration": {
"cleanUpCycle": 2000,
"timeout": 30000,
"proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
"hub": "127.0.0.1:4444/grid/register",
"url": "http://127.0.0.1:5723/wd/hub",
"host": "127.0.0.1",
"port": 5723,
"maxSession": 1,
"register": true,
"registerCycle": 5000,
"hubPort": 4444,
"hubHost": "127.0.0.1",
"hubProtocol": "http"
}
}
5.启动node结点
appium -p 5723 --nodeconfig appiumnode.json
adb -s 设备编号
若链接多台设备,可用"-s"指定设备编号
adb -s 设备编号 shell am start -n package/activity
6.代码
import os
from appium import webdriver
class TestGrid:
def setup(self):
caps = {}
caps['platformName'] = 'Android'
caps['platformVersion'] = '6.0'
caps['appPackage'] = 'com.xueqiu.android'
caps['appActivity'] = '.view.WelcomeActivityAlias'
caps['noRest'] = True
# udid = os.getenv("udid")
caps['udid'] = "127.0.0.1:7555"
# caps['udid'] = udid
self.driver = webdriver.Remote("http://192.168.1.100:4444/wd/hub", caps)
self.driver.implicitly_wait(10)
def teardown(self):
self.driver.quit()
def test_search(self):
self.driver.find_element_by_id("com.xueqiu.android:id/tv_search").click()
self.driver.find_element_by_id("com.xueqiu.android:id/search_input_text").send_keys("alibaba")
7.遍历本机设备
#!/bin/bash
for i in $(adb devices | grep "devices" | awk '{print $1}')
do
echo "start:{$i}"
udid=$i pytest test_search.py &
done
09 | 自动化测试平台实战3-STF
STF解决的问题
- 远程设备手动调试
- 远程设备自动化调试
安装方式
- mac
brew install rethinkdb graphicsmagick zeromq protobuf yasm pkg-config
- linux:docker
拉取镜像
docker pull openstf/stf:latest
docker pull sorccu/adb:latest
docker pull rethinkdb:latest
启动容器(rethinkdb,openstf)
docker run -d --name rethinkdb -v /srv/rethinkdb:/data --net host rethinkdb rethinkdb --bind all --cache-size 8192 --http-port 8090
docker run -d --name stf --net host openstf/stf stf local --allow-remote
关闭容器
docker stop stf
- windows不建议安装
搭建局域网STF
docker run -d --name stf3 --net host openstf/stf stf local --allow-remote --public-ip 192.168.0.103
docker logs -f stf3
查看日志
搭建自动化调试
- 参考资料
1.https://vbanthia.github.io/angular-swagger-ui/
2.https://github.com/openstf/stf/blob/master/doc/API.md
- 获取token
STF - Setting - keys:新建keys - 调用接口
1.获取用户信息:
curl -H 'Authorization: Bearer 4eea142d8c4a4fee9f8426e7eb7602422ea04144589143a9b9bc60bc8dddc35e' http://localhost:7100/api/v1/user | jq .
2.获取所有设备信息,并进行重要字段提取
curl -H "Authorization: Bearer $token" http://localhost:7100/api/v1/devices | jq -c '.devices[] | [.serial,.present,.remoteConnectUrl]'
3.获取所有可用的设备
curl -H "Authorization: Bearer $token \
" http://localhost:7100/api/v1/devices |
jq -c '.devices[]|[.serial,.present,.remoteConnectUrl]'|
grep true | awk -F \" '{print $2}'
4.stf.sh
#!/usr/bin/env bash
set -uxo pipefail
STF_TOKEN=xxx
STF_URL=xxx
DEVICE_SERIAL=xxx
if [ "$DEVICE_SERIAL" == "" ]; then
echo "Please specify device serial using ENV['DEVICE_SERIAL']"
return 1
fi
if [ "$STF_URL" == "" ]; then
echo "Please specify stf url using ENV['STF_URL']"
return 1
fi
if [ "$STF_TOKEN" == "" ]; then
echo "Please specify stf token using using ENV['STF_TOKEN']"
return 1
fi
function get_device
{
curl -H "Authorization: Bearer $STF_TOKEN \
"$STF_URL/api/v1/devices | \
jq -c '.devices[]|[.serial,.present,.remoteConnectUrl]'| \
grep true | awk -F \" '{print $2}'
}
# 授权
function add_device
{
response=$(curl -X POST -H "Content-Type: application/json" \
-H "Authorization: Bearer $STF_TOKEN" \
--data "{\"serial\": \"$DEVICE_SERIAL\"}" $STF_URL/api/v1/user/devices)
success=$(echo "$response" | jq .success | tr -d '"')
description=$(echo "$response" | jq .description | tr -d '"')
if [ "$success" != "true" ]; then
echo "Failed because $description"
return 1
fi
echo "Device $DEVICE_SERIAL added successfully"
}
# 将设备拉到本地
function remote_connect
{
response=$(curl -X POST \
-H "Authorization: Bearer $STF_TOKEN" \
$STF_URL/api/v1/user/devices/$DEVICE_SERIAL/remoteConnect)
success=$(echo "$response" | jq .success | tr -d '"')
description=$(echo "$response" | jq .description | tr -d '"')
if [ "$success" != "true" ]; then
echo "Failed because $description"
return 1
fi
remote_connect_url=$(echo "$response" | jq .remoteConnectUrl | tr -d '"')
adb connect $remote_connect_url
echo "Device $DEVICE_SERIAL remote connected successfully"
}
function remove_device
{
response=$(curl -X DELETE \
-H "Authorization: Bearer $STF_TOKEN" \
$STF_URL/api/v1/user/devices/$DEVICE_SERIAL)
success=$(echo "$response" | jq .success | tr -d '"')
description=$(echo "$response" | jq .description | tr -d '"')
if [ "$success" != "true" ]; then
echo "Failed because $description"
return 1
fi
echo "Device $DEVICE_SERIAL removed successfully"
}
5.同时启动多台设备
function get_device
{
curl -H "Authorization: Bearer $STF_TOKEN" $STF_URL/api/v1/devices | jq -c '.devices[]|[.serial,.present,.remoteConnectUrl]'
}
function get_remote_device
{
get_device | grep true | awk -F \" {'print $4'}
}
function run
{
get_remote_device|while read line; do { nohup adb -s $line shell monkey 1000 &};done
}
启动本地emulator设备
emulator -list-avds
emulator @设备