Android | 自动化测试辅助服务

今天聊聊Android的自动化测试,但这里先不讨论具体的技术方案,这些放到后面章节讨论,本文主要来跟大家分享一下自动化测试过程中一定会遇到的一些问题以及针对这些问题提供的一系列辅助服务。

UI自动化测试

不管是通过什么方案实现的UI自动化,录制回放也好、写自动化脚本也好,都会遇到同样的问题:在不同手机上安装被测应用时弹出的系统提示框,这部分肯定是没办法通过脚本实现的,而且存在兼容性问题:不同手机的安装流程一般是不一样的,那么怎么才能让安装这部分流程自动化呢?

想想去年很火的抢红包插件,其实解决的问题是类似的就是检测屏幕的UI变化然后让系统自动去点击相应的控件元素,我们的主角登场了:AccessibilityService 具体实现参考:https://github.com/logan62334/Jarvis

安装好辅助应用后,点击图标会打开系统的辅助功能页面,这里会看到系统服务中已经注册好了一个叫智能安装服务的条目,打开该服务即可。

大家可以根据不同手机的安装提示进行适配,目前项目只适配了华为和小米的机型。

Monkey

大家做Android稳定性测试的时候一定用过Monkey吧,但是实际跑的过程中会出现各种各样的问题,比如:关掉WIFI、关掉数据流量、锁掉屏幕等,接下来针对这些问题分析下如何解决:

针对关掉WIFI这种情况比较简单的做法就是通过起一个服务监听网络状态,一旦出现WIFI关闭的情况就自动打开当然这里涉及到权限的一些问题,只适用于root过的手机或者版本比较低的系统没有很严格的权限管理;另外一种情况就是利用部分手机可以调整快捷入口的方式将WIFI这个入口隐藏掉,还有就是可以通过遮挡住通知栏这样下拉操作就不会打开WIFI的快捷入口,数据流量也是一样的道理。

对于锁屏屏幕自动熄灭这样的情况,一种方式就是通过手机系统设置让屏幕常亮,另外一种方式就是通过PowerManager、KeyguardManager等来唤醒解锁Android设备或模拟器,具体实现方式参考:https://github.com/logan62334/Jarvis

Settings

另外可能在自动化测试的过程中我们希望控制系统的wifi、数据流量、动画等的状态,那么可以通过广播的形式来实现,具体方式参考:
https://github.com/logan62334/Jarvis


FullStackEngineer的公众号,更多分享

Share Comments

Python | 获取iOS设备信息的轻量级框架

今天接着上一篇Python | 获取Android设备信息的轻量级框架,来讲讲

如何通过Python实现一个轻量级的库来获取电脑上连接的iOS设备信息。

这个库只有一个文件,通过封装libimobiledevice命令实现,返回的是一个包含所有设备信息的标准json格式的列表方便解析,下面简单介绍一下:

libimobiledevice命令封装

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
@staticmethod
def get_ios_devices():
devices = []
output = Shell.invoke('idevice_id -l')
config_file = os.path.join(os.path.dirname(__file__), 'ios_mapping.json')
with open(config_file, 'r') as f:
config = json.loads(f.read())

if len(output) > 0:
udids = output.strip('\n').split('\t')
for udid in udids:
dic = {"os_type": 'iOS', "uid": udid}
output = Shell.invoke('ideviceinfo -u %s -k ProductType' % udid)
device_type = config[output.strip('\n')]
brand = ''
# -1表示找不到 0表示下标
if device_type.find("iPhone") != -1:
brand = 'iPhone'
elif device_type.find("iPad") != -1:
brand = 'iPad'
elif device_type.find("iPod") != -1:
brand = 'iPod'

dic['brand'] = brand
dic['model'] = device_type

output = Shell.invoke('ideviceinfo -u %s -k ProductVersion' % udid)
dic['os_type'] = 'iOS'
dic['os_version'] = output.strip('\n')
dic['rom_version'] = output.strip('\n')

output = Shell.invoke('idevicename -u %s' % udid)
dic['device_name'] = output.strip('\n')
devices.append(dic)
return devices

设备信息数据结构

1
2
3
4
5
6
7
8
9
10
11
[
{
"uid": "xxxxxxxxxxxxxx1f8a4dcfaac1fd01",
"rom_version": "11.0.3",
"brand": "iPhone",
"device_name": "马飞的 iPhone",
"os_version": "11.0.3",
"model": "iPhone6s",
"os_type": "iOS"
}
]

注:有时候会报Couldn’t connect to lockdown这样的错误,执行下面命令即可:

1
2
3
4
$ brew uninstall ideviceinstaller
$ brew uninstall libimobiledevice
$ brew install --HEAD libimobiledevice
$ brew install ideviceinstaller

这个库我已经上传到Pypi仓库,源码在github:https://github.com/logan62334/python-apptoolkit,点击阅读原文可以访问


FullStackEngineer的公众号,更多分享

Share Comments

Android | 图解外部存储和内部存储

存储概述

Android中根据数据是否为应用私有、是否需要给外部应用暴露以及数据的大小可以有以下几种选择:

  • Shared Preferences
  • 内部存储
  • 外部存储
  • 本地数据库存储
  • 通过网络在服务器端数据库存储

今天我们重点解释下内外部存储到底是什么有哪些区别,请看下图:

内外部存储的区别

  • 按照内外部存储:带External字眼则一定是外部存储的方法,如 getExternalFilesDir() ,外部存储需要运行时权限;
  • 按照公有私有性质:公有文件是Environment调用函数,而私有文件(包括内部私有与外部私有)是Context调用函数,公有文件不会随着app卸载而删除而私有则会,私有文件不会被Media Scanner扫描到。

FullStackEngineer的公众号,更多分享

Share Comments

Python | 获取Android设备信息的轻量级框架

今天跟大家分享一下,如何通过Python实现一个轻量级的库来获取电脑上连接的Android设备信息,为什么说轻量呢因为整个库也就4KB,相比其他诸如Appetizer这样动辄就8MB多的库要轻很多,而且也基本满足项目中的需求。

这个库只有一个文件,通过封装Android的ADB命令实现,返回的是一个包含所有设备信息的标准json格式的列表方便解析,下面简单介绍一下:

检查环境变量

1
2
3
4
5
6
7
8
9
10
# 判断是否设置环境变量ANDROID_HOME
if "ANDROID_HOME" in os.environ:
command = os.path.join(
os.environ["ANDROID_HOME"],
"platform-tools",
"adb")
else:
raise EnvironmentError(
"Adb not found in $ANDROID_HOME path: %s." %
os.environ["ANDROID_HOME"])

命令执行

1
2
3
4
5
6
7
8
9
class Shell:
def __init__(self):
pass

@staticmethod
def invoke(cmd):
output, errors = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
o = output.decode("utf-8")
return o

ADB命令封装

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
class ADB(object):
"""
参数: device_id
"""

def __init__(self, device_id=""):

if device_id == "":
self.device_id = ""
else:
self.device_id = "-s %s" % device_id

def adb(self, args):
cmd = "%s %s %s" % (command, self.device_id, str(args))
return Shell.invoke(cmd)

def shell(self, args):
cmd = "%s %s shell %s" % (command, self.device_id, str(args),)
return Shell.invoke(cmd)

def get_device_state(self):
"""
获取设备状态: offline | bootloader | device
"""
return self.adb("get-state").stdout.read().strip()

def get_device_id(self):
"""
获取设备id号,return serialNo
"""
return self.adb("get-serialno").stdout.read().strip()

def get_android_version(self):
"""
获取设备中的Android版本号,如4.2.2
"""
return self.shell(
"getprop ro.build.version.release").strip()

def get_sdk_version(self):
"""
获取设备SDK版本号,如:24
"""
return self.shell("getprop ro.build.version.sdk").strip()

def get_product_brand(self):
"""
获取设备品牌,如:HUAWEI
"""
return self.shell("getprop ro.product.brand").strip()

def get_product_model(self):
"""
获取设备型号,如:MHA-AL00
"""
return self.shell("getprop ro.product.model").strip()

def get_product_rom(self):
"""
获取设备ROM名,如:MHA-AL00C00B213
"""
return self.shell("getprop ro.build.display.id").strip()

设备信息获取

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
class DeviceInfo:
def __init__(self, uid, os_type, os_version, sdk_version, brand, model, rom_version):
self.uid = uid
self.os_type = os_type
self.os_version = os_version
self.sdk_version = sdk_version
self.brand = brand
self.model = model
self.rom_version = rom_version


class Device:
def __init__(self):
pass

@staticmethod
def get_android_devices():
android_devices_list = []
android_devices_infos = []
for device in Shell.invoke('adb devices').splitlines():
if 'device' in device and 'devices' not in device:
device = device.split('\t')[0]
android_devices_list.append(device)
for device_uid in android_devices_list:
device_info = DeviceInfo(device_uid, "Android", ADB(device_uid).get_android_version(),
ADB(device_uid).get_sdk_version(),
ADB(device_uid).get_product_brand(), ADB(device_uid).get_product_model(),
ADB(device_uid).get_product_rom())
android_devices_infos.append(device_info.__dict__)
return android_devices_infos

设备信息数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[
{
"uid": "BY2WKN1519078327",
"rom_version": "Che2-UL00 V100R001CHNC00B287",
"brand": "Honor",
"os_version": "4.4.2",
"sdk_version": "19",
"os_type": "Android",
"model": "Che2-UL00"
},
{
"uid": "GWY0217414001213",
"rom_version": "MHA-AL00C00B213",
"brand": "HUAWEI",
"os_version": "7.0",
"sdk_version": "24",
"os_type": "Android",
"model": "MHA-AL00"
}
]

安装&更新

1
pip install -U apptoolkit

这个库我已经上传到Pypi仓库,源码在github:https://github.com/logan62334/python-apptoolkit


FullStackEngineer的公众号,更多分享

Share Comments

Python | 一个快速实现CLI 应用程序的脚手架

今天跟大家分享一下如何快速实现一个Python CLI应用程序的脚手架,之所以会做这个是因为当时需要做一个运维的小工具希望用命令行的方式来使用,但是搜遍网上很多资料都没有系统讲解从开发、集成、发布、文档等一系列流程的文章。

工程结构


如上图,这就是一个比较规范的Python CLI应用项目了,下面一一讲下各文件的用途:

项目文档


这里我们用Sphinx来实现文档的自动生成,当然你要首先通过markdown和rst文件定义好文档的内容,然后进入docs目录执行 make html命令就可以在_build目录下生成对应的静态文件,如下图:

具体Sphinx如何使用以及配置后面会单独文章讲解

主工程


这里讲几个需要注意的地方
1、日志的配置:
这里可以全局设置日志的一些输出级别和格式化方式

2、cli文件
这里通过click库来实现

3、二进制文件打包

如上图,有时候我们的工程中会包含二进制文件,也就是非Python代码的文件,这时候如果还是像往常一样打包发布,安装的时候会发现无法找到此文件,所以需要在根目录的MANIFEST.in文件中加入

脚本


如下图,这里的make-release文件主要是用来自动控制版本的,如下图,通过Git 的提交记录了来作为项目的唯一版本号标识,再对init文件进行重新写入达到持续集成时版本号自增的目的。

单元测试


test文件夹中存放的就是项目的单元测试文件了,这里就不细展开讲了,后面会具体讲讲如何跟Jenkins集成实现静态代码检查

setup


最重要的就是setup.py这个文件了,项目最后打包发布到pypi仓库主要的配置信息都在这里了,如下图:

这个脚手架的项目地址:https://github.com/logan62334/python-cli-template
项目会持续更新,可以点击阅读原文访问


FullStackEngineer的公众号,更多分享

Share Comments

DevOps | 实现命令行交互自动化

嗨呀,好久没有更新了,最近几个月一直忙着部门内质量平台的建设,因为是新成立的小组所以很多东西都是从零开始做,这期间做了很多跟自动化、代码质量和工程效率相关的事情,接下来一段时间会慢慢把其中一些有趣的东西整理出来跟大家分享。

今天先来介绍一个Python中用来实现命令行交互自动化的模块,之所以会有这样的需求是因为我们希望把一些繁琐的命令行交互过程给透明化这样对用户来说会友好很多降低使用成本,如下图:

这里是一个典型的需要用户交互的命令行操作,当执行命令后会提示用户输入测试脚本文件名,回车后会再提示用户输入app的路径,如何让这一过程自动化呢?

就是它了shutit,其实还有个工具 pexpect 但是我试了好多次都没能达到想要的效果,而且网上大部分给出的解决方案也都是针对ssh登录自动化的,对于一个普遍的交互式命令行却不支持,当然也可能是我使用姿势不对?如果大家有通过pexpect实现的还请跟我交流哈


FullStackEngineer的公众号,更多分享

Share Comments