本文首发于:行者AI
游戏我的项目研发时,须要搭建一个自动化测试的平台,以冀望场内战斗应用自动化来测试,发现局内bug,防止重复劳动、进步测试效率以及防止人为的操作谬误。其中环境要求应用我的项目须要应用Airtest、poco对接强化学习的服务器,实现Airtest将状态信息发送给服务器,服务器返回下一步的决策。
1. 后期筹备工作
理解Airtest、poco、强化学习agent的决策形式。
1.1 Airtest介绍
Airtest基于图像识别的自动化测试框架。这个框架外围不在实现形式和技术上,而是理念!这个框架的理念借用是MIT(麻省理工)研究院的成绩 Sikuli ,他们构思了一种全新的UI测试模式,基于图像识别控件而不是具体内存里的控件对象。
(1)Airtest特点
- 反对基于图像识别的可程式化测试工具
- 跨平台
- 生成测试报告
- 反对poco等SDK内嵌,进步UI辨认精度
(2)Airtest界面(蕴含点击、滑动、判断、截屏等接口)
(3)Airtest成果演示
以上演示的是Airtest与间接通过图像识别对界面产生交互。如若须要与界面指定元素交互,是须要Poco提供的办法对界面上的元素进行操作
(4)Airtest局限性
然而在理论工程中,图像不会变化无穷,咱们须要捕捉我的项目的动静节点,针对动静节点进行点击、挪动等操作(比方商店买旗子的地位节点)
咱们须要另一个工具Poco。
1.2 Poco介绍
目前Poco只反对原生Android和ios的接口调用,其余平台均须要接入对应平台的sdk
(1)Poco获取UI树的形式
从根节点开始向下遍历子节点
在unity我的项目中,须要在unity中装置Poco的SDK
(2)Poco调用办法
(3)Poco调用举例
poco = UnityPoco()
poco('btn_start').click()
Airtest通过接口调用unity中的pocosdk,SDK对整个ui树进行遍历,将dump后的json信息传回Airtest。
Airtest在失去的UI树中找到‘btn_start’的元素地位信息,通过adb进行点击操作。
1.3 强化学习的简述
Environment 通常利用马尔可夫过程来形容,Agent 通过采取某种 Policy 来产生Action,和 Environment 交互,产生一个 Reward。之后 Agent 依据 Reward 来调整优化以后的 Policy。
用上图更形象的解释,state是环境的一个状态,observation是Agent察看到的环境状态,这里observation和state是一个意思。首先Agent察看到环境的一个状态,比方是一杯水,而后Agent采取了一个行为,这个行为是Agent把杯子中的水给打碎了,这样环境的的状态曾经产生了变动,而后零碎会对这个行为打一个分数,来通知Agent这样的行为是否正确,而后依据新变动的环境状态,Agent再采取进一步的行为。Agent所谋求的指标就是让Reward尽量的大。
2. 我的项目执行过程:
2.1 背景
通过将训练好的一个Agent部署到服务器上,其他人通过拜访服务器,流程如下:
a. 测试端收集信息->测试端将信息转成约定好的state格局->测试端将state发给服务器->服务器返回一个Agent的决策->测试端收到信息执行决策->
b. 测试端收集信息(新一轮循环的开始)…
在这个过程中,测试端收集信息耗时最为重大,针对我的项目需要决定对其局部进行优化。
2.2 具体问题
poco首次调用dump接口时会启动大量mincap等诸多可执行文件,导致7秒左右的提早。
Airtest操作遇到的提早过于重大,导致游戏每回合可操作工夫30秒内,只能进行4-5个动作。
然而本地训练的agent前期每回合操作数能达到16个左右,导致前期agent动作不能齐全在客户端上做完。
2.3 解决方案
提前加载poco的click事件
定位到dump耗时重大,决定从sdk的接口登程,缩小dump出的json文件大小。
a. 在unity的接口中,退出tagfilter、blacklist、propertylist参数,来管制json的文件大小。
其中tagfilter用于针对指定tag的unitygameobject的筛选,能够去除除UI和Default以外的所有物体。
blacklist用于针对unitygameobject名字的筛选,能进步dump效率50%
propertylist用于缩小单个unitygameobject的参数写入,默认单个物体有10多个参数,筛选后能够省下6个左右的参数。可进步dump效率33%
b. 在python的接口应用对应接口参数
该办法完满解决了操作提早的问题,目前客户端单回合30秒能够实现20个左右的动作。
2.4 具体步骤:
layerfilter 在本次我的项目中,有13个layer。只对tag为UI和Default的UGO进行递归写入子节点信息,剔除掉场景、特效等层级,能够大幅缩小开销。
namefilter 并非所有UI节点信息都是自动化测试须要取得的必要数据。所以在递归查问子节点时,遇到写入黑名单的UGO的名字时,能够缩小约50%的工夫开销。
次要批改C#的poco中AbstractDumper中的dumpHierarchyImpl接口,具体如图:
private Dictionary<string, object> dumpHierarchyImpl (AbstractNode node, bool onlyVisibleNode, Dictionary<string, object> extrapar)
{
if (node == null)
{
return null;
}
Dictionary<string, object> payload = new Dictionary<string, object>();
if (extrapar != null && extrapar.ContainsKey("param4") && extrapar["param4"] != null)
{
payload = node.enumerateAttrs(extrapar["param4"].ToString());
}
else
{
payload = node.enumerateAttrs(null);
}
Dictionary<string, object> result = new Dictionary<string, object> ();
string name = (string)node.getAttr ("name");
result.Add ("name", name);
result.Add ("payload", payload);
List<object> children = new List<object>();
if (extrapar!= null)
{
if (extrapar.ContainsKey("param3") && extrapar["param3"] != null)
{
requirelayer = extrapar["param3"].ToString().Split('|').ToList();
string layer = (string)node.getAttr("layer");
if (!requirelayer.Contains(layer))
{
//Debug.LogError("--dumpHierarchyImpl layer is not contains");
return result;
}
}
if (extrapar.ContainsKey("param2") && extrapar["param2"] != null)
{
try
{
filterlist.Clear();
string str = extrapar["param2"].ToString();
filterlist = str.Split('|').ToList();
}
catch
{
Debug.LogError("~~~dumpHierarchy Implextrapar param2 error");
}
if (filterlist.Contains(name))
{
return result;
}
}
}
foreach (AbstractNode child in node.getChildren())
{
if (!onlyVisibleNode || (bool)child.getAttr ("visible"))
{
children.Add (dumpHierarchyImpl (child, onlyVisibleNode, extrapar));
}
}
if (children.Count > 0)
{
result.Add ("children", children);
}
return result;
}
propertyfilter json默认dump出的一个节点参数蕴含:
name、payload、type、visible、pos、size、scale、anchorPoint、zOrders、clickable、components、_ilayer、layer、_instanceId等参数。咱们剔除掉了
visible|scale|anchorPoint|clickable|components|_ilayer|layer|_instanceId实际上用不上的参数,大大减少的dump进去json的文件大小,能够缩小约33%的工夫开销。
次要批改C#的poco中UnityNode中的enumerateAttrs、GetPayload接口,具体如下:
private Dictionary<string, object> GetPayload(string blackList)
{
Dictionary<string, object> all = new Dictionary<string, object>() {
{ "name", gameObject.name },
{ "type", GuessObjectTypeFromComponentNames (components) },
{ "visible", GameObjectVisible (renderer, components) },
{ "pos", GameObjectPosInScreen (objectPos, renderer, rectTransform, rect) },
{ "rawpos", GameObjectVec3Pos (objectRawPos) },
{ "rawrectpos", GameObjectVec3Pos (objectRectRawPos) },
{ "size", GameObjectSizeInScreen (rect, rectTransform) },
{ "scale", new List<float> (){ 1.0f, 1.0f } },
{ "anchorPoint", GameObjectAnchorInScreen (renderer, rect, objectPos) },
{ "zOrders", GameObjectzOrders () },
{ "clickable", GameObjectClickable (components) },
{ "text", GameObjectText () },
{ "components", components },
{ "texture", GetImageSourceTexture () },
{ "tag", GameObjectTag () },
{ "_ilayer", GameObjectLayer() },
{ "layer", GameObjectLayerName() },
{ "_instanceId", gameObject.GetInstanceID () },
};
Dictionary<string, object> payload = new Dictionary<string, object>();
if (!string.IsNullOrEmpty(blackList))
{
List<string> black_list = blackList.Split('|').ToList();
foreach(KeyValuePair<string, object> it in all)
{
if(black_list.Contains(it.Key))
{
continue;
}
payload.Add(it.Key,it.Value);
}
}
else
{
payload = all;
}
return payload;
}
发表回复