有幸能参与到实验室学长的一个项目当中,简单记录一下整个项目和一些收获
概述
UniRLTest 即 Universal Reinforcement Learning Test,是基于图像识别、深度强化学习等实现的跨平台软件测试工具
前端
前端部分主要负责与后端和设备进行通讯,接收后端传来的操作指令并在设备上执行,获取设备上应用的屏幕截图并传给后端进行分析
文件大致结构如下:
1 | ../urt/urt-frontend |
前端的运行需要一个配置文件作为额外参数(e.g. configs/android/anymemo.json),配置文件结构如下:
1 | { |
后端
此部分内容根据论文初稿整理
后端的功能主要分为两大模块:
图像处理
提取组件和页面布局特征
- 使用 Canny Edge Detection 技术从屏幕截图中提取组件
- 根据提取到的组件构建一棵组件树来表示页面布局特征
组件 Embedding
- 组件图像经过卷积神经网络处理得到一个 4096 维向量,表示组件的图像信息
- 将屏幕截图的组件位置变为全白而其余地方变为全黑而得到的图片,经过卷积神经网络处理得到一个 4096 维向量,表示组件的位置信息(不使用坐标是为了相对图像信息有足够的权重)
- 组件图像经过卷积神经网络处理得到一个 14 维独热向量,表示组件的类型
- 将上述结果拼接而成一个 8206 维的向量,即为 embedding 的结果
- 以上所说的卷积神经网络,是一个预训练好的用于判断组件类型的 VGG-16 模型,4096 维的向量是其倒数第二层的输出,14 维的向量是其最后一层的输出
页面布局 Embedding
- 将树状结构的页面布局转化为字符串
- 将该字符串使用预训练好的 LSTM 模型处理,输出 512 维的向量,即为 embedding 的结果
页面(app 状态)表示
- 计算所有组件的 embedding 向量的平均值,即用一个 8206 维的向量表示所有组件
- 将组件和页面布局的 embedding 结果连接成 8718 维的向量,表示整个页面(app 状态)
强化学习
这部分在此没有详细展开说明
- 动作生成
- 用 17 维的独热向量表示动作类型
- 用 8026 维的向量表示实施动作的组件(没有则做 zero-padding)
- 用一维向量表示 window size 参数(没有则做 zero-padding)
- 表示一个动作的向量由上述向量连接而成
- Q-Network 模型训练
- Q-Network 使用含有四个隐藏层的全连接神经网络
- replay-memory 中保存了每次状态转换以及其对应的奖励值,也是训练模型的数据来源
- 随着测试的进行,模型需要被不断训练和更新
- 动作决定
- 通过 Q-Network 的输出可以得到当前页面下,所有可能动作被执行的优先级
- 使用 Boltzmann Strategy 决定下一个被执行的动作(避免被困在局部最优解)
测试
Web 应用的测试和 PC 端应用的测试本人没有参与,所以以下内容只涉及安卓应用测试
安卓应用插桩测试方法
环境准备:
- Java:jdk8(较老的 gradle 无法用 jdk11 运行)和 jdk11(较新的 gradle 需要至少 jdk11 运行)
- Gradle:不用额外安装,项目中自带的 gradlew 脚本能在当前自动安装对应版本的 gradle
- Android SDK:使用 Android Studio 统一管理,环境变量中正确设置ANDROID_HOME的值
构建原项目:
在项目目录下执行如下命令进行构建:
1
./gradlew --no-daemon --stacktrace assembleDebug
–no-daemon 表示 Gradle 守护进程会在命令执行完成后自动退出,不会常驻后台(因为不同应用依赖不同版本的gradle,守护进程没法很好的被复用,无需常驻后台)
–stacktrace 表示若报错会打印堆栈信息
assembleDebug 表示构建 debug 包
这一步的目的是保证应用本身可正常构建(其构建产物不会被后续使用)
添加插桩代码:
备份 app 目录下的 build.gradle,并添加一个 apply 语句以应用 jacoco 插件
在 app 目录下添加 jacoco-multi.gradle 或 jacoco-multi-kt.gradle,包含了 jacoco 配置,其中后者是应对 kotlin 项目的配置(该配置文件是一个骨架,后面需要进行修改)
备份并修改 AndroidManifest.xml 配置文件,注册 InstrumentedActiviy 和 JacocoInstrumentation
向源码中加入三个文件:FinishListener、InstrumentedActiviy 和J acocoInstrumentation
(上述 app 目录为源码所在目录,一般为 app,也有可能为其它名称,该脚本若未检测到 app 目录,则会提示输入该目录的名称,需要用户自行判别目录并输入,有时源码所在目录就是项目目录,则输入一个点.
表示项目目录即可;AndroidManifest.xml 文件默认路径为 app/src/main 下,若检测不到,会要求用户输入其完整路径;源码 java 目录默认为 app/src/main/java,若检测不到,会要求用户输入其完整路径)
- 检查源码语言是纯 java 还是使用了 kotlin,若使用了 kotlin,检查是否在 app/build.gradle 中 apply 了带 “-kt” 的 jacoco 配置,若没有,则需进行替换
- 查看 build/intermediates 下的 javac 或 classes 目录,若没有 javac 目录而有 classes 目录,则将 jacoco配置中的 javac 全部替换成 classes
- (假定上一步是 javac 目录)在 javac 目录下查看 app 构建类型,一般是 xxxDebug,选一个作为测试类型,以后只用该类型的 apk(在 build/outputs/apk 目录下有对应的 apk 文件),修改 jacoco 配置中的 variantName 变量为测试类型的名称
构建插桩后的应用:
使用相同的命令构建:
1
./gradlew --no-daemon --stacktrace assembleDebug
这一步若出现报错,可能有以下几种情况:
java 版本不对(仔细查看报错信息,调整 jdk 版本)
jacoco 配置出错,无法设置 outputLocation 属性。这种情况是由于 gradle 版本较低导致的,将jacoco 配置中的 outputLocation 全部替换成 destination,然后将以下三行改掉:
1
2
3
4
5
6// classDirectories.from = files(...)
// sourceDirectories.from = files(...)
// executionData.from = ecFile
classDirectories = files(...)
sourceDirectories = files(...)
executionData = files(ecFile) // 注意这里要套一个files
安装并运行插桩后的应用:
检查本机是否已经连上用于测试的安卓设备(真机或虚拟机):
1
adb devices
项目目录下,在用于测试的设备上安装插桩后的应用:
1
adb [-s {设备名称}] install ./app/build/outputs/xxxDebug.apk
通过源码查看应用的{包名},并在测试设备上运行安装的应用:
1
adb [-s {设备名称}] shell am instrument {包名}/.test.JacocoInstrumentation
此时手机会打开插桩后的应用,随便交互几步后,关闭该应用并在后台进程中杀死该应用(若不杀死则会不断生成新的覆盖率数据)
- 若插桩后的应用存在打开时闪退的问题,可能是由应用的主题导致的:
- 尝试修改 app/src/main/AndroidManifest.xml 中 application 的 android:theme 属性,将原来的例如 AnyMemo.Theme.Dark 修改为 AnyMemo.Theme.Dark.NoActionBar**,再重新构建和安装
- 插桩后出现闪退问题的一般定位方法如下:
- 打开一个命令行,使用
adb logcat *:D
命令跟踪日志 - 到另一个命令行中启动 instrumentation
- 切换到第一步的命令行,ctrl+c 停止打印,自底向上查找相关的报错信息
- 打开一个命令行,使用
导出覆盖率数据并生成报告:
覆盖率数据文件被保存在 /data/data/<包名>/files 目录下,通过如下命令导出这些文件:
1
2
3
4
5
6
7
8adb shell
初次导出需要创建中间目录
mkdir /sdcard/coverage
下面run-as后为应用包名
run-as org.liberty.android.fantastischmemo
mv files/*.ec /sdcard/coverage
目标路径为空则默认为当前目录
adb pull /sdcard/coverage [目标路径]下面将 coverage 目录下所有 .ec 文件移动到该应用的项目目录下的 app/build/jacoco 目录下(没有则创建),在项目目录下使用如下命令生成报告(报告生成在 app/build/reports 目录下):
1
./gradlew --no-daemon --stacktrace multiJacocoTestReport
安卓测试实验流程
- 在安卓设备上安装插桩后的应用
- 在前端项目目录下的 configs/android/ 目录下添加被测应用对应的配置信息(xxx.json)
- 在服务器上启动后端(执行命令:sh run.sh)
- 在本地启动前端(执行命令:python urt_android xxx.json)
- 测试设备开始运行被测应用,UniRLTest 测试进行中 …
- 测试结束,覆盖率数据生成在前端项目目录下的 coverages/xxx/ 目录下
- 手动生成测试报告(具体步骤已在安卓应用插桩测试中讲过)