首先为了防止某些专业挑刺人士无限制发挥,先声明几个前提
1:索引优化是基础工作,没做好这个其他的不用提,但本文不展开此内容。
2:优化数据库查询有非常多的分支,减少SQL请求只是其中一个领域,其他分支本文不涉及。
3:在部分场景下,甚至需要增加SQL以解决诸如分布式或其他问题,本文不涉及。
4:运维优化和其他优化手段本文不涉及。
5:产品业务逻辑优化本文不涉及。
6:其他本文没提到的内容欢迎自行联想,技术水准高超者请忽略本文。
第一、查询请求的分析和裁剪
线上系统,出现请求较多,数据压力较大(索引优化到位的前提下),我会让程序员输出一段时间的查询请求。(通常数据库操作有封装对象,直接记录日志即可,建议写入/dev/shm 以减少i/o压力,如果请求频次实在很高,可以取一定比例写入日志),然后基于日志分析。
1、完全一致的查询请求有多少,平均每秒会出现多少这样的查询。
比如常见的,所有页面都加载系统信息 select * from systeminfo;
2、基于同一数据表同一主键的查询有多少,平均每秒会出现多少这样的查询
比如 select name from userinfo where uid=10134; select email from userinfo where uid=10134;
这两种请求,是可以通过建立缓存机制来优化的,
而且做了这个分析,会有一个很好的数据认知
当前数据库每秒处理多少查询请求,其中可优化的冗余请求有多少,如果建立缓存可以减少多少请求。提升系统支撑性多少?
对于一些不是特别出色的开源系统,分析一下会发现,可裁剪的查询请求是非常巨大的。
第二、更新请求的分析和裁剪
更新请求也可以优化,
我们一般用mysql的情况下,是先解开binlog文件,还原为文本文件,然后分析
基于同一数据表,同一主键的更新请求有多少,平均每个时间段出现多少这样的请求
举例1:
update user set lastacttime=.... where uid=10314; 经常更新最后活跃时间
举例2:
update posts set views=views+1 where pid=10004211; 更新同一个帖子显示数字
如果这样的请求较多,那么可以有针对性的建立队列,定时异步更新。
在异步更新过程中
范例1 多条请求只要记住同一主键最后一条即可;
范例2 多条请求可以在程序中合并,对数据库操作只进行一次。
这样更新请求频次就极大下降了。
如果线上有实时性要求,线上可以保持一个内存数据做同步更新。
方法其实很简单,但是很有效
简单总结
第一,要随时了解自己的读写请求频次情况
第二,一定时间范围内针对同数据表,同主键的读写请求,均是可优化,可裁剪的,但是也要考虑当时的系统负载构成和请求频次、影响度,抓大放小,解决主要问题即可。
就这样,其他方面,参见前提说明。
想必大家对SimpleDateFormat并不陌生。SimpleDateFormat 是 Java 中一个非常常用的类,该类用来对日期字符串进行解析和格式化输出,但如果使用不小心会导致非常微妙和难以调试的问题,因为 DateFormat 和 SimpleDateFormat 类不都是线程安全的,在多线程环境下调用 format() 和 parse() 方法应该使用同步代码来避免问题。下面我们通过一个具体的场景来一步步的深入学习和理解SimpleDateFormat类。
一、引子
我们都是优秀的程序员,我们都知道在程序中我们应当尽量少的创建SimpleDateFormat 实例,因为创建这么一个实例需要耗费很大的代价。在一个读取数据库数据导出到excel文件的例子当中,每次处理一个时间信息的时候,就需要创建一个SimpleDateFormat实例对象,然后再丢弃这个对象。大量的对象就这样被创建出来,占用大量的内存和 jvm空间。代码如下:
package com.peidasoft.dateformat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class DateUtil { public static String formatDate(Date date)throws ParseException{ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.format(date); } public static Date parse(String strDate) throws ParseException{ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.parse(strDate); } } |
你也许会说,OK,那我就创建一个静态的simpleDateFormat实例,然后放到一个DateUtil类(如下)中,在使用时直接使用这个实例进行操作,这样问题就解决了。改进后的代码如下:
package com.peidasoft.dateformat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class DateUtil { private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static String formatDate(Date date)throws ParseException{ return sdf.format(date); } public static Date parse(String strDate) throws ParseException{ return sdf.parse(strDate); } } |
当然,这个方法的确很不错,在大部分的时间里面都会工作得很好。但当你在生产环境中使用一段时间之后,你就会发现这么一个事实:它不是线程安全的。在正常的测试情况之下,都没有问题,但一旦在生产环境中一定负载情况下时,这个问题就出来了。他会出现各种不同的情况,比如转化的时间不正确,比如报错,比如线程被挂死等等。我们看下面的测试用例,那事实说话:
package com.peidasoft.dateformat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class DateUtil { private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static String formatDate(Date date)throws ParseException{ return sdf.format(date); } public static Date parse(String strDate) throws ParseException{ return sdf.parse(strDate); } } |
package com.peidasoft.dateformat; import java.text.ParseException; import java.util.Date; public class DateUtilTest { public static class TestSimpleDateFormatThreadSafe extends Thread { @Override public void run() { while(true) { try { this.join(2000); } catch (InterruptedException e1) { e1.printStackTrace(); } try { System.out.println(this.getName()+":"+DateUtil.parse("2013-05-24 06:02:20")); } catch (ParseException e) { e.printStackTrace(); } } } } public static void main(String[] args) { for(int i = 0; i < 3; i++){ new TestSimpleDateFormatThreadSafe().start(); } } } |
执行输出如下:
Exception in thread "Thread-1" java.lang.NumberFormatException: multiple points at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1082) at java.lang.Double.parseDouble(Double.java:510) at java.text.DigitList.getDouble(DigitList.java:151) at java.text.DecimalFormat.parse(DecimalFormat.java:1302) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1589) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1311) at java.text.DateFormat.parse(DateFormat.java:335) at com.peidasoft.orm.dateformat.DateNoStaticUtil.parse(DateNoStaticUtil.java:17) at com.peidasoft.orm.dateformat.DateUtilTest$TestSimpleDateFormatThreadSafe.run(DateUtilTest.java:20) Exception in thread "Thread-0" java.lang.NumberFormatException: multiple points at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1082) at java.lang.Double.parseDouble(Double.java:510) at java.text.DigitList.getDouble(DigitList.java:151) at java.text.DecimalFormat.parse(DecimalFormat.java:1302) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1589) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1311) at java.text.DateFormat.parse(DateFormat.java:335) at com.peidasoft.orm.dateformat.DateNoStaticUtil.parse(DateNoStaticUtil.java:17) at com.peidasoft.orm.dateformat.DateUtilTest$TestSimpleDateFormatThreadSafe.run(DateUtilTest.java:20) Thread-2:Mon May 24 06:02:20 CST 2021 Thread-2:Fri May 24 06:02:20 CST 2013 Thread-2:Fri May 24 06:02:20 CST 2013 Thread-2:Fri May 24 06:02:20 CST 2013 |
说明:Thread-1和Thread-0报java.lang.NumberFormatException: multiple points错误,直接挂死,没起来;Thread-2 虽然没有挂死,但输出的时间是有错误的,比如我们输入的时间是:2013-05-24 06:02:20 ,当会输出:Mon May 24 06:02:20 CST 2021 这样的灵异事件。
二、原因
作为一个专业程序员,我们当然都知道,相比于共享一个变量的开销要比每次创建一个新变量要小很多。上面的优化过的静态的SimpleDateFormat版,之所在并发情况下回出现各种灵异错误,是因为SimpleDateFormat和DateFormat类不是线程安全的。我们之所以忽视线程安全的问题,是因为从SimpleDateFormat和DateFormat类提供给我们的接口上来看,实在让人看不出它与线程安全有何相干。只是在JDK文档的最下面有如下说明:
SimpleDateFormat中的日期格式不是同步的。推荐(建议)为每个线程创建独立的格式实例。如果多个线程同时访问一个格式,则它必须保持外部同步。
JDK原始文档如下:
Synchronization:
Date formats are not synchronized.
It is recommended to create separate format instances for each thread.
If multiple threads access a format concurrently, it must be synchronized externally.
下面我们通过看JDK源码来看看为什么SimpleDateFormat和DateFormat类不是线程安全的真正原因:
SimpleDateFormat继承了DateFormat,在DateFormat中定义了一个protected属性的 Calendar类的对象:calendar。只是因为Calendar累的概念复杂,牵扯到时区与本地化等等,Jdk的实现中使用了成员变量来传递参数,这就造成在多线程的时候会出现错误。
在format方法里,有这样一段代码:
private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) { // Convert input date to time field list calendar.setTime(date); boolean useDateFormatSymbols = useDateFormatSymbols(); for (int i = 0; i < compiledPattern.length; ) { int tag = compiledPattern[i] >>> 8; int count = compiledPattern[i++] & 0xff; if (count == 255) { count = compiledPattern[i++] << 16; count |= compiledPattern[i++]; } switch (tag) { case TAG_QUOTE_ASCII_CHAR: toAppendTo.append((char)count); break; case TAG_QUOTE_CHARS: toAppendTo.append(compiledPattern, i, count); i += count; break; default: subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols); break; } } return toAppendTo; } |
calendar.setTime(date)这条语句改变了calendar,稍后,calendar还会用到(在subFormat方法里),而这就是引发问题的根源。想象一下,在一个多线程环境下,有两个线程持有了同一个SimpleDateFormat的实例,分别调用format方法:
线程1调用format方法,改变了calendar这个字段。
中断来了。
线程2开始执行,它也改变了calendar。
又中断了。
线程1回来了,此时,calendar已然不是它所设的值,而是走上了线程2设计的道路。如果多个线程同时争抢calendar对象,则会出现各种问题,时间不对,线程挂死等等。
分析一下format的实现,我们不难发现,用到成员变量calendar,唯一的好处,就是在调用subFormat时,少了一个参数,却带来了这许多的问题。其实,只要在这里用一个局部变量,一路传递下去,所有问题都将迎刃而解。
这个问题背后隐藏着一个更为重要的问题--无状态:无状态方法的好处之一,就是它在各种环境下,都可以安全的调用。衡量一个方法是否是有状态的,就看它是否改动了其它的东西,比如全局变量,比如实例的字段。format方法在运行过程中改动了SimpleDateFormat的calendar字段,所以,它是有状态的。
这也同时提醒我们在开发和设计系统的时候注意下一下三点:
1、自己写公用类的时候,要对多线程调用情况下的后果在注释里进行明确说明
2、对线程环境下,对每一个共享的可变变量都要注意其线程安全性
3、我们的类和方法在做设计的时候,要尽量设计成无状态的
三、解决办法
1、需要的时候创建新实例:
package com.peidasoft.dateformat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class DateUtil { public static String formatDate(Date date)throws ParseException{ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.format(date); } public static Date parse(String strDate) throws ParseException{ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.parse(strDate); } } |
说明:在需要用到SimpleDateFormat 的地方新建一个实例,不管什么时候,将有线程安全问题的对象由共享变为局部私有都能避免多线程问题,不过也加重了创建对象的负担。在一般情况下,这样其实对性能影响比不是很明显的。
2、使用同步:同步SimpleDateFormat对象
package com.peidasoft.dateformat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class DateSyncUtil { private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static String formatDate(Date date)throws ParseException{ synchronized(sdf){ return sdf.format(date); } } public static Date parse(String strDate) throws ParseException{ synchronized(sdf){ return sdf.parse(strDate); } } } |
说明:当线程较多时,当一个线程调用该方法时,其他想要调用此方法的线程就要block,多线程并发量大的时候会对性能有一定的影响。
3、使用ThreadLocal:
package com.peidasoft.dateformat; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class ConcurrentDateUtil { private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } }; public static Date parse(String dateStr) throws ParseException { return threadLocal.get().parse(dateStr); } public static String format(Date date) { return threadLocal.get().format(date); } } |
另外一种写法:
package com.peidasoft.dateformat; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class ThreadLocalDateUtil { private static final String date_format = "yyyy-MM-dd HH:mm:ss"; private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(); public static DateFormat getDateFormat() { DateFormat df = threadLocal.get(); if(df==null){ df = new SimpleDateFormat(date_format); threadLocal.set(df); } return df; } public static String formatDate(Date date) throws ParseException { return getDateFormat().format(date); } public static Date parse(String strDate) throws ParseException { return getDateFormat().parse(strDate); } } |
说明:使用ThreadLocal, 也是将共享变量变为独享,线程独享肯定能比方法独享在并发环境中能减少不少创建对象的开销。如果对性能要求比较高的情况下,一般推荐使用这种方法。
4、抛弃JDK,使用其他类库中的时间格式化类:
1)使用Apache commons 里的FastDateFormat,宣称是既快又线程安全的SimpleDateFormat, 可惜它只能对日期进行format, 不能对日期串进行解析。
2)使用Joda-Time类库来处理时间相关问题
做一个简单的压力测试,方法一最慢,方法三最快,但是就算是最慢的方法一性能也不差,一般系统方法一和方法二就可以满足,所以说在这个点很难成为你系统的瓶颈所在。从简单的角度来说,建议使用方法一或者方法二,如果在必要的时候,追求那么一点性能提升的话,可以考虑用方法三,用ThreadLocal做缓存。
Joda-Time类库对时间处理方式比较完美,建议使用。
mysql 创建 数据库时指定编码很重要,很多开发者都使用了默认编码,乱码问题可是防不胜防。制定数据库的编码可以很大程度上避免倒入导出带来的乱码问题。
网页数据一般采用UTF8编码,而数据库默认为latin 。我们可以通过修改数据库默认编码方式为UTF8来减少数据库创建时的设置,也能最大限度的避免因粗心造成的乱码问题。
我们遵循的标准是,数据库,表,字段和页面或文本的编码要统一起来
我们可以通过命令查看数据库当前编码:
mysql> SHOW VARIABLES LIKE 'character%';
发现很多对应的都是 latin1,我们的目标就是在下次使用此命令时latin1能被UTF8取代。
第一阶段:
mysql设置编码命令
SET character_set_client = utf8; SET character_set_connection = utf8; SET character_set_database = utf8; SET character_set_results = utf8; SET character_set_server = utf8; |
然后 mysql> SHOW VARIABLES LIKE 'character%'; 你可以看到全变为 utf8 。
但是,这只是一种假象
此种方式只在当前状态下有效,当重启数据库服务后失效。
所以如果想要不出现乱码只有修改my.ini文件,
从my.ini下手(标签下没有的添加,有的修改)
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
[mysqld]
default-character-set=utf8
以上3个section都要加default-character-set=utf8,平时我们可能只加了mysqld一项。
然后重启mysql,执行
mysql> SHOW VARIABLES LIKE 'character%';
确保所有的Value项都是utf8即可。
但是可恶的事情又来了,
|character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | latin1 |
| character_set_system | utf8
注意 该配置| character_set_server | latin1 无法设置成UTF8 交互时候仍然会出现乱码。
第二阶段:找到下面这东东
X:\%path%\MySQL\MySQL Server 5.0\bin\MySQLInstanceConfig.exe
重新启动设置,将默认编码设置为utf8.这样就能达到我们所要的效果了。
mysql> SHOW VARIABLES LIKE 'character%';
+--------------------------+---------------------------------------------------------+
| Variable_name | Value |
+--------------------------+---------------------------------------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | utf8 |
| character_set_system | utf8 |
| character_sets_dir | C:\Program Files\MySQL\MySQL Server 5.0\share\charsets\ |
+--------------------------+---------------------------------------------------------+
8 rows in set
另外注意事项:
1、建表时加utf8,表字段的Collation可加可不加,不加时默认是utf8_general_ci了。
CREATE TABLE `tablename4` ( `id` int(11) NOT NULL AUTO_INCREMENT, `varchar1` varchar(255) DEFAULT NULL, `varbinary1` varbinary(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 |
2、网页xxx.php/jsp保存时选择utf8编码,页头最好加上
header('conten-type:text/html;charset=utf-8');
在执行CRUD操作前先执行一下
mysql_query("set names utf8");
-------------------------
连接数据库设置编码
jdbc:mysql://地址:3306/数据库名?characterEncoding=utf8
-------------------------
java中的常用编码UTF-8;GBK;GB2312;ISO-8859-1;
对应mysql数据库中的编码utf8;gbk;gb2312;latin1
第一章 简介
什么是自动化测试
自动化测试是对一个已有的手工测试过程减少并尽可能排除人工干预的过程。
什么时候适合做自动化测试
下面是一组适合将手工测试自动化的考量因素:
● 测试需要经常重复。
● 测试流程和验证点相对长时间比较稳定。
● 测试目的是验证一个业务流程,而不是外观,感觉,颜色,图表布局等。
● 测试需要大量重复或者同时包含很多步骤,并且这些操作每次都需要完全一致,这就要求手工测试者不能疲劳大意。
● 测试生成的结果被监管机构要求电子化记录和存档并符合正式的证据要求。
● 测试通过或失败的结果相当容易判断且被所选自动化工具捕获。
● 测试需要使用大量的数据到被测应用程序中。
什么时候需要避免自动化
● 随机性测试,领域专家在各种业务流程组合中的随机尝试。
● 一次性测试或者只重复数次。
● 测试需要覆盖多个功能模块且这些功能模块在整个产品功能中的测试覆盖几乎非常小。
● 测试验证外观,感觉,颜色,图表的布局等。
● 测试结果是否通过需要从多个不同并且不相关的系统或(和)应用中判断
自动化测试流程
理解自动化测试中包含的各个阶段对于开发和有效利用测试框架以及用例非常重要:
● 选择“最适合的”自动化工具:在对任何应用开始自动化测试之前,重要的是针对主要应用部分选择最适合的工具。选择需要基于各种因素,比如价格,易用性,应用支持能力和产品服务支持。
● 概念证明(POC):此阶段包含创建一些脚本示例用来在一两个最重要的被测应用中验证业务流程。它可以帮助识别未来在测试用例的脚本开发中有可能碰到的主要问题。概念证明也可用来为你的应用选择最适合的自动化测试工具。
● 需求分析:包含分析某个应用的需求,研究已有的手工测试用例和定义当前自动化测试项目的范围。
● 项目估算:一旦自动化范围定义好,项目估算就可以根据各种因素,如需要自动化的测试用例数量,复杂程度,需开发的可复用模块,人员需求等制定下来。
● 框架设计:包含创建共享对象库,重用模块,编写最佳实践参考文档,以及实现任何可以对开发自动化测试脚本有用的基础支持组件。
● 测试脚本开发:通过调用可重用模块和在工作流具体脚本中增加相关验证点来创建测试用例。
● 调试:完成的测试脚本应该是经过调试的,以保证运行时符合预先设计。要确保在调试时使代码经过所有错误处理路径。
● 执行:在这个阶段测试脚本最终在回归测试中执行来验证被测应用。
● 结果分析:此阶段流程依据执行时生成的各个测试结果。
● 维护:这个阶段包括更新脚本来解决执行中发现的代码问题,其中可能包含UI或结构变动,或者是流程,功能以及新版本中不可避免的变更。一个设计良好的框架和测试集可以保证维护成本达到最小。
(未完待续)
版权声明:51Testing软件测试网及相关内容提供者拥有51testing.com内容的全部版权,未经明确的书面许可,任何人或单位不得对本网站内容复制、转载或进行镜像。51testing软件测试网欢迎与业内同行进行有益的合作和交流,如果有任何有关内容方面的合作事宜,请联系我们。
对于敏捷开发中的测试,其实之前我也是认为很有必要的。
对于一个敏捷开发过程(或者按照周博士的话叫做混合型的敏捷开发过程)而言,我觉得测试这个过程不是一个单一的过程,因为它不是只在某一个阶段开展的,而是在整个过程中无时无刻不见它。
1、需求阶段:
当一个需求被设计出来的时候,测试就必须介入,做什么事情呢?
第一个事情,根据这个设计文档,开始写测试用例
第二个事情,根据这个设计文档,结合用户的实际需求,从概念上看是否真的能够符合客户的需求(这个很重要,一旦能够发现问题,等于给公司减少了非常多的钱,因为如果有设计问题到最后被发现,那个时候开发和测试已经投入多少力量去做了,而且所用的代码可能会影响到其他程序的运行,要改回来,代价是很大的。)
2、开发阶段:
虽然开发阶段是开发人员的事情,但是测试人员同样必须介入:
第一个事情,开发人员完成代码后,白盒测试人员需要介入进去。
第二个事情,开发人员进行开发时,需要确保他们的开发能否符合测试用例中的测试点的要求。(我们知道,测试人员与开发人员的思想不是很一致,那天周博士也举了下拉框那个例子就能很好的说明问题,所以现在的情况是,测试人员已经提前把很多测试的点列出来,虽然还有不少点可能会在实际测试中发现,但是开发人员在开发阶段如果能把测试用例中这些测试点都覆盖得很好,那么产品质量从初期就已经在一个正确的道路上前进了。当然如果开发人员自顾自,觉得怎么做就怎么做,到后来发现不符合设计思想,然后去返工,这种后果造成的影响大家都能想象得到。)
3、测试阶段:
这个测试阶段的确是传统上测试人员拿到产品开始测试的阶段,但是它也不是一个单一的阶段,也需要很多不同的阶段组成。
第一个阶段:迭代测试阶段
这个阶段中,需要把当前迭代中完成的功能进行测试,完成一个就测试一个。在敏捷中,理论上每天都会有很用的Build让测试人员可以测试,所以一旦一个功能开发完成了以后,必然我们能够马上得到一个Build开展测试,发现重要的Bug就可以马上修复。
这个阶段非常重要,因为这是一个造成代价最小的阶段,首先开发刚完成功能,修起Bug来轻车熟路,另外一点,这个代码还没有被其他一些功能所调用,所以对其他地方造成的影响还不会很大。
所以测试人员需要在这个阶段投入大量人力去保质保量完成。
第二个阶段:多迭代集成测试阶段
当几个迭代已经过去了,有很多功能已经做好了以后,这个时候考虑到虽然单个功能看起来都还好,但是这么多功能现在都做完了,是不是相互之前会影响啊,或者对于稳定性造成影响之类的的因素,有必要进行多迭代功能集成测试(也包括了回归测试),这种测试比较多的是在类似Alpha Release,Beta Release的时候进行。
第三个阶段:系统性综合测试阶段
当所有迭代都完成以后,当然我们还是需要进行一次全方位的测试(当然也包括负载测试、性能测试等),这个就比较类似传统那个测试阶段了,这里就不多说了。
我们看得出,测试这块在SpecDD中占了很大一个地位,而且更重要的是,它是一个不固定的过程,在SpecDD中几乎每个过程都是需要测试这个过程存在的,所以它被称之为“流浪汉“也比较贴切,或者叫”济公“也不错,因为“哪里不平哪有我”。
相关链接:
什么是SpecDD?
不知道大家在做性能测试的时候,测试数据是如何准备的,笔者在实际工作中发现测试数据的准备会遇到以下几个问题:
其一,由于性能测试需要具备一定的并发量,尤其在实际系统所能承受最大并发量未知的情况下,测试数据的量也必须满足预期业务并发量的一个量的需求,如何准备这些量的数据是第一个问题;
其二,除了量的需求,数据也必须是符合业务逻辑的,是可用或者可测试用的数据,不是脏数据或无效数据。比如表与表之间是具备一定的关联关系,记录之间也有关联关系,所有的测试数据要符合这些规则,如何完全了解掌握这些规则,并且根据规则来生成测试数据是第二个问题;
其三,性能测试往往是安排在功能测试完成之后,在项目进度非常紧张的情况下,时间资源甚至是人力资源非常有限的情况下,如何快速掌握业务,准备有效的并且符合量的需求的测试数据是一个比较大的挑战。
针对以上问题,笔者工作中用到以下几种数据准备的方法:
一是用SQL脚本方式,插入测试数据,但是有几个前提条件,首先需要对该业务下所有关联的表结构非常熟悉,其次对整个业务也需要非常熟悉,而这些条件只有开发或者功能测试人员会具备。在以这种方式准备数据的时候,需要密切与开发或者功能测试人员进行沟通了解学习,并且在信息来源不全的情况,需要不断尝试,不断调试才能够准备出符合要求的测试数据。但是仍然会存在风险,即便数据准备完毕,也不能完全确保这些数据是真正合法的,可能这些数据符合被测业务的需求,但是却不符合其他业务或者实际生产环境的需求,也就是说不能完全代表真实数据;并且也存在遗漏其他数据但是业务却跑通的情况。通常情况下,SQL脚本批量导入数据的方式仍然是非常直接有效的方法,比较灵活,量和业务需求都是可控的;缺点就是需要搞清表间关系,精通业务流程,脚本也需要经常维护。
二是通过业务的方式去产生测试数据,当然不是手工去一个一个添加,如果量很大,势必需要依靠自动化工具来实现。这种情况下,测试人员只需要了解业务的操作流程,然后采用自动化工具比如LoadRunner,QTP之类就能通过业务大量生成数据,这样的数据一般都是合法可用的,能够确保之后的性能测试的质量。然而缺点也很明显,需要开发额外的测试脚本,要花费额外的时间和人力。
三是直接采用生产数据,在有现成数据并且数据保密性要求不高的情况下,可以采用这种方法,毕竟生产数据是原原本本的用户行为所产生的数据。但是有这样几个缺点,量不可能控,可能某些业务的数据量少了,不符合性能测试的需求;生产数据也会有脏数据的产生,会因为系统架构的调整,表结构的变化等等诸多因素产生脏数据,而这些数据是不具备业务意义的;多数情况下,生产数据一般不会被用于测试。
在没有开发的支持下,第一种方法会略显困难,但第一种方法也是非常直接有效的。
最后讲一下,我所设想的最后一种方法,可以节省很多时间和精力,从而把重点放在性能的调优上。在性能测试的初期分析阶段,可以先确立被测的模块,尽量缩小模块的范围,针对这个小模块的业务来准备数据,让开发配合去掉不必要的业务限制,比如说验证码、资格码之类就用相同的码就能验证通过,尽量减少数据之间的关联和限制。这样在准备数据的时候就非常轻松,可能只是简单的插入操作而已。从而把主要的精力放在了性能调优或者用户模型以及场景的设计上。
如果各位看客有自己的想法或者经验,非常欢迎畅所欲言,感激不尽!
摘要: 在性能测试中遇到性能瓶颈最的多地方就是数据库这块,而数据库出问题很多都是索引使用不当导致,根据以往遇到的索引问题做个简单的总结: 一、索引的利弊 索引的好处:索引能够极大地提高数据检索的效率,让Query 执行得更快,也能够改善排序分组操作的性能,在进行排序分组操作中利用好索引,将会极大地降低CPU资源的消耗。 索引的弊端: 1、更新数据库时会更新索引,这样,最明显的资源消耗就是增加了更新...
阅读全文
之前对于敏捷开发,我还是比较喜欢Scrum的方式,不过Scrum的方式大家也知道,虽然很敏捷,但是总是有些缺陷的,如下:
1、缺少对多团队参与的大型项目的指导框架。(我的想法:敏捷一般是针对小团队,但是类似大公司,比如微软、Sony这样子的,需要共同开发一个大的软件,比如Windows 8,能不能运用敏捷,怎么去运用敏捷,这种问题在现有的敏捷体系下很难回答出来)
2、缺少开发和QA测试的集成模型。(我的想法:现有的敏捷中对于测试这块的确重视不多,甚至有人认为开发做得好完全就不必测试了,其实真的开发做的100% Bug Free,这个世界上的确不用测试了,但是能吗?另外,开发一般只考虑正在做的这个功能是否能够正常工作,但是对于多个功能之间的调用之类的测试就不会考虑很多)
3、无法支持需求驱动下完整的可追溯性。(我的想法:Scrum不推荐写文档啊,提Bug啊,最好所有事情,都能面对面交流就行了,但是实际情况呢?面对面的交流,也许你今天能记得,但是明天可能忘记;如果只有一个Bug要修,你记得很清楚,但是如果是100个Bug要修呢,你能记得很清楚吗?如果你手下有N个开发,你怎么去记得哪个功能分配给谁在做呢?)
4、整个团队完全致力于项目的开发是基本前提。一旦开发团队的方向出现变化,会导致项目的崩溃。(我的想法:其实这个现象已经在很多开展敏捷开发的团队中见到,都是一开始满意欢喜想上马敏捷,最后却崩溃了。因为“专家”所谓的敏捷固定过程中,人人都要主动交流啊,自领任务啊,估算任务等这种很理想的状态并不是所有公司,所有人都能实现的,一旦当偏离值出现的时候,也就是这个项目崩溃的开始。)
5、一旦使用敏捷方法失败,不但时间会被浪费,团队人员也会出现松动。当相关人员离职后,整个过程的经验积累也无法得以保存。(我的想法:同第三点)
很多公司虽然在搞敏捷,其实都是被很多所谓的“专家”搞混了,以为敏捷会有一个固定的模式,其实这个是不对的,敏捷只是一种指导思想,软件的生命周期不会因为敏捷而发生变化,还是会有需求,还是会有开发,也还是会有测试,在这个的基础上,怎么去让这些过程能够衔接得很好,最后得到一个质量好的产品,才是最重要的。显然,敏捷是其中的一个方法,但是它也不是万能的,因为不同的产品会有不同的开发模式,比如,银行软件可能就不太适合敏捷。
在我看来,SpecDD其实是对于Scrum的一个升级。
这个模型中,大家可以看到Scrum的精神思想其实也完全体现得淋漓尽致,这个也就是为什么我说SpecDD其实可以看作Scrum的一个升级版一样。
在这个模型中,我最有感受的是测试这块,所以下面我就主要说说这块,今天就暂时不谈了。
(未完待续)
14)在远程机器上使用QTP时,我们能否记录应用的运行过程呢?
当然可以。你可以通过本地浏览器而不是通过像citrix 这样的遥控器记录你进入的远程应用。
如果你仍然不能记录,建议您将QTP 和应用安装在同一个机器上。
15)用一个例子解释创建对象的关键词。
创建并返回一个自动化对象的引用。
语法:CreateObject(servername.typename [,location])
参数:
servername:需要。提供对象的应用的名字。
typename:需要。创建对象的类型或者类。
location:可选。创建对象的网络服务器名字。
16)你能在Per-Action 和Shared Object Repository中切换吗?如果能,怎么切换的?
可以转换。找到Test,然后选择Settings,最后选择Resources,在这里可以选择储存库了。
17)什么是对象间谍?如何使用它?
对象间谍有助于确定正在被测试的应用的运行测试时间对象属性和方法。
您可以直接从工具栏或者对象库对华康直接访问对象间谍。
这在描述编程过程中非常有用。
18)光序标志符就可以使一个对象独一无二,那为什么不给他们顶级优先权呢?为什么它是先强制后辅助的?为什么我们不能直获取序标志符?
因素如下:
(a)如果两个对象彼此重叠,基于位置的对象识别将失败。
(b)如果仅基于索引识别你的脚本可以运行,但是执行时间将增加。
因此使用强制和辅助的特性。
19)在QTP代码文件中扩展名是什么?
代码的文件扩展名是script.mts.
20)简要解释一下QTP自动化对象模型。
QTP自动化对象模型处理自动化QTP本身。几乎所有的QTP提供的配置和功能都用QTP自动化对象模型展示。
几乎所有QTP的对话框有一个相应的自动化对象,可以使用自动化对象模型的相关属性或者方法设置或检索。
QTP自动化对象可以和标准的VB编程元素一起使用,如迭代循环或条件语句来帮助你设计脚本的选择。 1)QTP支持那些环境?
QTP支持以下环境:
Active X、Delphi、Java、Net、Oracle、People Soft Power Builder、SAP、Siebel、Stingray Terminal Emulator、Visual Basic Visual Age 、Web、Web Services
2)QTP的对象库类型是什么?
QTP支持两种类型的对象库:
(1) 共享对象库(也叫全局对象库)
(2) 单例对象库(也叫局域对象库)
共享对象库在处理多次测试中的静态对象时适用,扩展名是“.tsr”。
默认的是单例对象库,它的扩展名是“.mtr”
3)可以在其他测试中用脚本语言调用QTP测试吗?假设有四个测试,我想在一个主脚本中调用这些测试,这种QTP中可能吗?
答案是肯定的。你可以在你的测试中调用四个甚至更多脚本。为了达到这个目的,需保证相应脚本中的Actions是可重用的。然后从目标脚本中可以使调用这些可重用的actions。
4)什么是action split?在QTP中使用action split的目的何在?
Action split将存在的action拆分为两部分,目的在actions函数的基础上提高代码的可重用性。
5)在QTP中你如何处理Java树?
首先,选择 Java Add-In 启动QTP。第二步记录在Java树中的操作,如果在记录过程中遇到问题,选择Tools > Object Identification > Java、tree object,强制更改辅助属性来启用识别。
提示:对于任何环境的对象,你都可以把用类似的方法回答。
例如:如果问题是你怎样检查SAP多选框,你说,首先我该选择SAP Add in……等等。
6)解释一下QTP是如何识别对象的?
QTP识别任何GUI对象基于其相应的属性。当记录的时候,QTP在对象存储库的GUI对象中识别并且存储特殊属性(如在物体识别设置中定义的一样)。在运行时,QTP将比较存储属性值与屏幕上的属性。
7)QTP中有多少种记录模式?分别在什么时候用到?
QTP支持三种记录模式:
(1)普通模式,也叫上下文模式
(2)低级记录模式
(3)模拟模式
普通模式:默认的记录模式,充分利用了QTP的试验对象模型。它可识别屏幕任何位置的对象。这是重新编码的最佳模式,用于大多数的自动化测试。
低级记录模式: 这个模式准确地记录鼠标操作的x,y坐标。它有助于测试哈希表。它是用于记录QTP的正常模式下的不确定对象。
模拟模式:这个模式记录执行关于屏幕/应用程序窗口时鼠标和键盘的精确“动作”。这种模式对一些操作是很有用的,例如画画、记录签名、拖放操作。
8)你怎样在一个action中调用另一个action?
调用action有两种方法:
(1)调用复制的action。用这种方法,操作对象存储库、脚本和时间戳将被复制到目标测试脚本。
(2)调用现有的action。用这种方法,操作对象存储库、脚本和时间戳不会被复制,但一个调用(参考)将在源脚本的action上生成。
9)什么是虚拟对象?
应用程序可能包含有着像标准对象行为的对象,但不被QTP识别。您可以把这些对象定义为虚拟对象并将它们映射到标准的类,比如按钮或一个复选框。QTP在运行的会话中在虚拟对象上模拟用户的动作。在测试结果里,虚拟对象被当成标准的类对象展示出来。
例如,假设你想记录一个测试包含用户单击的位图的Web页面。位图包含几个不同的超链接区域,每个区域打开一个不同的目的页面。当你记录一个测试,Web站点匹配单击位图的坐标并且打开目标页面。
在运行的会话中启用QTP以点击所需坐标,你可以为一个区域的位图定义一个虚拟对象,其中包括那些坐标,并将其映射到按钮类。当运行一个测试时,QTP单击一个被定义为虚拟对象位图所在区域,这样网址就可以打开正确的目的地页面了。
10)如何使用QTP执行跨平台测试和跨浏览器测试?你能用一些例子解释说明吗?
创建单独的Actions,以满足不同的操作系统和浏览器的需要。
跨平台测试:
用内置的环境变量挖掘操作系统信息。
如:平台=环境(“操作系统”)。然后基于平台需要调用你记录在那个特定的平台的actions。
跨浏览器测试:
使用代码Browser("Core Values").GetROProperty("version"),可提取浏览器和它的相关版本。例如:Internet Explorer 6或Netscape 5,基于这个值您可以调用与浏览器相关的actions。
11)对象的逻辑名字是什么?
逻辑名字是QTP 在库里创建一个可唯一地与应用里的其他对象识别的对象时起的名字。它被QTP用来映射对象库中对象名相应的描述。如:Browser("Browser").Page("Guru99") ,在这里,Guru99 是对象的逻辑名字。
12)什么是描述性编程?
通常情况下,一个对象及其属性必须被记录在对象资源库,让QTP执行操作。
用描述性编程,你不用在对象资源库里储存对象及其属性值,而是在脚本里提到属性值对。
描述性编程背后的这种思想不是想绕开对象库,而是帮助识别静态对象。
13)运用描述性编程的时候,你用什么属性来识别浏览器网页?
可以用名字属性。
如:ex:
Browser("name:="xxx"").page("name:="xxxx"").....
或者,我们也可以用属性"micClass"。
如:ex:
Browser("micClass:=browser").page("micClass:=page")....
这是一个典型的Bug跟踪过程,设计者笃信只有完整的检查才会保证修改一个Bug的时候不会产生另外一个Bug。但是现实好像总是跟他作对,Bug返回率一直居高不下。于是设计者修订了流程,增加了几个步骤。他希望问题能够通过复查被发现出来。但是增加了流程以后Bug返回率反而提高了。
“这是人的问题!”流程设计者认为。
这不是人的问题,这是流程的问题。我说。
另外一个团队,并没有采用这样的模型,Bug返回率却很低,而且修改的时间明显的要短于前一个团队。他们采用的是这个流程:
这个流程和前一个流程的最大区别在于测试是自动进行的,而且由开发团队自己进行管理。这是开发团队的内部流程,由测试团队提出的Bug,将会被首先整理成一组测试用例来进行复现,自动测试中的代码可以自由修改,所以,问题定位要快的多,并且修改了代码以后,可以马上对测试用例进行回归测试。很快就可以知道是否修改完毕。并且,对于有影响的模块,由于已经有了大量的测试用例,可以全部回归,从而避免了因为影响性分析不充分而造成的Bug返回,所以大大的降低了Bug的返回率。
我们为一家大型电信公司做咨询的时候,他们采用了比图1更加复杂的流程,还有上传修改代码和确认的过程,但是效果没有得到改善,而且所耗时间更长。在采用了我们帮助其完善的新流程以后,Bug修改时间和效果上都得到了大大的改善。
注:Bug返回率是指,开发团队将修改后的代码提交给测试团队以后,测试团队验证Bug修改状况,对于没有修改正确的Bug返回到开发团队的情况称作返回。返回个数占修改个数的比例称作Bug返回率。