App对接口异常数据的容错测试

背景

大家一定有遇到过这样的场景,一个功能,从测试直到上线,稳定跑了很长一段时间,都是妥妥的,但突然有一天,线上就出现很多崩溃报错,崩溃率急剧飙升,最后发现是接口数据返回出现异常,客户端没有进行很好的容错处理,导致崩溃。本篇文章介绍了App对接口异常数据的容错测试方法。

1、为什么要做app对接口异常数据的容错测试?

1.1、首先考虑一个问题:app客户端的数据来源是什么?

1)本地数据(读取本地文件、代码中配置等);

2)网络请求的数据。

App客户端网络请求的数据,一般都是以开发时约定好的json格式的形式来传输,如下(当然,也会有不是json的情况):

1.2、既然都是约定好的,那会有什么问题?

情况1:客户端开发认为:我请求了,就一定会给我结果,写代码时,就没有考虑到请求结果不返回的情况;

情况2:客户端开发认为:我请求了,就一定会给我正确的格式,写代码时,就没有考虑到请求返回的格式不正确的情况;

情况3:接口返回的某个字段,开发在处理时,没有考虑到取不到该字段的情况,没有对该字段判空处理;

情况4:接口返回的某个字段的value值,需要客户端进行二次处理,开发在处理时,没有考虑到一些异常值的情况;

情况5:进入后续功能,并且发起下一次的请求,请求参数依赖于上一个请求的响应字段,但该字段返回的值异常,开发未考虑到这个情况,未做处理。

1.3、以上情况,可能会有什么后果?
情况1:会导致用户始终卡在loading加载状态,可能无法进行取消或者其他后续操作;

情况2、3、4、5:空指针或数组越界报错,导致直接闪退。


以上几种情况,如果不进行专门的容错测试,从测试直到上线,稳定跑了很长一段时间,都是妥妥的,但突然有一天,线上就出现很多崩溃报错,崩溃率急剧飙升,最后发现是接口数据返回出现异常,客户端没有进行很好的容错处理,导致崩溃。

2、测试方法

2.1手工测试方法

测试工具:fiddler、charles或其他抓包工具,下面以fiddler为例。

情况1测试方法:

1、手机设置代理,但是不开启fiddler(模拟开启了wifi,但是却无法上网的情况),或者是开启fiddler,对被测的接口进行断点(并且始终不放开断点)。

2、进入相应的界面,查看现象,如果一直处于loading,就说明没有加超时处理。预期应该是loading一定时间后,显示无网络容错页面,或根据业务设计展示上一次的缓存数据。

情况2、3、4、5测试方法:
1、手机设置代理,开启fiddler。

2、将要测试的接口断点(bpu + url)。

3、在app中进行操作,使之请求该接口。

4、拦截到请求后,修改请求的响应结果(后面会细具体说怎么修改响应),并使之返回给app。

5、查看当前待测功能界面、及后续的界面的操作,查看是否会造成闪退问题。

需要将响应结果修改成哪些格式进行测试?

1、响应码改成404、500等异常状态;

2、改成非预期格式(比如预期整个数据是json,那就把整个数据改成非json格式);

3、依次删除每一个key;

4、依次修改每一个value的类型(string、int、array、object、null这五个类型之间相互修改);

5、依次修改每一个value的值(类型不变)。

2.2测试的几个原则

1)每个新增或修改的功能,只要涉及到接口数据交互的,都要进行接口数据容错测试,评估工期的时候要预留出测试时间;

2)修改了接口返回数据后,会造成客户端一些功能不可用,这是正常的现象,我们测试的原则是,不能有卡死或闪退的严重问题,如果开发不愿意改,必须坚持原则;

3)修改value的测试数据时,应该与业务相结合来选取,测试前要搞清楚每个字段是用来干什么的;

4)当前app界面如果测试发现不会闪退,但可能点击界面上的某个元素会造成闪退(即上述情况5),因此也需要测试当面界面上点击后续元素是否会造成闪退(比如一个书籍列表页,返回的某个书籍元素id格式不正确,列表页不会闪退,但点击这本书籍,进入下个界面时,可能会造成闪退)。

2.3 提高测试效率

由上述手工测试方法得知,测试是通过先修改响应结果,然后再操作客户端,完成单次容错测试。

但由于一个接口响应结果的字段非常多,每个字段又需要修改成好几种情况,因此完成一个功能所有响应字段的测试,可能需要修改上百次的响应数据,这就非常的耗时了。

我们可以考虑一种提高测试效率的思路:使用fiddler的AutoResponder功能,把接口响应永远指向本地的一个txt文件,然后通过python脚本,针对txt文件自动进行修改。

测试方法:

1、先把待测接口的正确返回值保存到本地文件,例如D:/orijson.txt (与代码中配置的路径要一致)。

2、使用fiddler的autoresponder功能,把待测接口的响应结果,设置为本地文件D:/testjson.json(与代码中配置的路径要一致)。

3、运行脚本(使用python2运行),此时会看到如下结果:

此时打开本地文件D:/testjson.json,可以看到内容已经被修改为测试内容:

此时只需要在app上进行操作,那fiddler就会自动把修改过的响应值返回给app客户端,我们只需要观察app的表现即可。

测试下一条case,只需要按回车即可,如此循环往复,直到提示测试完毕。

可优化的点:目前这个方案相当于是半自动化测试,后续可以结合app的ui自动化,做成全自动化测试。


具体的实现代码:

#coding=utf-8
import json


class RobTest:
    def __init__(self):
        self.ori_json = {}
        self.test_datas= ["--RobTestString--", 0, [1], {"--RobTestKey--": "--RobTestValue--"}, None]

    # 读取本地txt中的测试数据,并转换为json对象
    def get_ori_txt(self):
        # 在你电脑上建立一个txt文件,把该文件地址配置到下面这行,到时候测试时,把待测接口拿到的原始数据,复制到这个文件里保存即可。
        f = open('D:/orijson.txt', 'r')
        line = f.readline()
        res = ""
        while line:
            res += line
            line = f.readline()
        f.close
        self.ori_json = json.loads(res)
        return self.ori_json

    # 把测试json数据写入到testjson.txt文件中,以供fiddler进行autoresponse测试
    def write_test_file(self, key_name = None):
        self.test_jsontxt = json.dumps(self.ori_json)
        if key_name:
            self.test_jsontxt = self.test_jsontxt.replace("\"" + key_name + "\"", '"---ROBTEST---"')
        print self.test_jsontxt
        # 使用fiddler的autoresponse功能,把待测接口的自动返回改成以下这个你设置的本地路径
        with open('D:/testjson.txt', 'w') as f:
            f.write(self.test_jsontxt)
        #提示用户输入回车,进行下一条case的测试
        raw_input("-------输入回车后,可以继续测试下一个case-------")

    # 执行测试
    def do_test(self, testjson):
        a = testjson
        for key in list(a.keys()):
            # 测试key不存在
            self.write_test_file(key)
            # 测试value改变的各种情况
            tempvalue = a[key]
            for testData in self.test_datas:
                a[key] = testData
                self.write_test_file()
            a[key] = tempvalue

            # 如果当前键值对的value值是string,还需要测试空字符串的情况
            if isinstance(a[key],basestring):
                # 测试空字符串的情况
                a[key] = ""
                self.write_test_file()
                a[key] = tempvalue

            # 如果当前键值对的value值是dict,还需要测试空dict的情况,并且需要递归测试
            if isinstance(a[key], dict):
                # 测试空dict的情况
                a[key] = {}
                self.write_test_file()
                a[key] = tempvalue
                #递归测试
                self.do_test(a[key])

            #如果当前键值对的value是list,还需要测试空dict的情况,并且如果第一个元素是dict,需要递归测试
            if isinstance(a[key], list) and len(a[key]) > 0:
                # 测试空list的情况
                a[key] = []
                self.write_test_file()
                a[key] = tempvalue
                # 递归测试
                if isinstance(a[key][0], dict):
                    self.do_test(a[key][0])


if __name__ == "__main__":
    t = RobTest()
    t.get_ori_txt()
    t.do_test(t.ori_json)
    print "测试完毕"

展示评论