关于自动化测试:基于深度强化学习的局内战斗自动化测试探索

58次阅读

共计 5051 个字符,预计需要花费 13 分钟才能阅读完成。

本文首发于: 行者 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;
  }

正文完
 0