导语

前面几篇文章介绍了在Macaca实践中的一些实用技巧与解决方案,今天简单分析一下Macaca的基础原理。这篇文章将以前面所分享的UI自动化Macaca-Java版实践心得中的demo为基础,进行一下实例讲解。

Macaca的基本组成

通过对源码各个模块的分析,可以帮助我们对Macaca的整体构成有一个基础的认识。Macaca已经开源,相关的源码在对应的github上都可以下载:

https://github.com/macacajs

大家会在alibaba集团的开源github上找到macaca的另一个仓库https://github.com/alibaba/macaca/,关于这两个仓库的关系这里简单讲一下,由于主仓库https://github.com/macacajs模块众多,所以在alibaba集团下的github上采用了alibaba/macaca,以方便管理,大家如果需要源码的话需要去macaca的主仓库查看,也就是https://github.com/macacajs

为了方便大家查看,宝宝费了好大劲画了下面这张图(觉得不错的给个赞吧):

备注:上图所有模块均可以在官方github上找到对应的源码 https://github.com/macacajs

模块拆分讲解:

Macaca

1. macaca-cli

Macaca提供的命令行工具

$macaca server启动server

$macaca server --verbose启动server并打印详细日志

$macaca doctor检验当前macaca环境配置

2. app-inspector

macaca提供的元素查找工具,可以将app视图的结构以布局结构树的格式在浏览器上展示出来,用过点击某个元素,就可以方便的查询到该控件的基本信息,以方便查找。具体使用可参考官网: https://macacajs.com/inspector

3. UI Recorder

macaca提供的脚本录制工具,可以通过录制获得脚本,对于入门同学很有帮助。https://macacajs.com/recorder

WebDriver-Server

Macaca是按照经典的Server-Client设计模式进行设计的,也就是我们常说的C/S架构。WebDriver-server部分便充当了server这部分的角色,他的职责就是等待client发送请求并做出响应。

WebDriver-Client

client端简单来讲就是我们的测试代码,我们测试代码中的一些行为,比如控件查找、点击等,这些行为以http请求的方式发送给server,server接收请求,并执行相应操作,并在response中返回执行状态、返回值等信息。

也正是基于这种经典的C/S架构,所以client端具有跨语言的特点,macaca-wd,wd.java,wd.py分别是Macaca团队针对Js Java 以及Python的封装,只要能保证client端按照指定的要求发送Http请求,任意语言都可以。

DriverList

自动化要在不同的平台上跑,需要有对应平台的驱动,这部分驱动接收到来自server的操作命令,驱动各自平台的底层完成对应的操作。

1. Android

Macaca针对安卓平台的驱动集合

  • macaca-android 安卓驱动
  • macaca-adb 封装了安卓的adb命令,来实现一些adb的操作,比如安装、卸载、启动app、获取设备列表这些操作
  • android-unicode 经过封装后的输入法,解决中文输入的问题
  • uiautomator-client 将来自server的操作指令转换为UIAutomator可以识别的指令,驱动uiautomator完成对应的操作
  • android-performance 用于自动化测试安卓性能相关的支持

2. iOS

Macaca针对iOS平台的驱动集合

  • macaca-ios iOS驱动
  • xctest-client 同安卓的uiautomator-client异曲同工,对XCUITest的封装,将来自server的操作指令转换为XCUITest可以识别的指令,驱动XCUITest完成对应的操作
  • ios-simulator 用于对ios模拟器的支持,可以通过模拟器运行用例
  • remote-debug 用于远程调试

3. Hybrid

Macaca针对Hybrid的驱动集合。

  • macaca-chrome web测试驱动
  • macaca-chromedriver 驱动chrome浏览器
  • ios-webkit-debug-proxy 适用于iOS平台对webview的调试

4. Electron

Macaca针对pc端网页应用的支持

  • macaca-electron

Macaca执行流程图

了解了Macaca的组成模块以及他们各自的作用,下面我们看一下各个模块是如何组装起来实现自动化测试流程的,宝宝同样费了很大劲画了一张图如下:

结合实例讲解Macaca基本原理:

以文章开始提到的demo为例(client以Java版为例) demo地址

源码克隆到本地并配置好Macaca相关环境后,我们来执行一次用例:

1. 启动macaca server

➜  bootstrap git:(master) ✗ macaca server --verbose
>> request.js:24:12 [master] pid:5499 get remote update info failed.
>> index.js:17:12 [master] pid:5503 webdriver server start with config:
{ port: 3456,
verbose: true,
always: true,
ip: '30.30.180.23',
host: 'MacBook-Pro.local',
loaded_time: '2016-12-07 17:00:22' }
>> middlewares.js:17:10 [master] pid:5503 base middlewares attached
>> router.js:129:10 [master] pid:5503 router set
>> webdriver sdk launched

从这一步打印的信息我们可以看到,这一步实际上执行的是流程图中第一步的操作,启动server,建立连接,然后server返回所连接的ip以及端口号,因为我们是本地跑,所以ip实际上是本机的ip地址

2. 执行用例

以SampleTest为例,右键执行junitTest,稍作等待,就会看到系统自动启动了ios的模拟器并跑起来了用例。执行过程中的某个截图如下:

首先我们来看一下对应用例启动的client端核心代码:

@Before
public void setUp() throws Exception { // 清除日志记录
ResultGenerator.clearOldData();
//清理截图重新记录
File file = new File(Config.ScreenshotPath);
deleteOldScreen(file); // 初始化应用基础信息
JSONObject props = new JSONObject();
if (Config.PLATFORM.equals("ios")) { // 创建ios实例
props.put("app", Config.IOS_APP);
props.put("platformName", Config.IOS_PLATFORM_NAME);
props.put("deviceName", Config.IOS_DEVICE_NAME);
driver.setCurPlatform(PlatformType.IOS);
} else { //创建安卓实例
props.put("app", Config.ADR_APP);
props.put("platformName", Config.ADR_PLATFORM_NAME);
driver.setCurPlatform(PlatformType.ANDROID);
} // 覆盖安装
props.put("reuse", Config.REUSE); JSONObject desiredCapabilities = new JSONObject();
desiredCapabilities.put("desiredCapabilities", props);
driver.initDriver(desiredCapabilities); }

在这段代码中,我们做的工作是根据不同的平台设置用例的一些基础启动信息,包含平台类型、安装包地址、设备id、是否覆盖安装等参数,设置完成后,通过driver.initDriver(desiredCapabilities)这个操作启动driver,这个过程便会按照流程图中的第二个步骤发送http请求,server会接收到这个请求并创建一个session,在这次的用例执行中,所有的操作都会基于这个session进行,来看一下针对这个操作控制台所打印的信息(为方便突出主要过程省略了部分无关日志):

>> responseHandler.js:11:12 [master] pid:5503 Recieve HTTP Request from Client: method: POST url: /wd/hub/session, jsonBody: {"desiredCapabilities":{"app":"/Users/Macaca/github/bootstrap/app/ios-app-bootstrap.zip","reuse":"3","platformName":"iOS","deviceName":"iPhone 6"}}
>> session.js:47:10 [master] pid:5503 Creating session, sessionId: abe8f19c-76ea-4bb0-b5b9-d69e3ce9b798.
>> helper.js:196:12 [master] pid:5503 Unzipping local app form /Users/Macaca/github/bootstrap/app/ios-app-bootstrap.zip
>> macaca-ios.js:194:10 [master] pid:5503 Get available devices(...省略设备列表)
...省略部分信息
>> proxy.js:54:14 [master] pid:5503 Proxy: /session:POST to http://30.30.180.23:8900/session:POST with body: {"desiredCapabilities":{"bundleId":"xudafeng.ios-app-bootstrap","app":"/var/folders/lf/lmrfrj9s4xn76wq_4k3x92380000gn/T/ios-app-bootstrap.app/","platformName":"iOS"}}
>> proxy.js:67:16 [master] pid:5503 Got response with status 200: {"value":{"sessionId":"6A1D2ED3-37BD-449C-A128-2E72DEF4CBF9","capabilities":{"device":"iphone","browserName":"ios-app-bootstrap","sdkVersion":"10.1","CFBundleIdentifier":"xudafeng.ios-app-bootstrap...
>> responseHandler.js:47:14 [master] pid:5503 Send HTTP Respone to Client: {"sessionId":"abe8f19c-76ea-4bb0-b5b9-d69e3ce9b798","status":0,"value":"{\"app\":\"/var/folders/lf/lmrfrj9s4xn76wq_4k3x92380000gn/T/ios-app-bootstrap.app/\",\"reuse\":\"3\",\"platformName\":\"iOS\",\"deviceName\":\"iPhone 6\"}"}

经过如上步骤后,连接便已经成功建立了,下一步再分析一下一个具体操作,以登录为例,对应的client端的代码如下:

(因为框架层封装了一些操作,所以代码看上去比较少,具体的控件查找部分看不到,有需要详细了解的可以研究源码)

// SampleTest.java

    @Test
public void test () throws Exception { // 处理登录
LoginPage loginPage = new LoginPage("登录页");
loginPage.setDriver(driver);
if (loginPage.hasPageShown(LoginPageUI.LOGIN_BTN)) {
saveScreen(loginPage.pageDesc);
ResultGenerator.loadPageSucc(loginPage);
loginPage.login("test", "123");
} else {
ResultGenerator.loadPageFail(loginPage); }
}

对应登录按钮的查询操作,我们会在控制台上看到如下的日志:

>> responseHandler.js:11:12 [master] pid:5503 Recieve HTTP Request from Client: method: POST url: /wd/hub/session/abe8f19c-76ea-4bb0-b5b9-d69e3ce9b798/element, jsonBody: {"using":"name","value":"Login"}
>> proxy.js:54:14 [master] pid:5503 Proxy: /wd/hub/session/abe8f19c-76ea-4bb0-b5b9-d69e3ce9b798/element:POST to http://30.30.180.23:8900/session/6A1D2ED3-37BD-449C-A128-2E72DEF4CBF9/element:POST with body: {"using":"name","value":"Login"}
>> proxy.js:67:16 [master] pid:5503 Got response with status 200: {"value":{"using":"name","value":"Login","description":"unable to find an element"},"sessionId":"abe8f19c-76ea-4bb0-b5b9-d69e3ce9b798","status":7}
>> session.js:107:14 [master] pid:5503 Send HTTP Respone to Client: {"value":"{\"using\":\"name\",\"value\":\"Login\",\"description\":\"unable to find an element\"}","sessionId":"abe8f19c-76ea-4bb0-b5b9-d69e3ce9b798","status":7}

在上面的日志中我们可以看到,当我们查找登录按钮的时候,client发送了一个http请求给server,请求的操作是element(这个表示控件查找),参数是{"using":"name","value":"Login"},这是告诉server我们要找的这个控件的name属性是Login,server收到这个请求,通过router路由转发给iOS的驱动(在启动driver的时候已经设置的平台类型,因此这里能知道找ios),iOS驱动收到请求驱动XCUITest框架对模拟器上的目标app执行对应的控件查找操作,得到response后原路返回给client,这样就完成了一次请求的完整的生命周期。

一点题外话

关于Macaca使用中的问题,很多同学会直接去社区里提问,但是很多时候大家问的问题都是类似的,这种情况建议大家先去查看一下官方仓库的issues,看看有没有人遇到自己同样问题的,如果没有,可以新建issue。查看issue之前先看下自己问题所属的模块,比如如果问题在wd.java的使用中,可以去wd.java仓库下查看issue:https://github.com/macacajs/wd.java/issues

小结

如上简单总结了Macaca的基础原理,提供一个小窍门是大家可以对照控制台输出与文章中的流程图一一对应,这样就能大体了解整个流程的数据流向,从而就能参透Macaca的基础原理了。个人水平有限,如有不当,欢迎指正。

后面会陆续放出自己在实践中的其他心得与经验,敬请期待,也欢迎大家交流讨论。

转:「发表于 TesterHome 」作者junhe ,https://testerhome.com/topics/6608

Macaca 基础原理浅析的更多相关文章

  1. Android-Binder原理浅析

    Android-Binder原理浅析 学习自 <Android开发艺术探索> 写在前头 在上一章,我们简单的了解了一下Binder并且通过 AIDL完成了一个IPC的DEMO.你可能会好奇 ...

  2. 沉淀,再出发:docker的原理浅析

    沉淀,再出发:docker的原理浅析 一.前言 在我们使用docker的时候,很多情况下我们对于一些概念的理解是停留在名称和用法的地步,如果更进一步理解了docker的本质,我们的技术一定会有质的进步 ...

  3. 消息队列——ActiveMQ使用及原理浅析

    文章目录 引言 正文 一.ActiveMQ是如何产生的? 产生背景 JMS规范 基本概念 JMS体系结构 二.如何使用? 基本功能 消息传递 P2P pub/sub 持久订阅 消息传递的可靠性 事务型 ...

  4. MyBatis 原理浅析——基本原理

    前言 MyBatis 是一个被广泛应用的持久化框架.一个简单的使用示例如下所示,先创建会话工厂,然后从会话工厂中打开会话,通过 class 类型和配置生成 Mapper 接口的代理实现,最后使用 Ma ...

  5. 关于 ReentrantLock 中锁 lock() 和解锁 unlock() 的底层原理浅析

    关于 ReentrantLock 中锁 lock() 和解锁 unlock() 的底层原理浅析 如下代码,当我们在使用 ReentrantLock 进行加锁和解锁时,底层到底是如何帮助我们进行控制的啦 ...

  6. 网络路径排查工具使用/原理浅析(MTR、traceroute、tracepath、windows下besttrace)

    在请求网络资源获取缓慢或者有丢包过程中.经常会使用到网络路径探测工具.linux 下最常用的有mtr.traceroute.tracepath 等. 你是否有一点疑惑,路径探测的原理到底是如何完成的, ...

  7. I2C 基础原理详解

    今天来学习下I2C通信~ I2C(Inter-Intergrated Circuit)指的是 IC(Intergrated Circuit)之间的(Inter) 通信方式.如上图所以有很多的周边设备都 ...

  8. HTTP长连接和短连接原理浅析

    原文出自:HTTP长连接和短连接原理浅析

  9. Javascript自执行匿名函数(function() { })()的原理浅析

    匿名函数就是没有函数名的函数.这篇文章主要介绍了Javascript自执行匿名函数(function() { })()的原理浅析的相关资料,需要的朋友可以参考下 函数是JavaScript中最灵活的一 ...

随机推荐

  1. 想涨工资吗?那就学习Scala,Golang或Python吧

    [编者按]据薪水调查机构 PayScale 提供的数据显示,掌握 Scala,Golang 和 Python 语言以及诸如 Apache Spark 之类的大数据技术,能带来最大的薪水提升.本文作者为 ...

  2. jquery中ajax的dataType的各种属性含义

    参考ajax api文档:http://www.w3school.com.cn/jquery/ajax_ajax.asp dateType后接受的参数参数类型:string 预期服务器返回的数据类型. ...

  3. 如何使用 Packer 在 Azure 中创建 Windows 虚拟机映像

    Azure 中的每个虚拟机 (VM) 都是基于定义 Windows 分发和操作系统版本的映像创建的. 映像可以包括预安装的应用程序和配置. Azure 应用商店为最常见的操作系统和应用程序环境提供了许 ...

  4. MyISAM和InnoDB的主要区别和应用场景

    主要区别: 1).MyISAM是非事务安全型的,而InnoDB是事务安全型的. 2).MyISAM锁的粒度是表级,而InnoDB支持行级锁定. 3).MyISAM支持全文类型索引,而InnoDB不支持 ...

  5. 4种更快更简单实现Python数据可视化的方法

    数据可视化是数据分析或机器学习项目中十分重要的一环.通常,你需要在项目初期进行探索性的数据分析(EDA),从而对数据有一定的了解,而且创建可视化确实可以使分析的任务更清晰.更容易理解,特别是对于大规模 ...

  6. THE ELEMENTS OF C# STYLE

    |前言 程序员其实艺术家,灵动的双手如行云流水般在键盘上创造着生命的奇迹,我认为代码是有灵魂的.同一个模块,在每个程序员手中所缔造出来的是不相同的. 最终,这个模块或者实现了最初的业务,但是回过头看看 ...

  7. [Errno 256] No more mirrors to try 解决方法

    安装tree时遇到问题yum [Errno 256] No more mirrors to try 解决方法: 1.yum clean all 2.yum makecache 3.yum update ...

  8. 【概率论】条件概率 & 全概率公式 & 朴素贝叶斯公式

    0. 说明 条件概率 & 全概率公式 & 朴素贝叶斯公式 学习笔记 参考 scikit-learn机器学习(五)--条件概率,全概率和贝叶斯定理及python实现 1. 条件概率 [定 ...

  9. [Spark Core] 在 Spark 集群上运行程序

    0. 说明 将 IDEA 下的项目导出为 Jar 包,部署到 Spark 集群上运行. 1. 打包程序 1.0 前提 搭建好 Spark 集群,完成代码的编写. 1.1 修改代码 [添加内容,判断参数 ...

  10. vcenter server appliance(vcsa) 配置IP的方法

    方法一: vcenter server appliance 5.1 及以后版本包括5.5,在安装完毕后,console界面是没有网络配置项的,如果需要进行IP配置,可以login后,输入命令yast( ...