06 Sonic - 实现多设备运行

我们做的是音视频的app,和一般app不同需要一个对手。例如,一台设备推流后,需要另一台设备拉流,这样一般自动化平台和sonic都不能满足需求。所以才想基于sonic的基础上,稍加修改来实现这个需求。

一、现状分析

sonic现有功能。

1、在测试用例管理中,创建两个用例。case1 打开app并点击一个按钮。case2点击返回按钮并点击一个按钮。

 

 2、创建两个套件,test 套件调用case,test2 套件调用case2。

 

3、先运行 test 再运行 test2。这样就初步实现一个case用不同设备运行的效果。但有两个问题

  • 每次运行套件都需要 Appium server ,每次启动都需要10S左右,有点慢
  • 不知道 test 套件1啥时候运行完成,也就是不知道啥时候运行 test2 套件
  • 同一个case的运行结果显示到了两个结果里
  • 同一个case拆的有点乱,脚本看着有些费劲。脚本要是多了,看着更乱。

二、处理路线图

1、只改 web + server 端。修改 runSuite 接口调用完case1再屌用case2,实现一次调用运行两个case。需要创建一个线程判断case1运行完成后再调用case2

2、配合修改 Agent ,创建  Appium server 后保持,直到收到断开命令后再释放 Appium server。修改上报运行结果,将多个case的结果报到一起

以上两步实现后,基本就可以满足需求了。

3、重写sonic用例编辑,实现添加步骤的同时指定设备类型。

4、重写测试套件运行,运行时重新整理case实现多设备,多设备类型运行case。

三、只改 web + server 端。修改 runSuite 接口调用完case1再调用case2

TestSuitesServiceImpl 的 218 行 NettyServer.getMap().get(id).writeAndFlush(result.toJSONString()); 将构造的数据发给的agent,所以可以在这里处理

3.1、同步 发送 test 套件,休眠20S后再发送 test2 套件

1、web上运行 test 套件

 

 

 2、调试模式在 TestSuitesServiceImpl.java 的 172 行打断点,这里就是在数据库中生成一个测试结果记录。

看到改动的表是 results

3、在 180 行打断点,记录的是发个 agent 的运行脚本,如下:

test套件发的内容如下:

{"msg":"suite","cases":[{"gp":{},"rid":14,"steps":[{"step":{"caseId":1,"conditionType":0,"content":"","elements":[],"error":3,"id":1,"parentId":0,"platform":1,"projectId":1,"sort":1,"stepType":"openApp","text":"com.example.tablayouttest"}},{"step":{"caseId":1,"conditionType":0,"content":"","elements":[{"eleName":"点击 quickstart","eleType":"xpath","eleValue":"//android.widget.Button[@text='QUICKSTART']","id":1,"projectId":1}],"error":3,"id":2,"parentId":0,"platform":1,"projectId":1,"sort":2,"stepType":"click","text":""}}],"device":[{"agentId":1,"chiName":"","cpu":"arm64-v8a","gear":0,"id":1,"imgUrl":"","manufacturer":"Meizu","model":"M1822","name":"meizu_M1822_CN","nickName":"","password":"","platform":1,"position":0,"size":"1080x2160","status":"ONLINE","udId":"822QEDU5227H3","user":"lrs","version":"8.1.0"}],"cid":1}],"pf":1}

test2套件发的内容如下:

{"msg":"suite","cases":[{"gp":{},"rid":20,"steps":[{"step":{"caseId":2,"conditionType":0,"content":"BACK","elements":[],"error":3,"id":3,"parentId":0,"platform":1,"projectId":1,"sort":3,"stepType":"keyCode","text":""}},{"step":{"caseId":2,"conditionType":0,"content":"","elements":[{"eleName":"点击 quickstart","eleType":"xpath","eleValue":"//android.widget.Button[@text='QUICKSTART']","id":1,"projectId":1}],"error":3,"id":4,"parentId":0,"platform":1,"projectId":1,"sort":4,"stepType":"click","text":""}}],"device":[{"agentId":1,"chiName":"","cpu":"arm64-v8a","gear":0,"id":1,"imgUrl":"","manufacturer":"Meizu","model":"M1822","name":"meizu_M1822_CN","nickName":"","password":"","platform":1,"position":0,"size":"1080x2160","status":"ONLINE","udId":"822QEDU5227H3","user":"lrs","version":"8.1.0"}],"cid":2}],"pf":1}

4、动手开始修改代码,在 TestSuitesServiceImpl.java 的 181 行代码修改如下

                    // 发送 test 套件
                    String testJsonStr = "{\"msg\":\"suite\",\"cases\":[{\"gp\":{},\"rid\":14,\"steps\":[{\"step\":{\"caseId\":1,\"conditionType\":0,\"content\":\"\",\"elements\":[],\"error\":3,\"id\":1,\"parentId\":0,\"platform\":1,\"projectId\":1,\"sort\":1,\"stepType\":\"openApp\",\"text\":\"com.example.tablayouttest\"}},{\"step\":{\"caseId\":1,\"conditionType\":0,\"content\":\"\",\"elements\":[{\"eleName\":\"点击 quickstart\",\"eleType\":\"xpath\",\"eleValue\":\"//android.widget.Button[@text='QUICKSTART']\",\"id\":1,\"projectId\":1}],\"error\":3,\"id\":2,\"parentId\":0,\"platform\":1,\"projectId\":1,\"sort\":2,\"stepType\":\"click\",\"text\":\"\"}}],\"device\":[{\"agentId\":1,\"chiName\":\"\",\"cpu\":\"arm64-v8a\",\"gear\":0,\"id\":1,\"imgUrl\":\"\",\"manufacturer\":\"Meizu\",\"model\":\"M1822\",\"name\":\"meizu_M1822_CN\",\"nickName\":\"\",\"password\":\"\",\"platform\":1,\"position\":0,\"size\":\"1080x2160\",\"status\":\"ONLINE\",\"udId\":\"822QEDU5227H3\",\"user\":\"lrs\",\"version\":\"8.1.0\"}],\"cid\":1}],\"pf\":1}";
                    NettyServer.getMap().get(id).writeAndFlush(testJsonStr);

                    try {
                        sleep(20000);
                        // 发送 test2 套件
                        String test2JsonStr = "{\"msg\":\"suite\",\"cases\":[{\"gp\":{},\"rid\":20,\"steps\":[{\"step\":{\"caseId\":2,\"conditionType\":0,\"content\":\"BACK\",\"elements\":[],\"error\":3,\"id\":3,\"parentId\":0,\"platform\":1,\"projectId\":1,\"sort\":3,\"stepType\":\"keyCode\",\"text\":\"\"}},{\"step\":{\"caseId\":2,\"conditionType\":0,\"content\":\"\",\"elements\":[{\"eleName\":\"点击 quickstart\",\"eleType\":\"xpath\",\"eleValue\":\"//android.widget.Button[@text='QUICKSTART']\",\"id\":1,\"projectId\":1}],\"error\":3,\"id\":4,\"parentId\":0,\"platform\":1,\"projectId\":1,\"sort\":4,\"stepType\":\"click\",\"text\":\"\"}}],\"device\":[{\"agentId\":1,\"chiName\":\"\",\"cpu\":\"arm64-v8a\",\"gear\":0,\"id\":1,\"imgUrl\":\"\",\"manufacturer\":\"Meizu\",\"model\":\"M1822\",\"name\":\"meizu_M1822_CN\",\"nickName\":\"\",\"password\":\"\",\"platform\":1,\"position\":0,\"size\":\"1080x2160\",\"status\":\"ONLINE\",\"udId\":\"822QEDU5227H3\",\"user\":\"lrs\",\"version\":\"8.1.0\"}],\"cid\":2}],\"pf\":1}";
                        NettyServer.getMap().get(id).writeAndFlush(test2JsonStr);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    //NettyServer.getMap().get(id).writeAndFlush(result.toJSONString());

 5、数据库执行如下sql 

select * from results where id=14; // 测试结果

UPDATE results set end_time=null, receive_msg_count=0, `status`=0 where id=14; // 恢复测试结果数据
DELETE FROM result_detail WHERE result_id=14; // 删除上一次运行结果


select * from results where id=20 // 测试结果

UPDATE results set end_time=null, receive_msg_count=0, `status`=0 where id=20; // 恢复测试结果数据
DELETE FROM result_detail WHERE result_id=20; // 删除上一次运行结果

6、web页面运行套件 test  

 

 

点击后在agent中就能看到运行test套件后又继续运行test2套件

 3.1、异步 发送 test 套件,判断 test 套件完成后,再发送 test2 套件

1、在 TestSuitesServiceImpl.java 的 181 行代码再次修改
                    cachedThreadPool.execute(() -> {
                        while (true) {
                            try {
                                sleep(1000);
                                Results results1 = resultsService.getById(14);
                                if (results1.getStatus() > 0) {
                                    System.out.println("==== 运行套件 test2");
                                    // 发送 test2 套件
                                    String test2JsonStr = "{\"msg\":\"suite\",\"cases\":[{\"gp\":{},\"rid\":20,\"steps\":[{\"step\":{\"caseId\":2,\"conditionType\":0,\"content\":\"BACK\",\"elements\":[],\"error\":3,\"id\":3,\"parentId\":0,\"platform\":1,\"projectId\":1,\"sort\":3,\"stepType\":\"keyCode\",\"text\":\"\"}},{\"step\":{\"caseId\":2,\"conditionType\":0,\"content\":\"\",\"elements\":[{\"eleName\":\"点击 quickstart\",\"eleType\":\"xpath\",\"eleValue\":\"//android.widget.Button[@text='QUICKSTART']\",\"id\":1,\"projectId\":1}],\"error\":3,\"id\":4,\"parentId\":0,\"platform\":1,\"projectId\":1,\"sort\":4,\"stepType\":\"click\",\"text\":\"\"}}],\"device\":[{\"agentId\":1,\"chiName\":\"\",\"cpu\":\"arm64-v8a\",\"gear\":0,\"id\":1,\"imgUrl\":\"\",\"manufacturer\":\"Meizu\",\"model\":\"M1822\",\"name\":\"meizu_M1822_CN\",\"nickName\":\"\",\"password\":\"\",\"platform\":1,\"position\":0,\"size\":\"1080x2160\",\"status\":\"ONLINE\",\"udId\":\"822QEDU5227H3\",\"user\":\"lrs\",\"version\":\"8.1.0\"}],\"cid\":2}],\"pf\":1}";
                                    NettyServer.getMap().get(id).writeAndFlush(test2JsonStr);
                                    break;
                                } else {
                                    System.out.println("sleep 1S =================");
                                }
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    });

 

 2、在 TestSuitesServiceImpl.java 的 开头增加

    private ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

 

3、继续在数据库执行sql,清理上一次运行记录

4、web页面运行 test 套件,从日志中就能看到每隔 1S 查一下数据库直到 test 套件运行完成后,就运行 test2 套件。

 四、配合修改 agent 

4.1、保持 Appium server  

1、复制 org.cloud.sonic.agent.common.maps 目录下的 HandlerMap.java 为 HandlerRunSuiteMap.java 备用。这个 map 用来保存 androidStepHandler

2、将 AndroidTestTaskBootThread.java 文件的201行 androidStepHandler.startAndroidDriver(udId); 改为

                if (HandlerRunSuiteMap.getAndroidMap().get(udId) == null) { // 按照设备 uuid 查找之前是否初始化过
                    androidStepHandler.startAndroidDriver(udId); // 没有初始化
                    HandlerRunSuiteMap.getAndroidMap().put(udId, androidStepHandler); // 初始化后 put 到 map 中
                } else {
                    androidStepHandler = HandlerRunSuiteMap.getAndroidMap().get(udId); // 之前初始化过,从 map 中取出使用
                }  

3、246 行将 androidStepHandler.closeAndroidDriver(); 注释。这句的意思是在一个 suite 结束后关闭 appium

 4、再次运行 test 套件,在日志中看到 先后执行test 套件和 test2 套件,但只启动了一次 appium

 

 5、到此同一个设备无论 runSuite 多少次,也只会启动一个 appium 实例,这样就解决多次调用需要等很久的问题。不过这里有一个bug,我将 closeAndroidDriver 注释了,这样一直没有释放 appium,后续还要加一个 step ,启动 appium、停止 appium

 4.2、修改上报运行结果,将多个case的结果报到一起

1、在 LogUtil.java 的 55 行添加如下代码

        if (resultId == 20) { // 如果是 test2 套件
            resultId = 14; // 临时将 test2 套件的id 20 改为 test套件的id 14
        }

 

 2、再次运行 test 套件,在日志中看到无论 test 套件还是 test2套件上报的 rid 都是14

3、在数据库中看到, 只有 result_id 为 14 的上报

4.3、修改下发 suite 数据,将多个case的结果报到一起

上边的方法虽然可以都报道 result_id=14 的结果里,但那么改太蠢。其实可以通过修改下发 suite 的方式让他报到一起。
1、还原 agent 下  LogUtil.java 代码

 

2、还记得上边通过修改server 中的 TestSuitesServiceImpl.java 文件,实现 test 套件运行完后运行 test2 套件的吗?就是改哪里。将 197 行的 rid:20 改为 14

3、重起 controller 后,再次运行 test 套件,同样在日志里看到两个套件上报的都是 rid=14,数据库中看到了也是 7 条 rid=14 的记录

所以后边只要修改下发数据,即可实现多个设备运行同一个 case ~~~ 

4.4、增加 step ,关闭 appium

 1、在 agent 的 AndroidStepHandler.java 的 1485 增加如下代码

            case "closeDriver" : // 增加 closeDriver 的 step
                closeAndroidDriver(); // 调用关闭 appium 的方法
                break;

 2、在 AndroidStepHandler.java 的 192 行的 closeAndroidDriver 方法中增加 清除 HandlerRunSuiteMap 代码

                HandlerRunSuiteMap.getAndroidMap().remove(log.udId); // appium 关闭成功后清除对应 udid HandlerRunSuiteMap

3、在server 中的 TestSuitesServiceImpl.java 文件的 197 行中增加关闭 appium 的测试步骤,如下

{"step": {"caseId": 10,"conditionType": 0, "content": "", "elements": [], "error": 3,"id": 5,"parentId": 0,"platform": 1,"projectId": 1,"sort": 5,"stepType": "closeDriver","text": ""}} 

完整 test2JsonStr 如下

String test2JsonStr = "{\"msg\":\"suite\",\"cases\":[{\"gp\":{},\"rid\":14,\"steps\":[{\"step\":{\"caseId\":2,\"conditionType\":0,\"content\":\"BACK\",\"elements\":[],\"error\":3,\"id\":3,\"parentId\":0,\"platform\":1,\"projectId\":1,\"sort\":3,\"stepType\":\"keyCode\",\"text\":\"\"}},{\"step\":{\"caseId\":2,\"conditionType\":0,\"content\":\"\",\"elements\":[{\"eleName\":\"点击 quickstart\",\"eleType\":\"xpath\",\"eleValue\":\"//android.widget.Button[@text='QUICKSTART']\",\"id\":1,\"projectId\":1}],\"error\":3,\"id\":4,\"parentId\":0,\"platform\":1,\"projectId\":1,\"sort\":4,\"stepType\":\"click\",\"text\":\"\"}},{\"step\": {\"caseId\": 10,\"conditionType\": 0, \"content\": \"\", \"elements\": [], \"error\": 3,\"id\": 5,\"parentId\": 0,\"platform\": 1,\"projectId\": 1,\"sort\": 5,\"stepType\": \"closeDriver\",\"text\": \"\"}}],\"device\":[{\"agentId\":1,\"chiName\":\"\",\"cpu\":\"arm64-v8a\",\"gear\":0,\"id\":1,\"imgUrl\":\"\",\"manufacturer\":\"Meizu\",\"model\":\"M1822\",\"name\":\"meizu_M1822_CN\",\"nickName\":\"\",\"password\":\"\",\"platform\":1,\"position\":0,\"size\":\"1080x2160\",\"status\":\"ONLINE\",\"udId\":\"822QEDU5227H3\",\"user\":\"lrs\",\"version\":\"8.1.0\"}],\"cid\":2}],\"pf\":1}";

 

 4、重起后重新运行 test 套件,这时候在日志里就看到运行 test 套件的时候启动了 appium,在 test2 套件运行完成后运行了关闭了 appium

 

到此,基本实现了多个设备完哼一个测试用例的需求了。但只是演示代码,还不能用于生产,比如:

1、创建appium的方法不太好,可以参考远控的测试用例执行的方案做

2、关闭 appium 的方法就不太好

3、还有很多异常处理的地方没有关闭 appium 等等

4、在上边的 “处理路线图” 中讲的,web页面还有很多体力活工作。

 

  

 

 

posted @ 2022-06-25 23:00  rslai  阅读(340)  评论(0编辑  收藏  举报