北京物流信息联盟

【友盟+】首创SDK自动化测试框架~图文详解

2021-08-31 15:35:27

作者:【友盟+】高级无线开发工程师 吴玉强、王飞

Tip:纯技术男撰稿,4000字,速读5分钟,消化2小时,开始脑暴吧~

         


为了确保 SDK 线上运行的稳定性,我们需要在开发后进行 SDK 测试,而为了提高测试效率,而且在拓展新项目的同时能兼顾已有项目的稳定性,在有限的资源内解放测试人员到更紧急的项目中来,就需要一个自动化工具来完成工作。

【友盟+】首创自动化工具,能够自动传不同参数、抓取输出数据,并自动验证数据准确性,输出结果,保障项目顺利稳定发布。

 

相对 App 的测试方案,市面上已经有非常多且成熟的 UI 级别的自动化测试框架,却鲜有针对 SDK 提供的自动化测试方案,原因是 SDK 属于为 App 提供服务的“插件”。一个 App 可接入一到多个 SDK 在内,而在项目中模块化是非常普遍的架构,所以 SDK 是针对细分功能提供服务的组件,有的提供数据服务、地图服务或节省开发成本的组件等等,这只能 SDK 开发者根据功能自行完成测试。

 

本篇说明的 SDK 测试方案是针对数据服务的 SDK 功能覆盖,皆包含 SDK 的 API、网络数据及缓存相关的逻辑测试,即非UI的纯数据逻辑的覆盖。

 

本篇是自动化测试基础上的延伸,相对安卓系统可以便利的通过 adb 指令控制如App安装、卸载、退出应用等“系统”级操作,iOS 在控制 App 层面上只能通过一些间接的手段完成上面几点需求,为了易于维护,在控制器中以有限状态机模式进行了构造,以便于后续增加更多的操作状态和测试用例。

一、测试框架概览


1

测试框架

整个测试流程就如下面描述的有向图,以 Pytest 驱动客户端执行任务,然后将客户端输出的请求数据进行截取处理,而后验证是否通过测试用例。


2

Android端测试框架

Android可以使用adb命令与app进行数据上的通信,如发送广播,启动Activity等。同时也可以使用shell命令对配置文件进行修改,再进行gradle编译,实现对app级别参数的修改,从而完成不同参数对app程序影响的验证。

3

iOS端测试框架

iOS由于系统特性,无法如安卓系统灵活运用系统命令来操作 App SDK,所以以一个 Socket 连接 Server 端进行通信。

另外在 iOS 系统上又可利用 Runtime 的特性,将传输的字符串转化为 API 调用,这样做的好处是将 Socket 模块和 Runtime 解析模块编入应用中就无需再次打包,只需 Python 端编好代码和测试 case,所有的功能调用都由两端约定的协议解析执行即可。


二、Android


1

SDK接口的验证

对于集成SDKapp,如果需要在App运行时,触发一个行为,可以通过广播来实现。可以根据action name完成对行为类型的分类,根据caseid完成对行为的区分。如下图所示:

 


根据上图示例如下:

os.system(            "adbshell am broadcast -a com.umeng.auto.track --es param \"" + str(es) + "\" --ei caseId " + bytes(ei))


其中com.umeng.auto.track为广播的action name用以区分类别;

ei为一个int数,相当于图中的caseid;

es为参数内容,参数的协议可以自由定义,建议使用json类型,方便对不同类型的数据进行处理


这样,我们只需要在广播中以ei写一个switch语句,执行不同的行为,如果测试不同参数的效果,可以使用es传递内容。


2

Activity级别初始化的验证

如果使用广播,是没办法绑定生命周期,即如果SDK需要在ActivityonCreate()中进行一些类初始化操作,是没法进行控制的。所以对于这种情况就需要使用adb命令中的启动Activity命令,基本流程与广播类似,但是caseid的处理在onCreate()中:

根据上图示例如下:

  os.system("adb shell am start -n " + self.pkgname + "/." + activity + " --es param \"" + str(es) + "\"  --ei caseId " + bytes(ei))


其中pkgname为包名,activity为activity的名字es为需要传入的内容,ei为一个int数,即caseId。与广播方式类似,只是将switch放到了onCreate中,根据ei和es进行相应的操作。


3

Application级别的验证

以上说的两种方式几乎可以涵盖SDK测试的部分case,但是对于部分SDK,初始化需要在程序一启动的Application中执行,这时上面的两种方式显然满足不了需求。


这时有两套方案可以应对。如下图所示:


二次编译

如上图所示,左边的部分,我们可以通过修改Java文件完成对Appliction中内容的修改,如在Application中会有一些静态常量,使用python修改java文件中的常量,并重新运行:

defchangeConstant(self, source,des):

        path =os.path.join(os.path.dirname(sys.path[0]),  'autotestAndroid')

        gradle_path= os.path.join(path,'app','src','main','java','deep','autotest','utils','Constant.java')

        print'-----gradle_path----',gradle_path

        if os.path.exists(gradle_path):

           build_file = open(gradle_path, 'r+')

            lines =build_file.readlines()

            for i in range(len(lines)):

               line = lines[i]

                if' '+source in line:

                   arr = line.split('=')

                   line = arr[0]+ '='+des+";\n"

                   lines[i] = line

           build_file = open(gradle_path, 'w+')

           build_file.writelines(lines)

            p =buildprocess.CompileProcess(path)

           p.start()

 

 

        else:

print'nonono='+ gradle_path

 


使用这种方式的好处是:

•   可以直接修改Application中的常量,如AppKey等,不用管是否执行了Application的onCreate()

•   不用考虑外设情况

•   同样适配对AndroidManifest.xml的测试

缺点是:

•   需要绑定工程路径

•   文件内容类型较多,容易出错,代码不具备通用性,有一定的二次开发难度

•   需使用gradle重新编译,如工程较大,耗时较长

配置文件:

除了上述方法,也可以在Application中读取一个SD卡配置文件,根据配置文件的协议进行对应的操作。每次只需更改配置文件的内容,并通过adb push放入SD卡指定路径中,然后重启App即可。


这样做的好处是:

•   配置文件的协议可以随意定义,更灵活

•   配置文件可以使用json格式,修改更简单

•   只需推到SD卡,耗时更少

•   不需要绑定工程路径


缺点是:

•   只能在Application的onCreate之后进行,局限性较大。

•   依赖外设SD卡

•   AndroidManifest的测试无法使用。

 

三、iOSSDK自动化测试流程


1

引入“守护”App

如「iOS端测试框架」所见,此时进行通信只有一个应用,这个应用就是我们用来测试 SDK Demo,通过这个宿主我们可以触发 SDK 提供的任何 API,通过 iOS runtime 我们可以触发 SDK 的类方法、实例方法甚至是私有 API,但这写都只局限于一个应用“沙盒”内,如上面说到的安装、卸载及 App 退出和切到后台就无能为力了,所以我们引入了另一个 Demo(Watch Demo),通过两个 Demo 的协同操作满足“沙盒”之外的需求。


两个 App 互相唤醒和通信

 

如上面提到的,所有功能调用都基于约定的协议来执行的,协议的设计也是不断新增的测试需求改造的。

 

2

业务协议

最初 Server 端与客户端以测试用例的 case id 来区分需要触发的事件,后来 case id 所代表的含义太多,而且客户端也是以运行时不断调用 Server 端发送指令的形式表现执行的具体功能,所以转为一条执行序列更加灵活及方便扩展。


一个测试用例可分为多条执行序列,执行序列内的协议包含了需要进行的方法调用或事件的处理。


以 Dplus 为例,如下数据包含了部分操作的执行序列:

"operations":{

    "$umeng_cloudayc_op9": {

        "arguments": {

            "param": [

                "$umeng_cloudayc_op*"

            ]

        },

        "type": "class",

        "class":"DplusMobClick",

        "method": "track:"

    },

    "$umeng_cloudayc_op5": {

        "arguments": {

            "param": []

        },

        "next":"$umeng_cloudayc_op9",

        "type": "class",

        "class":"DplusMobClick",

        "method":"clearSuperProperties"

    }

},

"type":"invoke",

"description":"401",

"first":"$umeng_cloudayc_op5"


由于是针对 SDK API 测试的协议,所以协议内的格式以调用的类名、方法名及参数为主,再加上部分细节参数加以说明,如 type 是 class 则调用类方法,是 instance 是示例方法。

 

需要注意的是,这个队列的结构是个字典,以标识前缀 $umeng_cloudayc_op 作为一个子事件的 key,value 则是其执行参数。而且可以看到在参数 param 的 value 里也有和子事件的 key 类似的值,这里的设计也是为了满足部分嵌套调用的需求。


举例来说,如此时需要通过一个接口验证之前缓存的数据是否发送正常,就要分三步:

第一存储数据;

第二将数据读出;

第三将第二步的结果作为参数传入最后调用的接口即可。

这样既能满足各种嵌套逻辑,又能实现远程构造客户端系统的实体对象作为参数进行接口调用。

 

回到上面的字典的结构,实际上在之前的协议格式使用的是数组作为执行序列的封装格式,不过在实际应用中无法满足灵活的要求,就如上面所说的组合的调用逻辑,有部分子事件是被动调用的,通过在其他事件内的参数检测来触发调用,如果是数组则无法控制这个执行序列的依赖关系。采用字典后,增加启动字段,在后续关联的子事件内,都会说明下一个执行的子事件,如果某个子事件是作为另外子事件的参数,则不会有 next 字段,因为它是被动触发的,不在执行队列之内。

 

在这个业务协议开发过程中,不断的根据测试需求进行改造、添加,从一开始的单一应用调用接口,到后面的多应用切换、前后台切换以及应用断开和重连,需要多套控制流程,在具体实现时,分散到了各个业务逻辑中,每增加一个控制都要兼容考虑是否会影响到其他模块,而且作为一个自动化测试“框架”,提前梳理好核心部分的流程会让之后更易于开发和维护,所以就引入了有限状态机的概念进行构造。


3

有限状态机


有限状态机(Finite-state machine)可用于模拟很多事物逻辑,顾名思义,它是一个有限的状态的处理逻辑,有下面几个特征:

状态数是有限的;

在当前时刻只有一种状态存在;

一个状态在满足某个条件后会切换到另一状态。


而有限状态机整体可以归纳为四个要素:现态、条件、动作以及次态。

 -现态:指当前时刻所表现的状态;

-条件:又称为事件,即当前状态在满足这个条件后会触发一个动作,从而进行状态装换;

-动作:即在现态满足条件后需触发的一系列操作,动作完成后即状态进行迁移。动作也可以忽略,在某些情况下,现态满足条件后,也无需执行任何动作就切换到新的状态。

-次态:是相对现态而言,表示了条件满足后迁移的状态,次态也可以与现态相同。


根据业务逻辑的特性及复杂程度,合适的使用有限状态机,可以使得逻辑表达清晰、封装及维护都很直观和方便。当一个业务包含的状态越多,就越适合使用优先状态机进行封装处理。


有限状态机应用非常广泛,如电子电路、编译器及网络协议 TCP 协议状态机等;需要注意的是要区分“动作”和“状态”,如果将“动作”也视为“状态”会导致编写状态机时产生问题。


4

有限状态机应用自动化测试

将业务逻辑应用到有限状态机,前提是需要熟悉对应的业务,并将其中的状态、动作和条件等抽离出来,然后再做进一步的划分和关联,构造出一个完整的有向图。

 

在自动化测试中,有如下几个关键词:

启动测试、监听、主App连接、守护App连接、接口调用、进入后台、进入前台、应用退出、崩溃、断开连接、重连等。

 

在日常开发中,如果遇到上面的”事件”,可能就顺其自然的开始写判断、写调用,可能不自觉的就写出了一个“有限状态机”,不过不会那么严格的区分什么是动作什么是状态,只要满足最后的结果就能达成目的。


但现在我们有意识的利用有限状态机进行划分,分离出状态和动作以及状态迁移的条件。看上面的关键字,好像都是一个个“动作”,仔细看“监听(中)”又可能是一个状态,但实际上我们还得需要结合业务的理解再抽象出一些状态,如“进入后台”,则是跳转到了守护 App,当前是控制守护 App 的状态;若是“进入前台”则守护 App 跳转到了“主App”,是控制主 App 的状态。

 

如下图就用刚才抽象出的关键词构造了一个简单的有限状态机:

按图说明:

-如架构图描述的,需要主App和守护App同时连接才可执行测试;

-在连接完成后,状态直接迁移到等待测试指令的状态,没有任何动作;

-有些组合状态可以合成一个状态,如运行守护App状态时可能主App断开连接,也可能保持连接,所以区分为两态分别管理;

-当自动化测试框架启动后,除了监听两个App同时连接,其他状态都是在已有App连接完成的前提下进行的,所以大部分时间是在执行测试case调用及App切换的。




友情链接

Copyright © 2023 All Rights Reserved 版权所有 北京物流信息联盟