前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >糖大夫--测量流程性能监控自动化方案设计

糖大夫--测量流程性能监控自动化方案设计

作者头像
腾讯移动品质中心TMQ
发布2018-02-06 16:49:08
8490
发布2018-02-06 16:49:08
举报
文章被收录于专栏:腾讯移动品质中心TMQ的专栏

糖大夫(简称)是一款血糖仪(想了解更多的同学请看这里http://tdf.qq.com/),但不止血糖仪。血糖仪终端具备触屏、联网、高准度血糖检测单元。除了终端之外,和它配合的还有微信端、医生端。微信端重家属属性,因糖尿病不可治愈,长期的管理中,家庭关怀是重要的一个环节,在患者无法坚持的时候,家庭给予有力的支持。医生端供医生远程了解患者血糖数据,并给予专业指导。

背景

故事的起源源自一次晨会,开发总监在会上主动提出把糖大夫测量流程性能做成日常监控,而碰巧我规划的下半年计划中就囊括了糖大夫自动化,那还在等什么?Just do it!

技术可行性预研

糖大夫测量流程操作步骤依次可以分解为:

1、进入测血糖页面

2、插入试纸

3、校验试纸(是否为已使用过试纸、是否为符合该血糖仪的试纸等)

4、校验通过后自动切换到采血页提示用户滴血

5、用户滴血

6、血糖模组计算血液中血糖浓度

7、血糖模组返回本次测量值给糖大夫app,app从测量过程页自动切换到结果页,并在结果页显示血糖测量值

其中只有步骤1、2、5是用户操作,步骤3、6由底层血糖硬件模组完成,步骤4、7的页面切换以及步骤3、6的检测都是由模组完成后,模组检测后,返回数据给糖大夫app,app根据返回的数据做页面切换

现在遇到了第一个问题---整个测量流程完全依赖于底层的硬件模组,单纯的自动化脚本是无法走完这个流程(单纯的自动化脚本只能进入测血糖页面),那能不能找到一种方法来mock硬件模组和糖大夫app通信,从而通过代码来跳过整个测量流程?

抱着这个目的,首先来看看源代码中糖大夫app和模组之间具体是如何进行通信的,通过阅读项目代码,具体的通信流程如下:

首先血糖模组是一个独立的硬件,模组把一些用户操作(试纸插入/滴血/)转换成数据,然后通过串口和糖大夫app进行通信

而糖大夫通信层负责数据接收,并提供了回调接口供业务层注册,业务层向通信层注册Handler,当通信层接收到模组传递的各类数据后(如试纸插入、采血等),通过注册Handler通知业务层各类事件的发生,而测血糖页面(单Activity+多Fragment)注册了对应的Handler,并在Handler.handleMessage回调函数中,处理各类页面跳转等页面切换操作

通过在采血页加入测试模拟代码拿到采血页注册的Handler,再通过Handler模拟发送各类硬件消息,可以完全跳过整个测量流程,看来从技术上来说是完全可行的!

那么现在又遇到新的问题,测试模拟代码在采血页内部执行,很容易拿到采血页注册的Handler对象,但是测试代码不在采血页内部,并且Activity的创建是由系统完成的,如何拿到测量页Activity实例内的Handler?

好的是(好吧,我承认之前做过类似的hook,所以这里跳转的比较突然),google已经帮我们想到了这个问题,在android4.0以上(4.0以下通过替换ActivityThread内静态Instrumentation类对象来实现),在Application类中提供了一个Activitylifecyclecallbacks接口,接口内回调函数和Activity各生命周期回调一一对应,并且每个回调函数均带有Activity参数,注册后,可以拿到本App内所有的Activity实例

代码语言:javascript
复制
    public interface ActivityLifecycleCallbacks {        
    void onActivityCreated(Activity var1, Bundle var2);       
    void onActivityStarted(Activity var1);      
    void onActivityResumed(Activity var1);       
    void onActivityPaused(Activity var1);        
    void onActivityStopped(Activity var1);       
    void onActivitySaveInstanceState(Activity var1, Bundle var2);      void onActivityDestroyed(Activity var1);
    }

通过测试代码实现这个接口并在Application中注册,然后通过instance of判断是否在采血页,获取到采血页Activity实例后,拿到Handler,然后模拟试纸插入、滴血、测量结束三类消息,自动化跳过了整个测量流程

开发设计以及工具选型

技术可行性验证通过,那么如何来设计整体架构,自动化脚本通过何种操作app内测试代码?它们之间又是如何通信?

对于自动化脚本来说,它并不关心通信的细节,而且如果暴露了通信细节,反而会加大自动化脚本的开发难度,所以通过封装成SDK的形式来屏蔽通信细节,自动化脚本只需要关注业务即可

SDK设计

在sdk设计中,把一次通信流程抽象为的Request/Response形式,并根据自动化测试业务场景,设计为同步请求(必须要等这个场景完成后才能进入自动化脚本下一步)、异步请求(如添加白名单这种不依赖返回值的操作)两种方式

而SDK本身架构设计,并没有太多东西,只需要做好分层设计,方便后续扩展以及维护即可

在稳定性上,反而需要重点关注,SDK内所有线程都实现了Thread.UncaughtExceptionHandler接口,防止未处理异常外逃到自动化脚本,从而导致自动化脚本crash

糖大夫APP内测试接口设计

在糖大夫APP这一侧,结合已有工具并考虑到后续糖大夫项目会加入越来越多的自动化,通信的接口会越来越多,这一部分必须要易扩展,易维护

通过代码分层设计,测试代码从下到上设计成与业务无关的通信层、负责请求转发的控制层、与业务耦合的逻辑层这种常见架构

同时,考虑到测试代码和开发代码同处于一个工程,必须保证测试代码不影响正式代码的稳定性和安全性,其次糖大夫本身未做分包处理,如果测试代码以及测试代码引入的第三库代码过多,那么很容易超过64K方法数限制;针对上述问题,做了以下措施规避:

1、测试代码放到专门的test包下,通过测试代码和开发代码分属不同的包来实现物理隔离,再通过编译打包控制测试代码不被打进去

2、开发代码中调起测试代码部分(在Application onCreate中调起测试代码),全部使用基类接口引用,并通过反射的方式加载,以防止打正式包出现编译错误

3、除了必须暴露的接口,所有测试接口访问权限均为private,并添加对应的注释,以防止开发人员误调测试接口(这部分主要针对开发代码中调起测试接口部分)

4、所有测试代码,吃掉全部异常,防止触发app内crash上报机制,误报crash

SDK和糖大夫APP进程间通信方式选型

自动化脚本和糖大夫app内测试代码,分处不同的进程,那么他们通过何种进程间通信方式来实现数据交换? 在android中,应用层app常见的通信方式有以下几种:

从编程角度来说,使用广播是最简单,但是广播的缺点很明显---只支持单向通信

不过,既然我们已经设计成sdk这种形式,完全可以通过让sdk和app各注册一个广播的形式来模拟双向通信(基于Uiautomator2的自动化脚本,能拿到Context对象,能很方便的注册广播)

通信协议格式设计

android中,通过binder跨进程传递的数据,只能是基本类型、String、实现了Serializable接口或者Parcelable接口的复合类型,考虑到序列化/反序列化操作难以程度,读取/解析效率,以及后续的可扩展性,选用了json这一常见数据交换方式作为通信协议载体(json和String可以很容易相互转换,json增加一个字段后,除了更改增加字段接口的读取和发送外,其他地方均不需要更改),针对每一个测试接口(比如跳过血糖流程、导入血糖数据),分配一个固定的全局唯一的cmd号

具体的协议格式如下(协议格式)

代码语言:javascript
复制
请求格式:
{    "cmd": 1000, ##要访问的全局唯一接口号
    "request data": {} ##请求的数据,可为空}


响应格式:
{    "cmd": 1000, ##本次响应的接口号
    "status": 1, ##请求状态(成功为0,非0为失败)
    "error msg": "", ##错误信息
    "result data": {} ##响应的数据,可为空}

自动化框架及平台选型

在自动化框架方面,因为糖大夫本身是基于android4.4.2编译,可以完美支持UIautomator2,所以选取UIautomator2作为自动化测试脚本框架

在性能监控以及调度展示平台方面,沿用测试组内部使用的工具/平台即可

整体时序图和Demo

Demo代码如下

代码语言:javascript
复制
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {   
private static UiDevice sDevice;    

@BeforeClass
    public static void beforeTest() {
        SugarSdk.init(InstrumentationRegistry.getTargetContext().getApplicationContext());

        sDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
        sDevice.pressHome();
    }    
@Test
    public void testSugar() {
        UiObject testBtn = sDevice.findObject(new UiSelector().resourceId("com.tencent.sugardoctor:id/btn_blood_sugar_test"));        try {
            testBtn.click();
        } catch (UiObjectNotFoundException e) {
            e.printStackTrace();
        }

        SkipSugarTestFlow skipSugarTest = new SkipSugarTestFlow();
        skipSugarTest.setRequestSugarResult(11.2);
        skipSugarTest.waitForSkipSugarTestFlowFinished();
        assertEquals(11.2, skipSugarTest.getSugarAppReturnedSugarResult(), 0.001);
    }

针对糖大夫APP内接口更新,可以不更新SDK的情况下兼容新接口

代码语言:javascript
复制
 /**针对糖大夫app已提供新接口,但SDK还未更新的情况下,自动化脚本可以兼容新接口
     *
     * 同步请求方式
     * **/
    @Test
    public void testSugarSync() {
        UiObject testBtn = sDevice.findObject(new UiSelector().resourceId("com.tencent.sugardoctor:id/btn_blood_sugar_test"));        try {
            testBtn.click();
        } catch (UiObjectNotFoundException e) {
            e.printStackTrace();
        }

        RequestQueue requestQueue = SugarSdk.getRequestQueue();
        JSONObject requestData = new JSONObject();        try {
            requestData.put("sugarResult", 11.2);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        JsonObjectRequest request = new JsonObjectRequest(10001, requestData);
        SyncRequest syncRequest = requestQueue.addSyncRequest(request);
        Response resp = syncRequest.waitForResponse();        try {            double sugarResult = resp.getResultData().getDouble("result");
            assertEquals(11.2, sugarResult, 0.001);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }    /**针对糖大夫app已提供新接口,但SDK还未更新的情况下,自动化脚本可以兼容新接口
     *
     * 异步请求方式
     * **/
    @Test
    public void testSugarAsync() {
        UiObject testBtn = sDevice.findObject(new UiSelector().resourceId("com.tencent.sugardoctor:id/btn_blood_sugar_test"));        try {
            testBtn.click();
        } catch (UiObjectNotFoundException e) {
            e.printStackTrace();
        }

        RequestQueue requestQueue = SugarSdk.getRequestQueue();
        JSONObject requestData = new JSONObject();        try {
            requestData.put("sugarResult", 11.2);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        JsonObjectRequest request = new JsonObjectRequest(10001, requestData);
        requestQueue.addAsyncRequest(request, new AsyncRequest.ResponseListener() {            @Override
            public void onResponse(Response response) {                if(response.isSuccessed()) {                    try {                        double sugarResult = response.getResultData().getDouble("result");
                        assertEquals(11.2, sugarResult, 0.001);
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

    }
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2016-10-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯移动品质中心TMQ 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
    • 开发设计以及工具选型
      • SDK设计
      • 糖大夫APP内测试接口设计
      • SDK和糖大夫APP进程间通信方式选型
      • 通信协议格式设计
      • 自动化框架及平台选型
    • 整体时序图和Demo
      • Demo代码如下
相关产品与服务
应用性能监控
应用性能监控(Application Performance Management,APM)是一款应用性能管理平台,基于实时多语言应用探针全量采集技术,为您提供分布式性能分析和故障自检能力。APM 协助您在复杂的业务系统里快速定位性能问题,降低 MTTR(平均故障恢复时间),实时了解并追踪应用性能,提升用户体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档