51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 2514|回复: 0
打印 上一主题 下一主题

高阶测试:Appium集成openSTF实现多设备并发执行用例

[复制链接]
  • TA的每日心情
    无聊
    前天 09:05
  • 签到天数: 1050 天

    连续签到: 1 天

    [LV.10]测试总司令

    跳转到指定楼层
    1#
    发表于 2021-10-12 13:55:51 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    一、应用场景:
      你是否有这样的困惑:通过appium+testng已经写好一个个移动端自动化用例了,单个用例运行也没有什么问题,但是真实的企业应用场景是筛选一批合适的用例同时运行,无人值守,那下面这个案例将是一个本人已实践过的方案,希望能带给你相关的思考。

      二、本文案例环境配置:
      搭建好openSTF服务,并连接至少2台移动设备(或模拟器)。
      http://www.360doc.com/showweb/0/0/983537700.aspx
      编写appium+testng安卓端自动化用例。
      安装nodejs并通过nodejs安装appium。
      http://www.360doc.com/content/21/0528/18/8189294_979429344.shtml
      基本思路图:

     注:
      本文仅作为一个思路引导,真正的实践其实是根据公司现有的自动化成果来做调整的,读者可将里面对你有用的东西引入进你自己的自动化中。
      接下来将根据上述图示来写代码,本文的重点是与openStf相关的内容,关于appium自动化用例如何编写,appium如何批量运行,怎样介入mq等不在本文范围。

      三、appium并发执行的2种思路
      3.1、 hub--node的方式(即selenium grid + appium node)
      如下图,它的grid其实就用的是selenium的grid,他们是通用的,只是web端你需要将浏览器node关联至grid,而移动端与grid关联的就是一个个appium server,每个appium server再去连接一台移动设备。
      此种方式需要在用例运行前即把每台移动设备与appium server连接好,然后用例运行时grid即去将用例分发至每个appium node上去执行。
      我们当前没有采用该种方式,考虑到移动设备需要充分利用,使用该种方式有局限性。---从当前了解到的知识来看,后续或许能找到相关解决方案。

      3.2、动态启appium服务(随时用随时启,只要有空闲移动设备)
      这是由appium本身的工作机制决定,因为一个appium服务,只能与一台移动设备通信,而要实现并发,那就需要多启几个服务,且每个服务都要连接一台手机。
      而在本案例中即通过在用例执行时去动态启动appium服务,并连接到空闲移动设备。

      四、代码实现
      1)启动appium server
    1. public class AppiumServer {
    2. private static AppiumServiceBuilder appiumServicebuilder;
    3. private static AppiumDriverLocalService appiumService;
    4. public static URL getAppiumUrl (){
    5.     logger.info("获取Ip:"+appiumService.getUrl().toString());
    6.     return appiumService.getUrl();
    7. }
    8. public static void startServer(){
    9.     //启动appium服务
    10.     appiumServicebuilder = new AppiumServiceBuilder();
    11.     appiumServicebuilder.withIPAddress("127.0.0.1");//此处ip无法动态,手机必须连接在服务启动的电脑
    12.     int port = Integer.parseInt("47"+ RandomUtil.nextInt(89));
    13.     logger.info("Random appium server port is:"+port);
    14.     if (!checkAppiumServerIsRunning(port)){
    15.         appiumServicebuilder.usingPort(port);//需要改成动态的
    16.     }

    17.     appiumServicebuilder.withArgument(GeneralServerFlag.SESSION_OVERRIDE);
    18.     appiumServicebuilder.withArgument(GeneralServerFlag.LOG_LEVEL,"error");//暂时不知道是哪里的日志级别
    19.     //appiumService = AppiumDriverLocalService.buildDefaultService();
    20.     appiumService = AppiumDriverLocalService.buildService(appiumServicebuilder);
    21.     appiumService.start();
    22. }
    23. }
    复制代码
    2)启动appium driver
    1. public class MyAndroidKeyMobileDriver{
    2. /*
    3. * 启动driver
    4. */
    5. public static WebDriver getDriver(ITestContext context) {
    6.     WebDriver driver = null;
    7.     DesiredCapabilities desiredCapabilities = null;
    8.     try {
    9.         desiredCapabilities = BaseDriven.setAndroidCapabilities(context);
    10.         //新增的代码
    11.         driver = new AndroidDriver<>(AppiumServer.getAppiumUrl(), desiredCapabilities);
    12.         DriverCache.set(driver);
    13.         driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);

    14.         logger.info("appium android 成功启动 APP Package [ " +

    15. desiredCapabilities.getCapability(AndroidMobileCapabilityType.APP_PACKAGE) + " ]。");
    16.     }catch ( WebDriverException e ) {
    17.         if (e.getMessage().contains("Connection refused: ")) {
    18.             logger.error("Appium Desktop 服务器未启动,请检查,谢谢。");
    19.         }
    20.         if (e.getMessage().contains("not find app package")) {
    21.             assert desiredCapabilities != null;
    22.             logger.error("Android app【 " + desiredCapabilities.getCapability

    23. (AndroidMobileCapabilityType.APP_PACKAGE) + " ] 不存在。");
    24.         }
    25.         logger.error("App启动异常:", e);
    26.     }
    27.     return driver;
    28. }
    29. }
    复制代码
    3)通过stf api操作设备

      注:stf提供了关于操作所挂载的设备相关接口,只要把stf服务启动起来即可访问。
    1. /*
    2. *   获取所有设备
    3. */
    4. public static List<DeviceEntity> getAllDevices(){
    5.     Map stfHeaderMap = new HashMap();
    6.     Map paramMap = new HashMap();
    7.     stfHeaderMap.put("Authorization","Bearer "+ACCESS_TOKEN);
    8.     String response = httpClientAdaptor.doGet(STF_SERVICE_URL + "devices",stfHeaderMap,paramMap);//查询所有的设备
    9.     Map maps = JsonUtil.toObject(response, Map.class); // json转换为Map
    10.     String devices = String.valueOf(maps.get("devices")); // 获取设备列表,是一个json数组
    11.     System.out.println("所有的设备信息:"+devices);

    12.     //将json数组转化为
    13.     List<DeviceEntity> deviceList = new ArrayList<>();
    14.     JSONArray array = JsonUtil.toJSONArray(devices);
    15.     for (int i = 0;i<array.size();i++){
    16.         DeviceEntity deviceEntity = new DeviceEntity();
    17.         JSONObject obj = (JSONObject) array.get(i);
    18.         deviceEntity.setSerial(obj.getString("serial"));
    19.         deviceEntity.setPlatform(obj.getString("platform"));
    20.         deviceEntity.setVersion(obj.getString("version"));
    21.         deviceEntity.setRemoteConnectUrl(obj.getString("remoteConnectUrl"));
    22.         deviceEntity.setUsing(Boolean.parseBoolean(obj.getString("using")));
    23.         deviceEntity.setReady(Boolean.parseBoolean(obj.getString("ready")));
    24.         deviceEntity.setPresent(Boolean.parseBoolean(obj.getString("present")));

    25.         String providerJson = obj.getString("provider");
    26.         Map providerMaps = JsonUtil.toObject(providerJson, Map.class);
    27.         String providerName = (String) providerMaps.get("name");
    28.         deviceEntity.setProvider(providerName);
    29.         deviceList.add(deviceEntity);
    30.     }
    31.     System.out.println("设备个数:"+deviceList.size());
    32.     return deviceList;
    33. }
    34. /*
    35. *   查找空闲设备并锁定
    36. */
    37. public static String connectFreeDevice(String slaveMachine){
    38.     String currentConnectedDevice = null;
    39.     List<DeviceEntity> deviceList = getAllDevices();
    40.     for (Iterator itr = deviceList.iterator(); itr.hasNext();) {
    41.         DeviceEntity device = (DeviceEntity) itr.next();
    42.         //获取对应编译机上挂载的手机设备
    43.         if (device.getProvider().contains(slaveMachine)){
    44.             if (!device.isPresent() || !device.isReady() || device.isUsing()){
    45.                 //logger.error("device is in use!");
    46.                 continue;
    47.             }
    48.             //找到空闲即申请使用
    49.             currentConnectedDevice = device.getSerial();
    50.             addDeviceToUser(currentConnectedDevice);
    51.             logger.info("Find free device:"+currentConnectedDevice);
    52.             break;
    53.         }
    54.     }
    55.     return currentConnectedDevice;
    56. }
    57. /*
    58. *   锁定设备,标识被某个stf的账户占用
    59. */
    60. private static boolean addDeviceToUser(String serial){
    61.     String reqJson = "{\"serial\": \""+serial+"\"}";
    62.     Map stfHeaderMap = new HashMap();
    63.     Map paramMap = new HashMap();
    64.     stfHeaderMap.put("Authorization","Bearer "+ACCESS_TOKEN);
    65.     stfHeaderMap.put("Content-Type","application/json; charset=utf-8");
    66.     String response = httpClientAdaptor.doPost(STF_SERVICE_URL + "user/devices",stfHeaderMap,reqJson);
    67.     logger.info("mq 端锁定设备响应消息:"+response);
    68.     Map maps = JsonUtil.toObject(response, Map.class); // json转换为Map
    69.     if (!Boolean.parseBoolean(String.valueOf(maps.get("success")))) {
    70.         logger.error("Device not found");
    71.         return false;
    72.     }
    73.     logger.info("The device ["+serial+"]is locked successfully");
    74.     return true;
    75. }
    76. /*
    77. * 释放设备(用例用完以后,将该设备重新置为空闲)
    78. */
    79. public static void releaseDevice(String serial) {
    80.     Map paramMap = new HashMap();
    81.     Map stfHeaderMap = new HashMap();
    82.     stfHeaderMap.put("Authorization","Bearer "+stfService.getAuthToken());
    83.     String response = httpClientAdaptor.doDelete(stfService.getStfUrl() + "user/devices/"+serial,stfHeaderMap,"");
    84.     logger.info("释放设备接口返回信息:"+response);
    85. }
    复制代码
    4)用例层调用上述方法

    package cn.study.testapi.base.support;

    import cn.study.testapi.base.enums.DrivenEnum;
    import cn.study.testapi.base.load.InitLoadNew;
    import cn.study.testapi.core.appium.server.AppiumServer;
    import cn.study.testapi.core.driver.driven.factory.KeyDriverFactory;
    import cn.study.testapi.core.driver.keydriver.AndroidKeyDriver;
    import cn.study.testapi.core.openstf.DeviceApi;
    import cn.study.testapi.utils.*;
    import cn.study.testapi.core.keyaction.AndroidKeyAction;
    import cn.study.testapi.core.entity.ComponentStep;
    import org.apache.log4j.Logger;
    import org.testng.ITestContext;
    import org.testng.annotations.*;
    import java.util.List;

    public class AndroidKeywordTest extends BaseKeyTest {
        protected static Logger logger = Logger.getLogger(AndroidKeywordTest.class);
        protected AndroidKeyDriver driver = null;
        //在testng suit之前启动appium server
        @Parameters({"deviceSerial"})
        @BeforeSuite(alwaysRun = true)
        public void setUp(@Optional("deviceSerial") String deviceSerial, ITestContext context) {
            InitLoadNew initLoadNew = new InitLoadNew(odinEnv, taskId);
            // 初始化接口返回result
            setCaseStepList(initLoadNew.getCaseStepList());
            AppiumServer.startServer();//启动appium server
            driver = MyAndroidKeyMobileDriver.getDriver(context); // 启动android app,根据自己的项目结构去设计此处获取driver的方式
        }

        @Test(alwaysRun = true, dataProvider = "keyword")
        public void testCaseStep(List<ComponentStep> data) {
            //执行你的用例
        }
        //aftersuit执行的动作,此处传一个【设备序列】参数,指明要释放的设备
        @Parameters({"deviceSerial"})
        @AfterSuite(alwaysRun = true)
        public void tearDown(@Optional("deviceSerial") String deviceSerial) {
            logger.info("========= tearDown ========");
            DeviceApi.releaseDevice(deviceSerial);//释放移动设备
            AppiumServer.stopAppiumServer();//用例执行完后停掉服务
            JdbcUtil.close();
            TempFileUtil.deleteOnExit();
            driver.quit();
        }

    }

    代码写完以后,你就可以尝试并行执行多条android端自动化用例看看效果啦。


    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

    x
    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏
    回复

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-11-23 08:24 , Processed in 0.066471 second(s), 23 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表