您的当前位置:首页正文

客户端SDK测试思路 + SDK如何进行自动化测试

2024-11-24 来源:个人技术集锦

本文来自

客户端SDK测试思路

是什么

        客户端SDK是为第三方开发者提供的软件开发工具包,包括SDK接口、开发文档和Demo示例等。SDK和应用之间是什么关系呢?以云信即时消息服务为例,如下图所示,应用客户端通过调用云信SDK接口,进行消息等数据查询存储等操作,或通过协议与云信服务器间进行通信。

测什么

1. 客户端SDK测试的对象

        客户端SDK测试,就是对提供给开发者的工具包里面的内容进行测试,因此测试的主要内容有:

  • SDK接口和文档
    SDK接口是测试的主要对象,也是核心的内容。

  • SDK日志
    对开发者来说,SDK接口里面的具体实现是透明的,当上层调用时遇到问题,只能依赖SDK打印的日志来定位分析。所以SDK日志是否完备,是否有助于解决问题,对应用开发者和SDK提供方来说都很重要。

  • Demo或行业解决方案
    Demo是SDK提供方用来示例如何调用接口实现具体的功能,也可以作为开发者直观感受SDK接入效果。行业解决方案类似Demo,但是,比Demo更加像一个产品,具有比较完整和典型的行业应用场景。可以让行业开发者比较明确知道,接入这个SDK做出来的产品效果如何。

  • 其他周边
    比如UIkit等,可能只是在SDK开发中的附带输出,但对有的开发者来说能极大降低接入成本。

2. 客户端SDK接口测试类型

        客户端SDK根据需求和开发平台不同,可能需要选择不同的测试类型对SDK接口进行测试,常见的测试类型有:

  • 功能测试
    保证SDK接口功能正确性和完备性。客户端SDK接口测试跟服务端接口测试类似,包括场景覆盖和接口参数覆盖。主要测试各种参数组合下的返回值,考虑数据是否缓存与存储,是否有回调,对于请求成功或失败都能按预期进行处理。

  • 性能测试
    保证SDK接口满足特定的性能需求,比如资源占用、移动设备耗电量等。在云信IM登录的场景,登录时可能收到大量同步数据包和离线消息包,那么对这些数据包的解析以及本地储存的性能就要进行保证,否则可能出现登录响应很慢甚至卡住的问题,所以测试时就需要考虑这个场景的性能。

  • 兼容性测试
    保证SDK兼容特定的设备平台,并与其他软件兼容。兼容设备平台的工作量通常是比较大的,先根据产品需求和市场现状对需要适配的设备平台做分析,再根据需要覆盖的机型、系统版本、分辨率等进行优先覆盖排序。移动端SDK兼容性测试需要考虑下对模拟器的支持,因为很多开发者可能就是先在模拟器上开发。客户端SDK覆盖多平台设备的,还要考虑多端消息数据包的互通。

  • 稳定性测试
    考察业务场景在一定压力下,持续运行一段时间,接口功能和设备资源占用有无异常。比如云信实时音视频通话场景中,要保证多人长时间通话且不断有人进出时的接口功能和设备资源占用无异常。

  • 网络相关测试
    保证在不同网络类型,不同网络环境下,SDK接口都能较好的处理。在涉及到多媒体资源或音视频通信,弱网下测试的需求较多,并且弱网下的处理通常需要反复优化和对比,不仅是新老版本效果对比,还包括竞品的效果对比测试。

  • 安全性测试 对隐私数据保护,访问权限的控制,用户服务鉴权等,SDK接口的安全性问题也是比较突出。安全性很多是在架构设计和开发设计中就考虑进去,但是最好还是有专门的安全性测试。

功能怎么测

        上述诸多测试类型中,功能测试先行。在进行客户端SDK测试前,需要全面的了解测试对象的细节:

  • 了解业务流程,结合API接口文档和开发指南,理顺接口的使用场景和调用关系;

  • 了解SDK协议,理解协议中字段的意义以及服务器端的处理逻辑;

  • 了解各接口或协议返回码,分析对应的场景;

  • 了解开发实现细节,可以绘制成图,便于测试分析和分层验证。

对客户端SDK进行测试,可以采用的分层测试方式由上至下依次有:基于Demo和解决方案->基于接口调用->基于代码。

基于Demo和解决方案的测试

        大多客户端SDK在提测时,都会有对应的Demo或者解决方案提交给测试,因此可以覆盖到该Demo或解决方案对应的接口或业务场景。而且测试人员可以比较直观的看到界面表现,上手快,所以在客户端SDK测试中比较常用,也是比较有效的。
        但这种测试方式的缺点也很多,Demo对接口和业务场景覆盖比较有限,对接口的输入输出参数不能全覆盖,发现问题时定位复杂度增加。精心设计的Demo以及多解决方案的形式或许可以最大程度满足测试需要,但是需要较大的Demo开发测试投入,也使得问题暴露的时间大大滞后。 基于Demo和解决方案的测试,可以是手工的也可以是UI层自动化测试。

基于接口调用的自动化测试

        基于接口调用的测试,包括对单个接口的测试,也包括业务场景的覆盖。这种测试方式直接有效,需要一定开发基础,可以参考下KS上之前其他同学关于AOS和iOS SDK接口自动化测试实践的经验总结。目前,我所在项目组的同事也有一些实践,以云信iOS SDK测试为例,最小回归测试对应接口也已经自动化,测试工程基本结构如下:

        基于接口调用的自动化测试,需要有有产品的思路、开发的知识和测试的思维,做起来有难度。但是因为SDK接口通常比较稳定,所以一旦实现并投入使用,测试效率和质量的收益都很大,值得拥有。

基于代码的单元测试

        单元测试是为开发代码质量保驾护航的一个重要环节,在测试左移推进的道路上,大家越来越意识到单元测试的重要价值。特别是在一些核心业务上,值得开发同学投入精力去做。

        其他测试类型的展开,跟应用层测试类似,杭研QA白皮书介绍的方法、工具基本能满足测试需求,就不再重复了。



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 接口的验证

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

根据上图示例如下:

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

        其中 com.umeng.auto.track 为广播的 action name 用以区分类别 ei 为一个 int 数,相当于图中的 caseides 为参数内容,参数的协议可以自由定义,建议使用 json 类型,方便对不同类型的数据进行处理这样,我们只需要在广播中以 ei 写一个 switch 语句,执行不同的行为,如果测试不同参数的效果,可以使用 es 传递内容。

2、Activity 级别初始化的验证

        如果使用广播,是没办法绑定生命周期,即如果 SDK 需要在 Activity 的 onCreate() 中进行一些类初始化操作,是没法进行控制的。所以对于这种情况就需要使用 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 文件中的常量,并重新运行:

def changeConstant(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 的测试无法使用

三、iOS 端 SDK 自动化测试流程

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 的状态。

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

按图说明:

  1. 如架构图描述的,需要主 App 和守护 App 同时连接才可执行测试;
  2. 在连接完成后,状态直接迁移到等待测试指令的状态,没有任何动作;
  3. 有些组合状态可以合成一个状态,如运行守护 App 状态时可能主 App 断开连接,也可能保持连接,所以区分为两态分别管理;
  4. 当自动化测试框架启动后,除了监听两个 App 同时连接,其他状态都是在已有 App 连接完成的前提下进行的,所以大部分时间是在执行测试 case 调用及 App 切换的。

感谢对本文的审校。

显示全文