跟着 App 用户量的不断增长,任何小的问题都可能放大成严重的线上事故,为了防止对App造成损害的任何可能性,咱们必须从各个方面去考虑 App 的安稳性建造,尽可能减少任何潜在的要挟。

1.布景介绍

为了保证 App 的安稳性,咱们现在有 XMoney 智能遍历测验(溃散、界面紊乱、加载异常等)、UI 主动化(溃散和事务逻辑验证)、Top1000 小程序遍历(溃散和事务逻辑报错)、接口安稳性建造(溃散和事务逻辑验证)。 今日要给咱们介绍的是接口安稳性建造,便是在后端回来数据如果不可靠的情况下,App 是否仍然能够安稳运转。

2.完成思路

计划一

Mock有专门的Mock渠道, 溃散有专门的溃散监控渠道, 咱们只需求把Mock数据下发到手机,如果有溃散,会被溃散渠道收集到,溃散会由值班同学分发给研制。

  1. 将接口代理到Mock渠道,Mock渠道依据response 生成批量的Mock数据
  2. 经过Appium调用App到制定页面, 触发对应恳求,恳求到了Mock渠道后,Mock渠道随机下发Mock数据
  3. 重复翻开指定页面, 直到遍历完Mock数据
  4. 溃散渠道进行溃散分发(人工)

Appium + mitmProxy 实现APP接口稳定性测试

长处:

短时间内能够掩盖很多的mock数据。

缺陷:

mock数据不可控。

计划二

计划一有太多的随机性,为了让下发的数据和下发数据后的结果能够对应, 咱们为了操控下发的数据, 咱们在计划一的基础上自建Mock server, 经过Appium脚本Mock server的交互,操控下发数据。

经调研,Mock server 比较大众的计划便是选用[mitmproxy](https://github.com/mitmproxy/mitmproxy), [mitmproxy](https://github.com/mitmproxy/mitmproxy)可完成 python 脚本的注入, 经过 python 脚本能够阻拦恳求的 request 和 response 数据,然后篡改数据后回来。由于 mitmproxy 是一个shell工具, 无法直接和python脚本交互, 所以python脚本和mitproxy的交互方式选用的是.ini的形式,相互读取 .ini 文件中的内容来完成数据交互。

长处:

mock数据可操控,能够校验预期结果。

缺陷:

需求预备mock数据, mock数据要经常保护。

2种计划咱们都有使用,计划一较为简略,在集成测验阶段履行,咱们不再翻开介绍。 计划二略微麻烦一些,回归测验阶段履行。以下将详细介绍下计划二怎么完成。

3.代码规划

计划流程

Appium + mitmProxy 实现APP接口稳定性测试

代码示例

测验Case

经过Mock更新数据,测验小程序晋级的逻辑 (因触及代码较多,咱们可经过完好系统查看)

测验小程序的同步晋级和异步晋级逻辑, 修改测验装备后,mitmproxy会监控对应的接口恳求, 发现存在要修改数据的恳求,则进行response修改后回来给手机端。 手机端验证是否晋级成功。

步骤:

  1. 修改测验装备(经过ini文件和mitmproxy完成联动, mitmproxy会在每次触发接口恳求时检测当时的测验装备文件)

  2. 翻开手机app

  3. 翻开某个小程序

  4. 验证是否晋级成功 (示例为把官方小程序Demo晋级成为携程小程序)

    “”” @File : test_update.py @Author : YangTongGang @Desc : 小程序晋级(同步更新、异步更新) “””

    class TestUpdate(BaseTestCases): “””检测小程序同步更新、异步更新 已适配(iOS & Android) “”” def setUp(self) -> None: # super(TestUpdate, self).setUp() self.business.del_sf_app(‘智能小程序’) if self.is_android: self.business.del_sf_mine_app(‘智能小程序’)

    def __update_action(self, is_sync=True):
        # 把CTS晋级成携程, 并修改max age = 0
        # 操控mitmproxy进行不同的测验
        if is_sync:
            test_type = TestType.TestMaxAgeZero
        else:
            test_type = TestType.TestSaveResponse
        self.changeTestType(test_type)
        protocol = CaseManager.official_demo_case().protocol
        self.business.open_swan(protocol)
        if is_sync:
            test_type = TestType.TestSyncUpdate
        else:
            test_type = TestType.TestAsyncUpdate
        self.changeTestType(test_type)
    def test_max_age_force_update(self):
        """测验小程序同步更新"""
        self.__update_action()
        self.business.open_app()
        protocol = CaseManager.official_demo_case().protocol
        if self.business.open_swan(protocol):
            self.assert_validate('汽车票')
    

Mitmproxy 发动

经过shell脚本发动mitmproxy并加载 http_handle.py文件,加载的python文件经过重写response或者request来达到监控每次恳求的request和response的才能。

每次收到测验使命时,测验使命都会有相应的装备, 如果发现需求发动代理服务,则会主动调用如下脚本,发动代理, 然后手动装备测验机代理到服务地址,或者把使命打到固定的测验机上。

#!/usr/bin/env bash
: '
@File   : start_ui_mock.sh
@Author : YangTongGang
@Desc   : 发动UI Mock服务
'
script_file=/CaseTester/HTTPRouter/http_router.py
path=$0
current_path=${path%/*}
current_path=${current_path%/*}
# 发动mock 数据服务
file=/SHELL/start_mock.sh
file_path=${current_path}${file}
chmod +x "${file_path}"
sh "${file_path}" ${script_file}
#!/usr/bin/env bash
: '
@File   : start_mock.sh
@Author : YangTongGang
@Desc   : 发动接口Mock服务
'
path=$0
file_path=$1
current_path=${path%/*}
current_path=${current_path%/*}
file_path=${current_path}'/..'${file_path}
echo "${file_path}"
local_ip=$(ifconfig -a|grep inet|grep -v 127.0.0.1|grep -v inet6|awk '{print $2}'|tr -d "addr:"|head -n 1)
echo '=========== 代理服务地址 ============'
echo 'Script:' "${file_path##*/}"
echo ''
echo "${local_ip}":8088
echo ''
echo '==================================='
# mitmdump -s "${file_path}" -p 8088  --flow-detail 0
status=$(mitmdump -s ${file_path} -p 8088  --flow-detail 0)
if [$? == 0]
then
    exit 0
fi

监控response

注册对应的接口处理模块,遇到对应的接口时履行对应的测验。

"""
@File   : http_router.py
@Author : YangTongGang
@Desc   : 恳求中转站
"""
# 注册handle
HANDLES = [
    # Core更新恳求
    UpdateCoreHandle,
    # APS拉包恳求
    PkgAPSHandle,
    # PMS拉包恳求
    PkgPMSHandle,
    # Update接口
    UpdateHandle,
    # 我的页面, 下拉二楼的静默更新个数
    GetPkgListHandle
]
def response(flow):
    """接口response"""
    url = flow.request.url
    path = urlparse.urlsplit(url)['path']
    proxy_mock_urls = []
    for handle in HANDLES:
        proxy_mock_urls.append(handle.identifier)
    test_type = get_proxy_test_type()
    if test_type == TestType.TestNone:
        return
    if path not in proxy_mock_urls:
        return
    response_dic = json.loads(flow.response.content)
    for handle in HANDLES:
        if handle.identifier in path:
            handler = handle(response_dic, test_type)
            response_dic = handler.package_dic
            flow.response.content = bytes(json.dumps(response_dic), encoding='utf8')
            return

4.机房规划简介

为了便利了解整个事务规划,顺便把咱们的机房规划也给咱们简略介绍一下。使命履行一般都是经过流水线派发到指定机房的随机空闲手机,测验完成后直接回来测验报告,测验履行人无需关心中心过程,也无需保护各种脚本。脚本都在机房保护, 防止在本地Appium部署困难, 脚本更新不及时等问题。

5.结语

由于实际使用过程中仍然需求很多人工介入,收益较小,所以履行频率不高,且该计划时间略微久远,所以在此只给咱们抛砖引玉,开辟下思路之用,后续如有需求能够作为一种备选计划。

6.本文作者及团队介绍

三翼鸟****数字化技术渠道-前端开发才能渠道」经过持续搭建和完善前端研制流程、跨端复用才能、通用才能、规范和机制,建立标准化、渠道化的前端研制系统,致力于提高团队前端开发才能、研制功率和用户体验,同时负责内容交互、场景交互、小程序、海外app等前端事务开发。