yanglilibaobao/
共986个网摘 [
1 2 3 4 5 6 ...
33 ]
下一页 |
访问yanglilibaobao的个人空间
yanglilibaobao收录,使用标签:数据库,时间:2007-8-16 9:59:32 | 相关网摘,我也收藏
一、概述
随着数据库在各个领域的使用不断增长,越来越多的应用提出了高性能的要求。数据库性能调优是知识密集型的学科,需要综合考虑各种复杂的因素:数据库缓冲区的大小、索引的创建、语句改写等等。总之,数据库性能调优的目的在于使系统运行得更快。
调优需要有广泛的知识,这使得它既简单又复杂。
说调优简单,是因为调优者不必纠缠于复杂的公式和规则。许多学术界和业界的研究者都在尝试将调优和查询处理建立在数学基础之上。
称调优复杂,是因为如果要完全理解常识所依赖的原理,还需要对应用、数据库管理系统、操作系统以及硬件有广泛而深刻的理解。
数据库调优技术可以在不同的数据库系统中使用。如果需要调优数据库系统,最好掌握如下知识:1)查询处理、并发控制以及数据库恢复的知识;2)一些调优的基本原则。
这里主要描述索引调优。
二、索引调优
索引是建立在表上的一种数据组织,它能提高访问表中一条或多条记录的特定查询效率。因此,适当的索引调优是很重要的。
对于索引调优存在如下的几个误区:
误区1:索引创建得越多越好?
实际上:创建的索引可能建立后从来未使用。索引的创建也是需要代价的,对于删除、某些更新、插入操作,对于每个索引都要进行相应的删除、更新、插入操作。从而导致删除、某些更新、插入操作的效率变低。
误区2:对于一个单表的查询,可以索引1进行过滤再使用索引2进行过滤?
实际上:假设查询语句如下select * from t1 where c1=1 and c2=2,c1列和c2列上分别建有索引ic1、ic2。先使用ic1(或ic2)进行过滤,产生的结果集是临时数据,不再具有索引,所以不可使用ic2(或ic1)进行再次过滤。
索引优化的基本原则:
1.将索引和数据存放到不同的文件组
没有将表数据和索引数据存储到不同的文件组,而不加区别地将它们存储到同一文件组。这样,不但会造成I/O竞争,也为数据库的维护工作带来不变。
2.组合索引的使用
假设存在组合索引it1c1c2(c1,c2),查询语句select * from t1 where c1=1 and c2=2能够使用该索引。查询语句select * from t1 where c1=1也能够使用该索引。但是,查询语句select * from t1 where c2=2不能够使用该索引,因为没有组合索引的引导列,即,要想使用c2列进行查找,必需出现c1等于某值。
根据where条件的不同,归纳如下:
1) c1=1 and c2=2:使用索引it1c1c2进行等值查找。
2) c1=1 and c2>2:使用索引it1c1c2进行范围查找,可以有两种方法。
方法1,使用通过索引键(1,2)在B树中命中一条记录,然后向后扫描找出 第一条符合条件的记录,从此记录往后的每一条记录都是符合条件的。这种方法的弊端在于:如果c1=1 and c2=2对应的记录数很多,会产生很多无效的扫描。
方法2,如果c2对应的int型数据,可以使用索引键(1,3)在B树中命中一条记录,从此记录往后的每一条记录都是符合条件的。
本文中的例子均采用方法1。
3)c1>1 and c2=2:因为索引的第一个列不是等于号的,索引即使后面出现了c2=2,也不能将c2=2应用于索引查找。这里,通过索引键(1,- ∞)在B树中命中一条记录,向后扫描找出第一条符合c1>1的记录,此后的每一条记录判断是否符合c2=2,如果符合则输出,否则过滤掉。这里我们称c2=2没有参与到索引运算中去。这种情况在实际应用中经常出现。
4)c1>1:通过索引键(1,- ∞) 在B树中命中一条记录,以此向后扫描找出第一条符合c1>1的记录,此后的每条记录都是符合条件的。
3.唯一索引与非唯一索引的差异
假设索引int1c1(c1)是唯一索引,对于查询语句select c1 from t1 where c1=1,达梦数据库使用索引键(1)命中B树中一条记录,命中之后直接返回该记录(因为是唯一索引,所以最多只能有一条c1=1的记录)。
假设索引it1c2(c2)是非唯一索引,对于查询语句select c2 from t2 where c2=2,达梦数据库使用索引键(2)命中B树中一条记录,返回该记录,并继续向后扫描,如果该记录是满足c=2,返回该记录,继续扫描,直到遇到第一条不符合条件c2=2的记录。
于是,我们可以得知,对于不存在重复值的列,创建唯一索引优于创建非唯一索引。
4.非聚集索引的作用
每张表只可能一个聚集索引,聚集索引用来组织真实数据。语句“create table employee (id int cluster primary key,name varchar(20),addr varchar(20))”。表employee的数据用id来组织。如果要查找id=1000的员工记录,只要用索引键(1000)命中该聚集索引。但是,对于要查找name=’张三’的员工记录就不能使用该索引了,需要进行全表扫描,对于每一条记录判断是否满足name=’张三’,这样会导致查询效率非常低。
要使用聚集索引,必需提供id,我们只能提供name,于是需要引入一个辅助结构实现name到id的转换,这就是非聚集索引的作用。该非聚集索引的键是name,值是id。于是语句“select * from employee where name=’张三’”的执行流程是:通过键(’张三’)命中非聚集索引,得到对应的id值3(假设’张三’对应的id为3),然后用键(3)命中聚集索引,得到相应的记录。
5.是不是使用非聚集索引的查询都需要进行聚集的查询?
不是的,虽然在上一点中查询转换为聚集索引的查找,有时候可以只需要使用非聚集索引。
创建表并创建相应的索引:create table t1(c1 int,c2 int,c3 int);create index it1c2c3 on t1(c2,c3)。查询语句为:select c3 from t1 where c2=1。
因为索引it1c2c3(c2,c3)覆盖查询语句中的列(c2,c3)。所以,该查询语句的执行流程为:通过索引键(1,- ∞)命中索引it1c2c3,对于该记录直接返回c3对应的值,继续向后扫描,如果索引记录中c1还是等于1,那么输出c3,以此类推,直到出现第一条c1不等于1的索引记录,结束查询。
6.创建索引的规则
创建索引首先要考虑的是列的可选择性。比较一下列中唯一键的数量和表中记录的行数,就可以判断该列的可选择性。如果该列的“唯一键的数量/表中记录行数”的比值越接近于1,则该列的可选择行越高。在可选择性高的列上进行查询,返回的数据就较少,比较适合索引查询。相反,比如性别列上只有两个值,可选择行就很小,不适合索引查询。
http://news.csdn.net/n/20070816/107539.html
yanglilibaobao收录,使用标签:数据库,时间:2007-8-15 17:47:13 | 相关网摘,我也收藏
一、概述
这篇文章是数据库性能调优技术的第二篇。上一篇讲解的索引调优是数据库性能调优技术的基础。这篇讲解的深入理解单表执行计划,是数据库性能调优的有力工具。
查询语句可以有多种可选执行计划,如何选择效率最高的执行计划?达梦数据库、oracle数据库、sql server数据库都是采用基于成本的查询优化,对备选执行计划进行打分,选择大家最小的执行计划进行执行。这些内容,我会在后续的几篇文章中进行详细的描述。在此之前,我们首先需要掌握如何理解数据库执行计划。这篇文章讲解只涉及单表操作的执行计划。
达梦数据库、oracle数据库、sql server数据库都可以显示给定语句的执行计划。我详细分析了这三个数据库的执行计划,三者之间并无本质区别。
所以本文的内容适合于这三个数据库。同样,也应该适合绝大多数其它的数据库。
单表执行的深入理解,是了解多表执行计划的基础。达梦数据库显示的执行计划时,显示的信息会多一些。
因此,这篇文章中我选择达梦数据库作为实例数据库来讲解执行计划的原理。
读完本文后,应该能够读懂这三个数据库的单表执行计划。
二、深入理解数据库执行计划
达梦数据库的执行计划有两种显示方式:第一种为图形化的显示方式;第二种为文本式的显示方式。这里采用第二种方式进行讲解。
理解执行计划,是迈向理解数据库性能调优的重要一步。从执行计划中,我们可以看出数据库是如何执行查询语句,并根据执行计划判断出该查询语句的执行是否高效,以及如何进行优化。
下面我们将通过一些例子来理解数据库执行计划。
1.没有索引的全表扫描过滤如何执行?
构造处执行场景:
create table t1(c1 int,c2 int);
insert into t1 values(1,1);
insert into t1 values(2,2);
insert into t1 values(3,3);
insert into t1 values(4,4);
insert into t1 values(5,5);
insert into t1 values(6,6);
查询语句为:
select * from t1 where c1=2;
该语句的执行过程,如果用语言描述可以描述成这样:
1)如果是第一次执行该步骤,则取得表的第一条记录;否则取得当前记录的下一条记录。如果记录已经扫描结束,则执行步骤4,否则执行步骤2。
2)判断该记录是否满足过滤条件c1=2,满足则执行步骤3,否则执行步骤1。
3)把该记录放到结果集中,执行步骤1。
4)将结果集返回给客户端。
实际上,数据库执行查询语句的过程也是类似的,下面是该查询语句的执行计划:
#RSET:[21, 1, 1];
#XFLT:[0, 0, 0]; EXPR0 = 2
#CSEK:[21, 1, 1]; INDEX33555545(T1), FULL_SCAN
该执行计划中出现的内容,在此做出解释:
1)CSEK(查找)类似于上文中描述的步骤1,方括号中的内容是执行该操作的评估代价,本文不作分析。“INDEX33555545(T1)”说明使用了T1表的聚集索引,“FULL_SCAN”表示对聚集索引INDEX33555545(T1)进行全扫描。
这里需要注意的是,达梦数据库中的表默认情况下是索引组织的。如果建表时指定了cluster primary key,那么数据以该clsuter primary key组织数据,否则以rowid组织数据。
2)XFLT(过滤)类似于上文中描述的步骤2,“EXPR0 = 2”是过滤条件。
3)RSET(结果集)类似于上文中描述的步骤3,用来存放符合条件的记录集。
我们可以看出,数据库的执行过程和我们用语言描述的步骤是一致的。
该查询语句完整的执行流程如下:
1)CSEK取得第一条记录(1,1)传给XFLT,将控制权传给XFLT。
2)XFLT发现该记录(1,1)不符合条件,将控制权传给CSEK。
3)CSEK取得下一条记录(2,2)传给XFLT,将控制权传给XFLT。
4)XFLT发现记录(2,2)符合条件,将该记录传给RSET,将控制权传给RSET。
5)RSET将记录(2,2)放入结果集,将控制权传给XFLT。
6)XFLT给控制权传给CSEK。
7)CSEK取得下一条(3,3)传给XFLT,将控制权传给XFLT。
8)XFLT发现该记录(3,3)不符合条件,将控制权传给CSEK。
9)CSEK取得下一条(4,4)传给XFLT,将控制权传给XFLT。
10)XFLT发现该记录(4,4)不符合条件,将控制权传给CSEK
11)CSEK取得下一条(5,5)传给XFLT,将控制权传给XFLT。
12)XFLT发现该记录(5,5)不符合条件,将控制权传给CSEK。
13)CSEK取得下一条(6,6)传给XFLT,将控制权传给XFLT。
14)XFLT发现该记录(6,6)不符合条件,将控制权传给CSEK。
15)CSEK发现描述操作已经结束,通知XFLT结束。将控制权传给XFLT。
16)XFLT得知查询操作结束,通知RSET结束。将控制权传给RSET。
17)RSET得知操作结束。
18)发送结果集(包含记录(2,2))给客户端。
2.如果表t1上的c1列有非唯一索引,如何执行呢?
表t1的定义以及数据和1中描述的一样。
创建索引:
create index it1c1 on t1(c1);
查询语句“select * from t1 where c1=2;”对应的执行计划为:
#RSET:[201, 2, 1];
#CSEK(SECOND):[201, 2, 1]; IT1C1(T1), INDEX_EQU_SEARCH
CSEK行的“SECOND”表示使用非聚集索引“IT1C1”,对该索引进行索引等值(INDEX_EQU_SEARCH)查找。
该执行计划的执行流程为:
1)CSEK使用c1=2查找非聚集索引,得到第一条c1=2的索引记录(2,rowid1)中的rowid1(为数值)。使用rowid1查找聚集索引得到对应的数据记录(2,2)传递给RSET,将控制权传给RSET。
2)RSET将记录(2,2)放入结果集,将控制权传给CSEK。(因为c1上的索引是非唯一的,所以可能出现两条以上的记录满足c1=2,所以需要将控制权传给CSEK)。
3)CSEK取得当前非聚集记录的下一条记录(3,rowid2),因为3!=2,所以扫描结束。将控制权传给RSET。(如果满足c1=2的记录数大于1条,需要继续传递记录给RSET,以此类推,直到遇到不满足c1=2的那条记录,结束操作。)
4)RSET得知操作结束。
5)发送结果集(包含记录(2,2))给客户端。
3.如果表t1上的c1列有唯一索引,如何执行呢?
首先删除c1列上的非唯一索引,然后在c1列上创建唯一索引:
drop index it1c1;
create unique index uit1c1 on t1(c1);
查询语句“select * from t1 where c1=2;”对应的执行计划为:
#RSET:[201, 2, 1];
#CSEK(SECOND):[201, 2, 1]; UIT1C1(T1), INDEX_EQU_SEARCH
该执行计划的执行流程为:
1)CSEK使用c1=2查找非聚集索引,得到c1=2的索引记录(2,rowid1)中的rowid1(为数值)。使用rowid1查找聚集索引得到对应的数据记录(2,2)传递给RSET,将控制权传给RSET。(当然,有人也许会问,如果没有记录满足c1=2怎么办呢?那么,此处什么记录都不传递给RSET,通知RSET查询操作结束,最后返回空集给客户端)。
2)RSET将记录(2,2)放入结果集,操作结束(因为是唯一索引,所以最多只有1条记录满足c1=2)。
3)发送结果集(包含记录(2,2))给客户端。
这里我们发现,例3使用了唯一索引,例2使用了非唯一索引。例3的执行速度大于例2的执行速度。
4.如何理解执行计划中的top n操作?
查询语句“select top 10 * from t1 where c1〉2;”对应的执行计划为:
#RSET:[21, 1, 1];
#XTOP:[0, 0, 0]; top_off(0), top_num(10)
#XFLT:[0, 0, 0]; EXPR1 〉 2
#CSEK:[21, 1, 1]; INDEX33555545(T1), FULL_SCAN
XTOP(取得前N条记录):将XFLT操作符传递来的记录放入到RSET(结果集)中,并判断记录数是否已经等于给定值10(语句中的top 10)。如果已经等于10,则查询已经执行成功,退出。否则将控制权限传给XFLT,继续执行。依次执行,直到取得10条记录,或者表CSEK操作已经查询结束(即符合条件的记录不满10条)。
5.如何理解执行计划中的order by操作?
查询语句“select top 10 * from t1 where c2〉2 order by c1;”对应的执行计划为:
#RSET:[21, 1, 1];
#XSORT:[0, 0, 0]; keys_num(1), is_distinct(FALSE)
#XFLT:[0, 0, 0]; EXPR1 〉 2
#CSEK:[21, 1, 1]; INDEX33555545(T1), FULL_SCAN
XSORT(对记录进行排序):将XFLT操作符传递来的记录插入到XSORT维护的临时空间中的合理位置,按c1进行有序排列。然后将控制权传给XFLT以取得下一条符合条件的记录。等处理完所有符合条件的记录。XSORT操作符才会将控制权限传给RSET。
6.是不是查询语句中一旦出现order by字句,执行计划中就会出现XSORT操作符?
不是。
比如,查询语句“select c1 from t1 order by c1;”对应的执行计划为:
#RSET:[0, 0, 0];
#CSEK:[0, 0, 0]; UIT1C1(T1), FULL_SCAN
从执行中我们可以看出,达梦直接对索引UIT1C1进行全索引扫描,对于得到的每一条记录不需要进行XSORT排序操作,直接放入RSET(结果集)中。因为索引UIT1C1本身就是按照c1进行排序的。
7.有文档说,对于语句“select max(c1) from t1”,可以在c1列上创建索引从而查询速度变快。那么在执行计划中是如何体现的呢?
查询语句“select max(c1) from t1”对应的执行计划:
#RSET:[0, 0, 0];
#XEVL:[0, 0, 0];
#FAGR:[0, 0, 0]; function_num(1)
在这个执行计划中,我们没有看到CSEK操作符。因为c1上存在索引UIT1C1,该索引叶子节点的最右端就是c1的最大值。FARG直接返回该最大值。语句“select min(c1) from t1;”、语句“select count(*) from t1;”的执行原理一样。XEVL是表达式计算,本文不进行讲解。
8.如果列上存在索引,如何理解中的group by操作?
查询语句“select c1,count(*) from t1 where c1〉=2 group by c1;”对应的执行计划为:
#RSET:[11, 1, 1];
#XEVL:[0, 0, 0];
#SAGR:[0, 0, 0]; group_by_num(1), function_num(1)
#CSEK:[11, 1, 1]; UIT1C1(T1), INDEX_GE_SEARCH
我们可以得到,CSEK使用了索引UIT1C1进行了范围查找。首先传递给SARG的是连续的c1=2的记录组,然后是c1=3的记录组,然后是c1=4的记录组,……
此处SARG的执行流程是
1)从CSEK取得一条c1=2记录,将计数加1,
2)从CSEK取得下一条记录,如果该记录满足c1,将计数+1。
3)重复执行步骤2,直到取得第一条不满足c1=2的记录,将(2,对应的计算)传递给XEVL,再传给RSET(结果集)。接着对c1=3的记录组执行同样的流程。依此类推,直到处理完所有符合条件的记录。
这里我们的分组函数是count(*),如果是其它的分组函数,处理过程类似。
9.如果列上不存在索引,如何理解中的group by操作?
查询语句“select c2,count(*) from t1 where c2〉=2 group by c2;”对应的执行计划为:
#RSET:[21, 1, 1];
#XEVL:[0, 0, 0];
#HAGR:[0, 0, 0]; group_by_num(1), function_num(1)
#XFLT:[0, 0, 0]; EXPR0 〉= 2
#CSEK:[21, 1, 1]; INDEX33555550(T1), FULL_SCAN
这里因为c2上没有索引,HARG的作用是HASH分组。
HARG的执行流程是:
1)从XFLT取得一条记录
2)记录的c1=m,如果在hash表中已经对应项,计数+1,如果不存在对应项,在创建一个新的hash项。
3)所有的符合过滤条件的记录处理完成之后,HARG才会将控制权限传给上层操作符,HARG每次向上层操作符传递一条(m,m对应的计数)。
这里我们的分组函数是count(*),如果是其它的分组函数,处理过程类似。
http://news.csdn.net/n/20070815/107516.html
yanglilibaobao收录,使用标签:数据库,时间:2007-8-7 9:51:31 | 相关网摘,我也收藏
关系数据库设计之时是要遵守一定的规则的。尤其是数据库设计范式现简单介绍1NF(第一范式),2NF(第二范式),3NF(第三范式)和BCNF,另有第四范式和第五范式留到以后再介绍。在你设计数据库之时,若能符合这几个范式,你就是数据库设计的高手。
第一范式(1NF):在关系模式R中的每一个具体关系r中,如果每个属性值都是不可再分的最小数据单位,则称R是第一范式的关系。例:如职工号,姓名,电话号码组成一个表(一个人可能有一个办公室电话和一个家里电话号码)规范成为1NF有三种方法:
一是重复存储职工号和姓名。这样,关键字只能是电话号码。
二是职工号为关键字,电话号码分为单位电话和住宅电话两个属性
三是职工号为关键字,但强制每条记录只能有一个电话号码。
以上三个方法,第一种方法最不可取,按实际情况选取后两种情况。
第二范式(2NF):如果关系模式R(U,F)中的所有非主属性都完全依赖于任意一个候选关键字,则称关系R 是属于第二范式的。
例:选课关系 SCI(SNO,CNO,GRADE,CREDIT)其中SNO为学号, CNO为课程号,GRADEGE 为成绩,CREDIT 为学分。由以上条件,关键字为组合关键字(SNO,CNO)
在应用中使用以上关系模式有以下问题:
a.数据冗余,假设同一门课由40个学生选修,学分就重复40次。
b.更新异常,若调整了某课程的学分,相应的元组CREDIT值都要更新,有可能会出现同一门课学分不同。
c.插入异常,如计划开新课,由于没人选修,没有学号关键字,只能等有人选修才能把课程和学分存入。
d.删除异常,若学生已经结业,从当前数据库删除选修记录。某些门课程新生尚未选修,则此门课程及学分记录无法保存。
原因:非关键字属性CREDIT仅函数依赖于CNO,也就是CREDIT部分依赖组合关键字(SNO,CNO)而不是完全依赖。
解决方法:分成两个关系模式 SC1(SNO,CNO,GRADE),C2(CNO,CREDIT)。新关系包括两个关系模式,它们之间通过SC1中的外关键字CNO相联系,需要时再进行自然联接,恢复了原来的关系
第三范式(3NF):如果关系模式R(U,F)中的所有非主属性对任何候选关键字都不存在传递信赖,则称关系R是属于第三范式的。
例:如S1(SNO,SNAME,DNO,DNAME,LOCATION)各属性分别代表学号,
姓名,所在系,系名称,系地址。
关键字SNO决定各个属性。由于是单个关键字,没有部分依赖的问题,肯定是2NF。但这关系肯定有大量的冗余,有关学生所在的几个属性DNO,DNAME,LOCATION将重复存储,插入,删除和修改时也将产生类似以上例的情况。
原因:关系中存在传递依赖造成的。即SNO -> DNO。而DNO -> SNO却不存在,DNO -> LOCATION, 因此关键辽 SNO 对 LOCATION 函数决定是通过传递依赖 SNO -> LOCATION 实现的。也就是说,SNO不直接决定非主属性LOCATION。
解决目地:每个关系模式中不能留有传递依赖。
解决方法:分为两个关系 S(SNO,SNAME,DNO),D(DNO,DNAME,LOCATION)
注意:关系S中不能没有外关键字DNO。否则两个关系之间失去联系。
BCNF:如果关系模式R(U,F)的所有属性(包括主属性和非主属性)都不传递依赖于R的任何候选关键字,那么称关系R是属于BCNF的。或是关系模式R,如果每个决定因素都包含关键字(而不是被关键字所包含),则RCNF的关系模式。
例:配件管理关系模式 WPE(WNO,PNO,ENO,QNT)分别表仓库号,配件号,职工号,数量。有以下条件
a.一个仓库有多个职工。
b.一个职工仅在一个仓库工作。
c.每个仓库里一种型号的配件由专人负责,但一个人可以管理几种配件。
d.同一种型号的配件可以分放在几个仓库中。
分析:由以上得 PNO 不能确定QNT,由组合属性(WNO,PNO)来决定,存在函数依赖(WNO,PNO) -> ENO。由于每个仓库里的一种配件由专人负责,而一个人可以管理几种配件,所以有组合属性(WNO,PNO)才能确定负责人,有(WNO,PNO)-> ENO。因为一个职工仅在一个仓库工作,有ENO -> WNO。由于每个仓库里的一种配件由专人负责,而一个职工仅在一个仓库工作,有(ENO,PNO)-> QNT。
找一下候选关键字,因为(WNO,PNO) -> QNT,(WNO,PNO)-> ENO ,因此(WNO,PNO)可以决定整个元组,是一个候选关键字。根据ENO->WNO,(ENO,PNO)->QNT,故(ENO,PNO)也能决定整个元组,为另一个候选关键字。属性ENO,WNO,PNO 均为主属性,只有一个非主属性QNT。它对任何一个候选关键字都是完全函数依赖的,并且是直接依赖,所以该关系模式是3NF。
分析一下主属性。因为ENO->WNO,主属性ENO是WNO的决定因素,但是它本身不是关键字,只是组合关键字的一部分。这就造成主属性WNO对另外一个候选关键字(ENO,PNO)的部分依赖,因为(ENO,PNO)-> ENO但反过来不成立,而P->WNO,故(ENO,PNO)-> WNO 也是传递依赖。
虽然没有非主属性对候选关键辽的传递依赖,但存在主属性对候选关键字的传递依赖,同样也会带来麻烦。如一个新职工分配到仓库工作,但暂时处于实习阶段,没有独立负责对某些配件的管理任务。由于缺少关键字的一部分PNO而无法插入到该关系中去。又如某个人改成不管配件了去负责安全,则在删除配件的同时该职工也会被删除。
解决办法:分成管理EP(ENO,PNO,QNT),关键字是(ENO,PNO)工作EW(ENO,WNO)其关键字是ENO
缺点:分解后函数依赖的保持性较差。如此例中,由于分解,函数依赖(WNO,PNO)-> ENO 丢失了, 因而对原来的语义有所破坏。没有体现出每个仓库里一种部件由专人负责。有可能出现一部件由两个人或两个以上的人来同时管理。因此,分解之后的关系模式降低了部分完整性约束。
一个关系分解成多个关系,要使得分解有意义,起码的要求是分解后不丢失原来的信息。这些信息不仅包括数据本身,而且包括由函数依赖所表示的数据之间的相互制约。进行分解的目标是达到更高一级的规范化程度,但是分解的同时必须考虑两个问题:无损联接性和保持函数依赖。有时往往不可能做到既有无损联接性,又完全保持函数依赖。需要根据需要进行权衡。
1NF直到BCNF的四种范式之间有如下关系:
BCNF包含了3NF包含2NF包含1NF
小结:
目地:规范化目的是使结构更合理,消除存储异常,使数据冗余尽量小,便于插入、删除和更新
原则:遵从概念单一化 "一事一地"原则,即一个关系模式描述一个实体或实体间的一种联系。规范的实质就是概念的单一化。
方法:将关系模式投影分解成两个或两个以上的关系模式。
要求:分解后的关系模式集合应当与原关系模式"等价",即经过自然联接可以恢复原关系而不丢失信息,并保持属性间合理的联系。
注意:一个关系模式结这分解可以得到不同关系模式集合,也就是说分解方法不是唯一的。最小冗余的要求必须以分解后的数据库能够表达原来数据库所有信息为前提来实现。其根本目标是节省存储空间,避免数据不一致性,提高对关系的操作效率,同时满足应用需求。实际上,并不一定要求全部模式都达到BCNF不可。有时故意保留部分冗余可能更方便数据查询。尤其对于那些更新频度不高,查询频度极高的数据库系统更是如此。
在关系数据库中,除了函数依赖之外还有多值依赖,联接依赖的问题,从而提出了第四范式,第五范式等更高一级的规范化要求。在此,以后再谈。
各位朋友,你看过后有何感想,其实,任何一本数据库基础理论的书都会讲这些东西,考虑到很多网友是半途出家,来做数据库。特找一本书大抄特抄一把,各位有什么问题,也别问我了,自已去找一本关系数据库理论的书去看吧,说不定,对各位大有帮助。说是说以上是基础理论的东西,请大家想想,你在做数据库设计的时候有没有考虑过遵过以上几个范式呢,有没有在数据库设计做得不好之时,想一想,对比以上所讲,到底是违反了第几个范式呢?
我见过的数据库设计,很少有人做到很符合以上几个范式的,一般说来,第一范式大家都可以遵守,完全遵守第二第三范式的人很少了,遵守的人一定就是设计数据库的高手了,BCNF的范式出现机会较少,而且会破坏完整性,你可以在做设计之时不考虑它,当然在ORACLE中可通过触发器解决其缺点。以后我们共同做设计之时,也希望大家遵守以上几个范式。
http://database.ccidnet.com/art/1105/20070807/1168921_1.html
yanglilibaobao收录,使用标签:数据库,时间:2007-8-7 9:48:16 | 相关网摘,我也收藏
SQL Server 2000 和SQL Server 2005都允许用户恢复整个数据库和选择性的恢复单个的文件或者文件组。这在当有一个特定的文件或者文件组出现问题时,你想不用恢复整个数据库就能恢复失败的文件时是很有用的。而当这些文件或者文件组非常小或者只是数据库的一小部分时就显得尤其有用
这有一个具体例子:如果你有一个单个的出现问题的文件。这个文件有50MB大小,而你的整个数据库运行着大约有几十亿的字节,这样的话如果能恢复单个失败文件的话就显的非常有意义。这样的事情发生的一个情景是当文件或者文件组在单独的驱动器上,而驱动器出现了问题。通常,仅仅恢复单个文件或者文件组会使总的停止时间缩短,因为它明显减少了需要恢复的总的数据量。
现在,为什么你不选择这么做呢?这有一些原因:
你需要有事务日志备份。如果你想从备份中恢复一个文件或者文件组,你同时也需要恢复与它们一起创建的事务记录备份,从而使整个数据库能够处于一个一致的状态。在SQL Server 2000 和 2005中,你需要使用Full Recovery或者Bulk-Logged Recovery模式(也就是说不是Simple Recovery)来使它成为可能。我应该指出SQL Server的实现者们并没有尽他们的努力来完成判断从上一次备份一个文件或者文件组是否已经被修改了的功能。如果没有被改变,那么事务记录是没有什么必要的。但是,总的来说,还是需要事务记录备份的,如果你现在还没有一个备份事务记录的恢复或者备份计划,那么现在就创建一个吧。
在要恢复的文件或者文件组中的表格与数据库中其他的表格之间的数据不一致性成为需要考虑的一个问题。如果你有相互之间依赖的表格,并且这些表格没有被存储在相同的物理文件或者文件组中(这有时是不可避免的),仅仅恢复一个文件或者文件组可能会导致它与数据库其他部分不同步。例如,你有一个表格被另一个表格用JOIN关联,并且这个JOIN使用了一个视图和存储过程,这时仅仅恢复一个而不恢复另一个可能会有问题。
当你在数据库中只有一个文件组。如果你的所有的数据仅仅存储在一个文件或者文件组中,并且它又不是一个特别大的数据库时,会发生什么事情呢?那时恢复一个文件或者文件组的努力就变的没有什么意义了。
选择性的恢复文件或者文件组的主要原因是当恢复数据库很大,以至于恢复整个数据库的代价很大的时候,使得对数据库的局部损坏的恢复成为可能。在一个非常小的轻量级的数据库里,和nonproduction系统中,或者数据库中只有一个文件组的时候,实现选择性的恢复功能就显的没有太大的意义,因为这时恢复整个数据库和恢复单个的文件或者文件组没有什么太大的区别。
我发现大多数时候当人们想使用文件或者文件组恢复时,他们实际上是想把一个特定的表格恢复到先前的一个点的时刻的状态。这在SQL Server中不是一个显式支特的特性,但是存在这么做的方法,倘若你不介意由于采用这种方法而需要手工的来管理可能产生的不一致(就想上面#2所说的)。如果你手边就有一个数据库备份的话,你可以简单的恢复那个备份,仅仅把它看作是相同数据库的不同名字的实例。接着,通过事务记录把数据库向前滚动到指定的点(如果需要这样做的话),然后手工的把当前的数据库拷贝到目标数据库中。
我自己已经实验过这种方法几次,但是仅仅只有一个表格没有与相同数据库中的其他的表格有很大的关联。我的例子涉及了一个包含了留言版系统的聊天网站。我不得不经常恢复一些在留言版上被意外删除的消息,这些是自包含的:从留言版表格的数据产生的唯一的JOINs是外部的而不是内部的。因此,我可以随意的更新表格因为我知道我不会让那个表格与其他表格不同步的。
在SQL Server 2000和它更高的版本中,当你做一个RESTORE操作的时候你可以使用PARTIAL子句,从而使仅仅需要的文件组数据被恢复。这作为一个时间和空间上的节约开销的措施是非常有用的:你不需要进行繁重的恢复所有数据的工作,而仅仅只需要对一个表格进行操作。而且很可能也没有足够的空间来进行完全的恢复操作。
http://searchdatabase.techtarget.com.cn/sqlserver/179/3473679.shtml
yanglilibaobao收录,使用标签:数据库,时间:2007-8-7 9:45:42 | 相关网摘,我也收藏
根据甲骨文以往几个版本的发行经验,发布新版Oracle数据库的频率在3年左右,以此类推,Oracle 11g应该在07年年底发布,选择在7月份发布,不知道是否和代号Katmai的SQL Server 2008有关,因为目前还处于测试阶段的SQL Server 2008将在08年2月发布,业内用Oracle 10g和SQL Server 2003做比较也曾一度惹恼了甲骨文……
未来数据库市场竞争的焦点已不再局限于传统的数据库,新的应用不断赋予数据库新的生命力。
数据库的冰火较量
甲骨文数据库Oracle 11g坚持对网格的支持,但网格计算在中国有点冷。
Gartner公司最近公布了2006全球关系型数据库管理系统市场份额报告,甲骨文的市场份额为47.1%,是全球排名第一的数据库厂商。市场份额继续保持比最接近的两个竞争对手市场份额之和还高的局面。
2007年7月12日,甲骨文公司在美国纽约宣布推出数据库Oracle 11g,这是Oracle数据库的最新版本。甲骨文介绍说,Oracle 11g有400多项功能,经过了1500万个小时的测试,开发工作量达到了3.6万人/月。
有意思的是,根据甲骨文以往几个版本的发行经验,发布新版Oracle数据库的频率在3年左右,以此类推,Oracle 11g应该在07年年底发布,选择在7月份发布,不知道是否和代号Katmai的SQL Server 2008有关,因为目前还处于测试阶段的SQL Server 2008将在08年2月发布,业内用Oracle 10g和SQL Server 2003做比较也曾一度惹恼了甲骨文。
XML显高温
当XML面世之时,也许没有哪个数据库厂商会对这种技术给以足够的关注,然而在今天,XML已经开始对数据存储产生巨大的影响。到现在,这种可扩展标记语言已是各种数据,特别是文档的首选格式,国际主流的数据库厂商们自然也随行就市,全都推出了兼容传统关系型数据与XML数据混合应用的新一代数据库产品。
XML在数据存储方面有一个明显的优点,那就是可以直接将逻辑关系编写在XML文件当中。一个时髦的XML数据库应该提供哪些功能呢?归纳起来应该有四个基本功能:使用、存储、查询和产生XML的能力。
在Oracle 10g中,曾被人们津津乐道的最重要的改进是增加了对XML schema(XML语法)转换的支持,它允许用户通过将现有的数据映射为新的schema来实现XML schema转换。而不必把所有XML数据输出后再重新输入进去,其它事情将由数据库自动完成。
在Oracle 11g中, XML DB的性能又获得很大提高,XML DB是Oracle数据库的一个组件,客户可以以本机方式存储和操作XML数据。11g增加了对二进制XML数据的支持,现在客户可以选择适合自己特定应用及性能需求的XML存储选项。
当然,不仅仅是甲骨文看好XML,为吸引Oracle用户,IBM公司DB2 9打XML旗号直接把XML作为其新产品的最大卖点;微软和Sybase也宣称它们的产品也可以实现高性能XML存储与查询,使现有应用更好地与XML并存。
网格计算有点冷
新的Oracle 11g仍使用g(Grid)作为后缀,以代表这是一个包含了网格技术基础的数据库。甲骨文称,Oracle 11g能更方便地在低成本服务器和存储设备组成的网格上运行。不过,目前仅有IBM DB2数据库也支持网格计算技术。
网格计算将多个服务器和存储器当作一台大型电脑协调使用,使它们在高速网络上动态地共享计算机资源,以满足不断变化的计算需求。简而言之,即将多个服务器和存储器当作一台主机协调使用。网格计算被广泛视为未来的计算方式。
尽管微软对网格计算的兴趣也很浓厚,承诺要让Windows能够更好地适应高数据强度的计算网格。但微软除了在内部研究之外,似乎一直在这个话题上非常沉默。对于数据库中网格计算,微软和Sybase方面表示,网格应用在技术上还需解决一些问题(如:多节点性能问题) ,网格技术要成为商业应用的主流,还需要几年时间在应用和产品上进一步完善。
不容乐观的是,在咨询公司Quocirca发布的调查显示,我国网格实际采用率仍然偏低,总体网格指数在15个被调查国家中只排第9位,处于中下游,甲骨文表示,中国用户可能对网格的价值还没有真正接受。很多中国企业有一种观望的心态,觉得应用网格存在风险。
11g安全了吗?
有业内人士曾表示,Oracle 10g只能算是一个过渡版本。因为06年,下一代安全软件机构NGSS对微软SQL Server和Oracle数据库做了一个弱点对比,结果表明Oracle的数据库产品存在更多的弱点。
NGSS的研究人员称,Oracle有233个缺陷点,而SQL Server只有59个。这些缺陷在SQL Server7、2000以及2005中,在Oracle8、9以及10g版本中被报道,并被修复。分析机构ESG也发布调查报告表示,在安全性方面微软击败了甲骨文,似乎甲骨文数据库“无懈可击”的安全神话已不复存在。
针对那些不断对甲骨文安全性能表示批评的专家,甲骨文终于开始猛烈还击。2006年底,甲骨文全球技术事业部的安全经理Eric Maurice在公司的博客上表示,甲骨文在开发和安全方面的技术水平居业界领先位置。与微软数据库的安全性能比较,不过是别有用心的人在玩数字游戏,甲骨文不会让外部的压力改变其既定的安全策略。
到现在,起码可以从资料上看到,Oracle的安全认证获得最高认证级别的ISO标准认证,而SQL Server并没有获得什么安全认证。从这方面证明了Oracle的安全性不应该被受到如此指责。
从甲骨文此次推出的11g可以看到,在安全方面除了10g已经存在的数据阀门和加密外,11g又增加了四项安全功能,即安全备份、非对称数据的授权安全检索、监控、管理和报警。
Oracle 11g数据库增强了Oracle透明数据加密功能,将这种功能扩展到了卷级加密之外。11g还增加了表空间加密功能,可用来加密整个表、索引和所存储的其它数据。存储在数据库中的大型对象也可以加密。
看来甲骨文很注重11g在安全上的表现,闪回交易技术可以撤销错误交易以及任何相关交易,并行备份和恢复功能。另外,一种新的顾问软件—数据恢复顾问,可自动调查问题,智能地确定恢复计划并处理多种故障情况。
Oracle 11g的Oracle Data Guard组件可用于对生产数据库的报告、备份、测试和“滚动”升级。通过将工作量从生产系统卸载到备用系统,并组成一个更经济的灾难恢复解决方案。
也许正是在安全性上的增强,才使得甲骨文公司数据库服务器技术高级副总裁Andy Mendelsohn自信地表示:“Oracle 11g真正克服了挑战并实现了真正的创新。”
一个疯狂的发烧友在自家的车库中改造了小型IDC,并利用Sun Enterprise 220R Server架设了Oracle 10g数据库
增强信息生命周期管理和存储管理能力:引入了更多的自助式管理和自动化功能;
◆透明的加密:Oracle将这种功能扩展到了卷级加密之外;
◆提高信息可用性:免受计划停机和意外宕机影响;
◆更快的XML:通过XML DB组件,客户可以本机方式存储和操作XML数据;
◆增强了自助式管理和自动化能力:增加了自动SQL和存储器微调等管理功能;
◆增强了应用开发能力:提供多种开发工具供开发人员选择,包括Java实时编译器。
开源数据库成熟时
以MySQL、PostgreSQL为代表的开源数据库系统,已成为取代闭源数据库的一种颇具吸引力的选择。
成熟的开源数据库,让Oracle这样的闭源厂商难以腾飞
知名的网络游戏The Matrix Online(骇客帝国Online),每天有数万名网上玩家同时在线,为了能够支撑庞大的在线游戏玩家同时进行游戏,Sony Online Entertainment(索尼在线娱乐公司,以下简称SOE)需要密集使用数据库。
以往SOE会使用大量的Oracle RAC集群提供服务,但是由于Oracle数据库的授权证十分昂贵及欠缺弹性,加上公司需要更多额外数据库。所以从2005年开始,该公司就开始寻求既具有较低总拥有成本,又具有较好灵活性的开源数据库以取代Oracle数据库。
踢开Oracle
SOE对数据库的需求相当可观,其数据库应用程序是关键任务应用程序的最恰当诠释:每天有数十万在线玩家在玩SOE的游戏,而每款游戏都是一个数据库及其密集的应用程序。
事实上,SOE在应用开放源代码的应用上有很长历史,曾经就使用过Linux、Tomcat、Apache、Hibernate架设系统,此次的开源抉择,SOE更注重寻觅能够利用其宝贵资产(内部能够熟练使用Oracle的数据库人才)的方法,在选择开源数据库时,列出了四项标准:
1、能够充分发挥其现有数据库管理员和开发人员的潜能;
2、易于将SOE现有的Oracle应用程序迁移到新的数据库;
3、能够提供商用级别质量可靠性,包括备份和恢复标准,以支持关键任务应用程序;
4、可扩展,具有高性能。
在选择开源数据库之前,SOE需要使用许多Oracle 9i RAC群集。另外,SOE在其后台运营中部署了数据库。与如今的很多企业一样,SOE希望开源软件能够提供应对这些业务挑战的解决方案。
根据SOE对不同开放源码数据库的评估,他们选择了兼容Oracle数据库的EnterpriseDB,因为SOE有八成以上的特定Oracle应用程式,可以在极少、甚至无需修改的情况下在EnterpriseDB执行,以现有开支比较,利用EnterpriseDB后,每款线上游戏的整体拥有成本可降低80%,每年节省总额过百万,在2007至2008年度,SOE将使用数百台EnterpriseDB数据库集群服务器。
开源更有前途
使用Oracle数据库的企业一般都会对开源数据库感兴趣,主要有三个方面原因:首先,通过部署开源数据库,这些企业可以显著降低数据库的总拥有成本(TCO),有时降幅甚至高达90%;其次,他们通常可以从其他厂商获得更大的许可灵活性以及业务便利;最后,这些企业会发现其他厂商更渴望为他们提供出色技术。
对于开源数据库,企业多少也会有些担心,因为许多企业仅限于将开源数据库用于一些简单的应用程序,如一些网站的支持应用。因为这些企业普遍认为开源数据库可能不够稳定,可靠性或者可扩展性不够高,因而无法满足他们的关键任务应用需求。另外,企业可能还觉得更改数据库的代价,例如,与应用程序重新编码,人员重新培训相关的成本,可能会超出预期的节省目标。
MySQL、FireBird、EnterpriseDB和Postgre(PostgreSQL的前身)都是非常健壮的开源数据库,而EnterpriseDB又是基于PostgreSQL进行开发的,不仅保留了PostgreSQL的稳定性,而且可实现50%的速度增长,系统可以自动监测是否有补丁程序存在,大大减轻管理员的工作。
近几年来,美国一些大企业纷纷采用开放源码数据库,它们往往在总部采用商业数据库,而在分支机构的Linux服务器上采用开源产品。这些数据库除了费用便宜,还各有独到之处。与商业化产品相比,开源数据库结构简单,但功能不简单,读取操作快捷,易管理,甚至不需要全职的管理员。由此吸引了像Cisco、Yahoo这样的大公司,以及众多的中小企业。而在国内,我们熟悉的新浪、网易等大型门户网站也是开源数据库的使用者。
http://searchdatabase.techtarget.com.cn/analysis/215/3475715.shtml
yanglilibaobao收录,使用标签:Java,时间:2007-8-6 17:01:17 | 相关网摘,我也收藏
经常看到不少人抱怨Java EE/J2EE中配置太复杂,烦琐,不简单易学,其实所谓简单易学是取决于你是否有OO思维方式。
分层架构是面向对象OO在企业软件中应用的标志,目前一个企业软件系统包括表现层、业务层和持久层,那么分层架构和OO关系是如何?
表现层的界面表单中通常是一些离散数据,也就是单个字段数据,通过Struts等框架提供ActionForm以及标签库,将这些单个字段数据封装起来和业务层的Domain Model进行了映射,因此,表现层的主要编程工作就是映射配置。
持久层是将Domain Model对象保存到数据库中,过去使用JDBC,我们要逐个打开这些Model对象,然后每个字段逐个 保存到数据库中,如果说表现层框架是实现离散数据封装,那么持久层实现的是反方向:拆封。Hibernate是一个持久层O/R mapping框架, 也就是在对象和关系数据库之间进行映射的框架,EJB的CMP也是类似道理,因此,持久层的主要编程工作也是映射配置。
表现层和持久层这种配置工作就如同打包邮寄一样:你首先要将你的单件用一个箱子包装起来,达到目的地,这个箱子被打开,单件被逐步取出。表现层和持久层这样做的目的是保证中间业务层完全面向对象,保证业务层完全是和一个个对象模型打交道。
在一个真正面向对象的系统中,表现层和持久层是为了将非对象化的数据转为对象。因此,在先进的JavaEE/J2EE架构中,表现层和持久层的主要工作就是配置工作,而且主要是映射mapping的配置。
下面的问题就是:如何解决映射配置简单而且易用,如果拥有正确的指导配置的思维,那么配置工作就容易简单多, 否则,就倍感配置复杂。 那些感觉Java配置复杂的人其实他并没有完整的OO思维。为什么这么说呢?以ORM(Hibernate)配置简易方式说明:
配置的简要之道
首先,配置是映射XML配置,顾名思义,也就是在两者之间做协调,牵线搭桥,说白了,就是做红娘,但和做红娘又有些区别,做红娘可以要求双方做些改变,互相迁就,但是做映射配置,则不能这样,因为那样做就可能做出和需求要求不一样的东西。
配置的简要之道就是:围绕对象模型进行配置;而不是围绕数据表进行配置。
以持久层映射配置来说:存在Domain Model对象和关系数据表,如果感觉在两者之间配置映射很困难,双方做些改变,但是有可能 需求不答应,你一旦为协调而作出的改变可能偏离需求实现的目标,最后作出的系统面貌全非,根本不是客户所需要的。
那么怎么办?很显然,紧扣需求,反映需求的那一方坚决不要变动,那么Domain Model和关系数据表哪一方反映需求呢?按照OO分析,当然 是Domain Model,Model对象我们是依据Evans Model等模型驱动设计MDD概念设计出来,他们是需求的代表。
很显然,我们的映射配置必须顺着Model对象这个思维来配,对于名词式的Model,关联无外乎是其主要关系,当然还有继承,因此,象Hibernate 这些映射配置语法也是面向这些主要对象关系的。
表现层配置也是同样的道理,需要将Domain Model配置成界面表单,在实际中,我们有可能采取的是通过界面收集需求,因此,这个映射配置过程也是考验Model对象是否提炼正确与否,有可能发现Model不能实现一些界面需求功能,这时反过来必须修改我们的Model,而不是仅仅在表现层这个技术层面做些补救措施就糊弄过去。
Java EE/J2EE系统开发过程 敏捷的迭代是必然的。没有一个天才能够一步到位提炼出兼顾界面和数据表以及需求的统一模型出来。
总之,完成一个真正面向对象的Java EE/J2EE系统,必须抓住领域建模和具体框架熟练配置两点,只有这样才能保证Java项目成功实施。最关键的是提炼出反映出业务系统的领域模型:Domain Model,完成业务建模后,就是依赖Struts/Hibernate等配置分配将Model 映射到界面和数据库,其实就是将业务模型移植到计算机领域并能够正确运行。
高聚合和低关联
如果一个系统都被设计成相互没有任何不包含的单个对象,很显然是不能正确反映实际需求的,万事万物都是有其部分组成的,例如窗户由玻璃和框架组成,人是由胳膊 腿等身体部分组成,现实世界中,事物之间总是存在关系,聚合和组成是最常见的。
例如订单,一个订单Orders中由客户名称和地址,订购的产品品种和数量,客户名称和地址我们可以抽象为Customer来代表,产品我们使用Product来代表,由于一个订单中可能订购了多个产品,很显然,一个订单对象中应该有多个Product对象,而且每个Product的数量不一样,我们将Product和其数量再抽象包装成OrderLine订单条目对象,这样,订单中包含多个订单条目,而且订单条目只有依赖某个订单,是其组成部分,是一种强聚合关系,不是普通的聚合或关联关系。而Customer和Order之间是一种聚合关系,如果订单没有客户信息,就不成为订单了。
下面再以用户User这个对象为例,用户User可能拥有很多动态属性,一些属性需要运行时动态确定,用户和动态属性是一个整体和部分的聚合关系;每个用户都必然属于某个部门,因此,用户和部门属性对象之间也是一个整体和部分的聚合关系,这两种聚合关系不同之处在于:前者一个用户可能有多个动态属性,是1:N关系;部门Dept和用户User之间是1:N关系,一个部门中可能有多个用户,反过来说,对于用户User来说:它和部门Dept之间是N:1关系。
通过以上建模过程,我们基本搞清楚两件事:这个领域中存在哪些模型对象?按照Evans的DDD理论,哪些是实体,哪些是值对象;然后我们必须搞清楚那些聚合关系,他们是整体部分的关系,用来共同组成一个完整对象的。
持久层Hibernate聚合实现
在持久层我们需要做的主要工作就是将上述Domain Model 进行持久化映射配置,以User为例,User是一个实体,我们配置User.hbm.xml如下:
〈hibernate-mapping〉
〈class name="sample.model.User" table="testuser"〉
〈id name="userId" type="java.lang.String" 〉
〈generator class="assigned"/〉
〈/id〉
〈property name="username" type="java.lang.String"〉
〈column name="name" /〉
〈/property〉
〈!--表示和部门Dept之间是一种多对一关系 --〉
〈many-to-one cascade="save-update" name="dept"
class="sample.model.Dept" column="categoryId" /〉
〈!-- 表示和用户属性UserPropperty之间是一种1对多关系--〉
〈bag name="userProps" inverse="true" cascade="all" 〉
〈key column="userId" /〉
〈one-to-many class="sample.model.UserProperty" /〉
〈/bag〉
〈/class〉
〈/hibernate-mapping〉
在User的映射配置文件中,我们很自然地表达了上节Model之间的聚合关系,通过Hibernate配置,我们将模型对象之间的关系可以持久化保存到数据库中了,也就是可以永久维持这种关系,实际上,现实世界中也是这样的,部分和整体的关系是一直存在,除非这个整体这个对象不存在,而且修改部分对象内部值,必须通过整体这个对象。
在user配置中,我们并没有去做任何关系数据表testuser的设计和设定,因为我们知道,当User.hbm.xml配置完成后,这个J2EE系统部署发布到J2EE容器中时,Hibernate会根据这个配置自动创建数据表testuser,数据表的建立已经是一个部署调试阶段的、技术层面的具体工作。
Hibernate重要的父子关系
Hibernate在处理User和UserProperty这样一对多的父子关系时,具体实现起来要有一些具体细节必须注意,而且Hibernate2和Hibernate3两个版本是不一样的:
当我们需要只通过一句话session.save(user)或session.update(user)就能完成User和它其中多个Userproperty都能自动保存或更新时(必须指定cascade="all" 或save-update),尤其是update(user)更新时,其子集合userProps属性中可能有一些Userproperty是修改过的,一些Userproperty则是新增的,对于新增要使用insert语句;而对于修改则使用update语句,当我们笼统地调用一句update(user)时,那么Hibernate是如何判断这个user中子集合中哪些是修改?哪些是新增的?
Hibernate是通过主键来判断的,也就是说,通过UserProperty的主键来判断该对象是修改?还是新增。最关键的是:这个主键必须由Hibernate自动产生,如果你想自己指定子对象UserProperty主键,那么就有可能很多麻烦,这个麻烦是出其的麻烦,无法判断具体原因。所以,在简单方便的道路上迈错一步就是万丈深渊。下面是UserProperty的映射配置:
〈hibernate-mapping〉
〈class name="sample.model.UserProperty" table="userprops"〉
〈id name="propId" type="java.lang.String" 〉
〈generator class="uuid.hex"/〉〈!-- 不能为assigned--〉
〈/id〉
〈property name="name" /〉
〈property name="value" /〉
〈!-- 为提升性能而设定 --〉
〈many-to-one name="user" column="userId" not-null="true"/〉
〈/class〉
〈/hibernate-mapping〉
注意:以上配置只适合Hibernate 3.0以上版本,如果是Hibernate 2,那么必须在 :
〈id name="propId" type="java.lang.String" 〉
中加入unsaved-value="null",而且这个值是null还是0或-1,取决你的主键类型:
〈id name="propId" type="java.lang.String" unsaved-value="null"〉
是不是感觉Hibernate2太麻烦了!在Hibernate3中,就没有这个规定了,所以,如果当初使用Hibernate2来实现J2EE的oo简洁实现之道,还存在技术上的困难和难点。
Hibernate2和Hibernate3在处理父子关系上,还有一个不同就是lazy设定上:Hibernat2缺省lazy是false,当通过load将User获取以后,在session关闭以后,你可以直接通过user.getUserProps()方法获得其中子集合;而Hibernate3则不行了,缺省lazy是true,在session关闭情况下,只有两种方式获得子集合:
1. Open session in view,也就是在表现层一直打开持久层的session,这不但违背分层不干扰原则,而且造成数据库连接一直打开,一旦出错,有可能造成内存泄漏死机等问题。
2.在load父对象User时,调用Hibernate.initialize(user.getUserProps());强行装载所有的子对象,这样问题是:我们再也无法通过简单一句load生成父对象User及其所有内部部分,而无须照顾其内部关系。
板桥实践中总结方法是:根据当初EJB CMP的读取模式,采取JDBC来读取整个User及其部分子集合,缺点也是必须在Dao语句中打开User(破坏封装),根据其内部结构从数据库中获取数据,这样的好处是:我们可以使用统一的Hibernate模板来进行任何一个模型的持久化(不必为每个模型写一套DAO实现类),而无须关心其内部结构了。
注意:Spring+Hibernate采取的是Open session in view方案,这也是这种架构在系统复杂时发生性能问题一个原因,J道性能板块有多个这样的求救贴。
使用Hibernate映射配置另外一个注意点就是:使用双向关系可以提高性能,但是Evans DDD告诉我们,建模时尽量搞 单向关系,不要用双向,这两者有矛盾之处,实际中,我们如果使用Hibernate作为持久层框架,那么就采取双向,性能很重要啊,否则后果很严重,这种设计和性能不匹配也是目前面向对象领域需要解决的另外一个问题。
通过在子对象UserPropery配置中引入many-to-one ,然后在父对象User配置中规定inverse="true" 来实现双向,Hibernate会通过和insert或update一条SQL语句完成关系设定。
表现层Struts聚合实现
前面我们完成了Hibernate的映射配置,下面是表现层的映射配置,这是使用标签库来实现,我们使用Struts的标签库来实现:在界面主要实现下图效果:
当进行用户User资料增删改查时,需要一个如图录入页面,部门是通过下来菜单选择,用户属性UserPropery是通过一行行属性名称和属性值输入的,主要是在user.jsp中完成:
〈html:form action="/userSaveAction.do" method='post'〉
〈html:hidden property="action" /〉
〈!-- 下拉菜单选择部门,通过使用Struts的Action串联,产生deptListForm新ActionForm--〉
〈html:select property="dept.deptId" 〉
〈logic:notEmpty name="deptListForm" 〉
〈html:optionsCollection name="deptListForm" property="list" value="deptId" label="name"/〉
〈/logic:notEmpty〉
〈/html:select〉
〈br〉
UserId:〈html:text property="userId" /〉
〈br〉
Username:〈html:text property="username" /〉
〈table〉
〈tr〉〈td〉属性Id〈/td〉〈td〉属性名称〈/td〉〈td〉属性值〈/td〉〈/tr〉
〈tr〉〈td〉
〈html:hidden property="userProp[0].propId" /〉
〈/td〉〈td〉
〈html:text property="userProp[0].name" /〉
〈/td〉〈td〉
〈html:text property="userProp[0].value" /〉
〈/td〉〈/tr〉
〈tr〉〈td〉
〈html:hidden property="userProp[1].propId" /〉
〈/td〉〈td〉
〈html:text property="userProp[1].name" /〉
〈/td〉〈td〉
〈html:text property="userProp[1].value" /〉
〈/td〉〈/tr〉
〈tr〉〈td〉
〈html:hidden property="userProp[2].propId" /〉
〈/td〉〈td〉
〈html:text property="userProp[2].name" /〉
〈/td〉〈td〉
〈html:text property="userProp[2].value" /〉
〈/td〉〈/tr〉
〈/table〉
〈br〉〈input type='submit' value='submit'〉〈/input〉
〈/html:form〉
相应的UserActionForm和User Model内容差不多,不同之处:为接受多个动态属性的输入,需要设定一个特定的方法:
public class UserForm extends ModelForm {
.....
public UserProperty getUserProp(int index) {
return (UserProperty)((List)userProps).get(index);
}
.....
}
增删改查和批量查询根据JdonFramework的简化可迅速配置实现,这里不再描述,整个项目的代码结果如下图:也就是10个类左右,而且都是和业务有关,简要,扣主题,整个案例代码是免费自由下载,作为JdonFramework应用源码下载之一的sample。
总结
一个真正面向对象的JavaEE或J2EE系统,应该是一个围绕领域模型的多层架构,以面向对象OO思维进行领域模型提炼和重构,继续以OO思维进行表现层和持久层的配置实现,才能寻找到一条Java系统快速有效高质量的解决之道。
http://www.jdon.com/artichect/javaee.html
yanglilibaobao收录,使用标签:项目管理,时间:2007-8-6 16:42:07 | 相关网摘,我也收藏
北京外企人力资源服务有限公司(FESCO)最新调查统计显示,今年上半年外企职位日语软件开发等冷门专业的中高端人才短缺,而初级秘书、助理、会计、人力资源等大众行业人才需求仍然很旺,占外企人才市场需求的62%。
随着北京外包业经济发展规划的力度加大以及相应政策的调整,IT外包已经成为北京的朝阳产业,一些外资公司纷纷在京建立IT的外包基地或发展外包产业,测试工程师、日语软件工程师、程序员等职位出现较大的人才需求潜力。其中,比较突出的人才短缺出现在专业软件开发。
中低端职位需求仍然是目前活跃在整个外企人才招聘市场的主力职位,初级秘书、助理、会计等大众行业需求占整体市场需求的62%;市场、销售类人才占11%;日语专业需求占7.4%;IT/通讯类占7%;工业类占5.4%。
http://tech.sina.com.cn/it/2007-08-06/08301658051.shtml
yanglilibaobao收录,使用标签:.NET,时间:2007-8-6 16:41:05 | 相关网摘,我也收藏
分部方法的语法
在看C#语言的What's New时,突然发现新特性列表的最后,多出了一个“Partial Method Definitions ”,但并不像其他新特性一样有超链接链接到其说明。上网搜索了一下,关于分部类型的信息非常少。尤其是中文信息,只有CSDN的 周融 在其《C# 3.5 语言在 Visual Studio Orcas Beta 1 上的新增功能(二) 》一文的最后提到了这个分部方法,但没有进一步说明。英文技术文章中,倒是有两篇不错的:http: //blogs.msdn.com/wesdyer/archive/2007/05/23/in-case-you-haven-t-heard.aspx 和 http://community.bartdesmet.net/blogs/bart/archive/2007/07/28/c-3-0-partial -methods-what-why-and-how.aspx.
又仔细看了一下MSDN Library for Visual Studio 2008 Beta 2,终于对这个语言特性有所了解,在这里介绍一下,希望对大家有所帮助。
分部方法的定义和分部类型类似,只需在方法定义前添加partial关键字。但分部方法只能拆分成两个部分——一部分是定义声明(Definition Declaration),另一部分是实现声明(Implement Declaration)。其中定义声明看上去和抽象方法类似:
partial class CA
{
// ……
private void partial M(); // 定义声明|
而实现声明看上去和普通方法类似:
private void partial M() // 实现声明
{
// 方法体
}
在调用分部方法时,和调用其他方法一样:
CA a = new CA();
a.M();
只是,如果只有定义声明而没有编写实现声明,则编译器不会发射(Emit)该方法和调用该方法的语句的元数据与IL代码。换言之,如果没有编写实现声明,则编译得到的程序集中,CA类型里并没有M这个方法。
使用分部方法的注意事项
分部方法的语法非常简单,但有一些事项要注意。
如果没有写实现声明,则不会发射方法调用代码,也不会对参数进行求值。因此,对于下面的例子:
class CA { partial void M(int i); static void Main() { CA a = new CA(); int i = 0; a.M(i++); } }
分部方法M只有定义声明,没有实现声明,因此也不会发射调用该方法的代码:a.M(i++),因此也不会对i++进行求值。所以最终i的值依然是0.但如果为M编写了实现声明,则a.M(i++)的代码会被编译到最终的程序集中,同时参数也被求值,i的值将被变为1.
分部方法只能出现在分部类中。
分部方法必须是私有(private)的,并且返回值类型必须是void.
分部方法可以带有参数,并且其参数可以带有this、params和ref修饰符,但不能带有out修饰符。
分部方法不可以是虚拟(virtual)的。
分部方法不可以是外部(extern)的。
分部方法可以是静态(static)的,也可以是不安全(unsafe)的。
分部方法可以是泛型方法,泛型约束必须放置在定义声明中,但也可以在事先声明中重复说明。在定义声明和实现声明中,类型参数和类型参数的名字不一定必须一致。
不能将分部方法封装到一个委托中。
分部方法的应用场景
分部方法和分部类型的初衷是类似的,一方面可以使得不同的开发者能够同时编写一个类型的不同部分,另一方面可以分离自动生成的代码和用户手写的代码。和分部类型一样,分部方法也会在编译初期被合并成一个方法定义。猜测:从微软的角度来看,第二个“初衷”可能才是真正的初衷。
由此,分部方法有如下几个应用场景:(场景1 出自In Case You Haven't Heard这篇文章「http://blogs.msdn.com/wesdyer/archive/2007/05/23/in-case-you- haven-t-heard.aspx」),场景2 出自Visual Studio 2008的Linq to SQL技术,而场景3 则是Anders Liu自已臆想出来的。
场景1 轻量级事件处理
有的时候,自动生成的代码需要事件这类语言构造来通知用户对某些操作进行处理,但实际上用于编写的代码就位于自动生成的类型之中。此时,或者需要触发一个事件,或者就需要生成一个virtual方法来让用户继承。但无论是事件还是继承,开销都是比较大的,所以可以通过分部方法来实现轻量级的处理方式。如下面的类:(本例子引用自前述的In Case You Haven't Heard一文)
partial class Customer { string name; public string Name { get { return name; } set { OnBeforeUpdateName(); OnUpdateName(); name = value; OnAfterUpdateName(); } } partial void OnBeforeUpdateName(); partial void OnAfterUpdateName(); partial void OnUpdateName(); }
这里定义了三个分部方法,其意义不言而喻。假设这是系统自动生成的代码,则我们只需在另外一个源代码文件中的partial class Customer中实现这几个分部方法即可。
场景2 自定义DataContext中的Insert、Update、Delete方法
当使用Linq to SQL向项目中加入了实体类之后,还会创建一个XxxDataContext类,这个类继承自DataContext类,并且是partial的。这个类封装了具体的数据库操作功能(实体类仅封装数据库中的数据),如对象的插入、更新和删除等。
下面我们来看一下这个自动生成的类定义:
[System.Data.Linq.Mapping.DatabaseAttribute(Name="AdventureWorks")] public partial class AdventureWorksDataContext : System.Data.Linq.DataContext { private static System.Data.Linq.Mapping.MappingSource mappingSource = new AttributeMappingSource(); #region Extensibility Method Definitions partial void OnCreated(); partial void InsertAWBuildVersion(AWBuildVersion instance); partial void UpdateAWBuildVersion(AWBuildVersion instance); partial void DeleteAWBuildVersion(AWBuildVersion instance); ......
这里我们可以看到一系列的partial方法。其中第一个OnCreated实际上属于场景1中描述的情况,是一个轻量级的事件,表示 DataContext环境对象创建完毕。而其他partial方法则用于自定义DataContext的IUD操作。对于每一个表(实体类),这里都会出现一组InsertXxx、UpdateXxx和DeleteXxx方法。如果我们希望自定义删除行为(如希望将一个IsDelete字段设置为 true来表示已删除),则可以在另一个文件中扩展这个partial类,并为对应的Delete方法提供实现声明。
场景3 新的调试信息输出方法
这是Anders Liu臆想的场景,在分部方法的协助下,我们可以写出这样的代码:
partial class CA { partial void DebugPrint(string msg); ... void F() { .... DebugPrint("aaa"); } } partial class CA { #if DEBUG partial void DebugPrint(string msg); { Debug.WriteLine(msg); } #endif }
这样做的好处在于,我们还是反过来说罢,如果不这样做,必须在每次调用调试代码时都加入#if判断。而这样可以将调试代码都写成方法,在一处用#if进行判断。
缺点在于,由于分部方法必须是私有的,所以必须针对每个类写一套调试代码。
小结
嗯,总而言之,Anders Liu在这篇文章里说的是分部方法。
http://dotnet.chinaitlab.com/CSharp/727264.html
yanglilibaobao收录,使用标签:软件测试,时间:2007-8-6 16:18:06 | 相关网摘,我也收藏
人是测试工作中最有价值也是最重要的资源,没有一个合格的、积极的测试小组,测试就不可能实现。然而,在软件开发产业中有一种非常普遍习惯,那就是让那些经验最少的新手、没有效率的开发者或不适合干其他工作的人去做测试工作。这绝对是一种目光短浅的行为,对一个系统进行有效的测试所需要的技能绝对不比进行软件开发需要的少,事实上,测试者将获得极其广泛的经验,他们将遇到许多开发者不可能遇到的问题。
①、沟通能力
一名理想的测试者必须能够同测试涉及到的所有人进行沟通,具有与技术(开发者)和非技术人员(客户,管理人员)的交流能力。既要可以和用户谈得来,又能同开发人员说得上话,不幸的是这两类人没有共同语言。和用户谈话的重点必须放在系统可以正确地处理什么和不可以处理什么上。而和开发者谈相同的信息时,就必须将这些活重新组织以另一种方式表达出来,测试小组的成员必须能够同等地同用户和开发者沟通。
②、移情能力
和系统开发有关的所有人员都处在一种既关心又担心的状态之中。用户担心将来使用一个不符合自己要求的系统,开发者则担心由于系统要求不正确而使他不得不重新开发整个系统,管理部门则担心这个系统突然崩溃而使它的声誉受损。测试者必须和每一类人打交道,因此需要测试小组的成员对他们每个人都具有足够的理解和同情,具备了这种能力可以将测试人员与相关人员之间的冲突和对抗减少到最低程度。
③、技术能力
就总体言,开发人员对那些不懂技术的人持一种轻视的态度。一旦测试小组的某个成员作出了一个错误的断定,那么他们的可信度就会立刻被传扬了出去。一个测试者必须既明白被测软件系统的概念又要会使用工程中的那些工具。要做到这一点需要有几年以上的编程经验,前期的开发经验可以帮助对软件开发过程有较深入的理解,从开发人员的角度正确的评价测试者,简化自动测试工具编程的学习曲线。
④、自信心
开发者指责测试者出了错是常有的事,测试者必须对自己的观点有足够的自信心。如果容许别人对自己指东指西,就不能完成什么更多的事情了。
⑤、外交能力
当你告诉某人他出了错时,就必须使用一些外交方法。机智老练和外交手法有助于维护与开发人员的协作关系,测试者在告诉开发者他的软件有错误时,也同样需要一定的外交手腕。如果采取的方法过于强硬,对测试者来说,在以后和开发部门的合作方面就相当于“赢了战争却输了战役”。
⑥、幽默感
在遇到狡辩的情况下,一个幽默的批评将是很有帮助的。
⑦、很强的记忆力
一个理想的测试者应该有能力将以前曾经遇到过的类似的错误从记忆深处挖掘出来,这一能力在测试过程中的价值是无法衡量的。因为许多新出现的问题和我们已经发现的问题相差无几。
⑧、耐心
一些质量保证工作需要难以置信的耐心。有时你需要花费惊人的时间去分离、识别和分派一个错误。这个工作是那些坐不住的人无法完成的。
⑨、怀疑精神
可以预料,开发者会尽他们最大的努力将所有的错误解释过去。测式者必须听每个人的说明,但他必须保持怀疑直到他自己看过以后。
⑩、自我督促
干测试工作很容易使你变得懒散。只有那些具有自我督促能力的人才能够使自己每天正常地工作。
11、洞察力
一个好的测试工程师具有“测试是为了破坏”的观点,捕获用户观点的能力,强烈的质量追求,对细节的关注能力。应用的高风险区的判断能力以便将有限的测试针对重点环节。
http://softtest.chinaitlab.com/Manage/727448.html
yanglilibaobao收录,使用标签:软件测试,时间:2007-8-6 16:14:54 | 相关网摘,我也收藏
人们常常以为,开发一个程序是困难的,测试一个程序则比较容易。这其实是误解。设计测试用例是一项细致并需要高度技巧的工作,稍有不慎就会顾此失彼,发生不应有的疏漏。
不论是黑盒测试方法还是白盒测试方法,由于测试情况数量巨大,都不可能进行彻底的测试。所谓彻底测试,就是让被测程序在一切可能的输入情况下全部执行一遍。通常也称这种测试为“穷举测试”。 “黑盒”法是穷举输入测试,只有把所有可能的输入都作为测试情况使用,才能以这种方法查出程序中所有的错误。实际上测试情况有无穷多个,人们不仅要测试所有合法的输入,而且还要对那些不合法但是可能的输入进行测试。 “白盒”法是穷举路径测试,贯穿程序的独立路径数是天文数字,但即使每条路径都测试了仍然可能有错误。第一,穷举路径测试决不能查出程序违反了设计规范,即程序本身是个错误的程序。第二,穷举路径测试不可能查出程序中因遗漏路径而出错。第三,穷举路径测试可能发现不了一些与数据相关的错误。E.W.Dijkstra的一句名言对测试的不彻底性作了很好的注解:“程序测试只能证明错误的存在,但不能证明错误不存在”。
在实际测试中,穷举测试工作量太大,实践上行不通,这就注定了一切实际测试都是不彻底的。当然就不能够保证被测试程序中不存在遗留的错误。软件工程的总目标是充分利用有限的人力和物力资源,高效率、高质量地完成测试。为了降低测试成本,选择测试用例时应注意遵守“经济性”的原则。第一,要根据程序的重要性和一旦发生故障将造成的损失来确定它的测试等级;第二,要认真研究测试策略,以便能使用尽可能少的测试用例,发现尽可能多的程序错误。掌握好测试量是至关重要的,一位有经验的软件开发管理人员在谈到软件测试时曾这样说过:“不充分的测试是愚蠢的,而过度的测试是一种罪孽”。测试不足意味着让用户承担隐藏错误带来的危险,过度测试则会浪费许多宝贵的资源。
测试是软件生存期中费用消耗最大的环节。测试费用除了测试的直接消耗外,还包括其它的相关费用。能够决定需要做多少次测试的主要影响因素如下:
①、系统的目的
系统的目的的差别在很大程度上影响所需要进行的测试的数量。那些可能产生严重后果的系统必须要进行更多的测试。一台在Boeing 757上的系统应该比一个用于公共图书馆中检索资料的系统需要更多的测试。一个用来控制密封燃气管道的系统应该比一个与有毒爆炸物品无关的系统有更高的可信度。一个安全关键软件的开发组比一个游戏软件开发组要有苛刻得多的查找错误方面的要求。
②、潜在的用户数量
一个系统的潜在用户数量也在很大程度上影响了测试必要性的程度。这主要是由于用户团体在经济方面的影响。一个在全世界范围内有几千个用户的系统肯定比一个只在办公室中运行的有两三个用户的系统需要更多的测试。如果不能使用的话,前一个系统的经济影响肯定比后一个系统大。除此而外,在分配处理错误的时候,所花的代价的差别也很大。如果在内部系统中发现了一个严重的错误,在处理错误的时候的费用就相对少一些,如果要处理一个遍布全世界的错误就需要花费相当大的财力和精力。
③、信息的价值
在考虑测试的必要性时,还需要将系统中所包含的信息的价值考虑在内,一个支持许多家大银行或众多证券交易所的客户机/服务器系统中含有经济价值非常高的内容。很显然这一系统需要比一个支持鞋店的系统要进行更多的测试。这两个系统的用户都希望得到高质量、无错误的系统,但是前一种系统的影响比后一种要大得多。因此我们应该从经济方面考虑,投入与经济价值相对应的时间和金钱去进行测试。
④、开发机构
一个没有标准和缺少经验的开发机构很可能开发出充满错误的系统。在一个建立了标准和有很多经验的开发机构中开发出来的系统中的错误不会很多,因此,对于不同的开发机构来说,所需要的测试的必要性也就截然的不同。 然而,那些需要进行大幅度改善的机构反而不大可能认识到自身的弱点。那些需要更加严格的测试过程的机构往往是最不可能进行这一活动的,在许多情况下,机构的管理部门并不能真正地理解开发一个高质量的系统的好处。
⑤、测试的时机
测试量会随时间的推移发生改变。在一个竟争很激烈的市场里,争取时间可能是制胜的关键,开始可能不会在测试上花多少时间,但几年后如果市场分配格局已经建立起来了,那么产品的质量就变得更重要了,测试量就要加大。测试量应该针对合适的目标进行调整。
http://softtest.chinaitlab.com/Manage/727451.html
yanglilibaobao收录,使用标签:软件测试,时间:2007-8-6 16:11:55 | 相关网摘,我也收藏
白盒测试作为测试人员常用的一种测试方法,越来越受到测试工程师的重视。白盒测试并不是简单的按照代码设计用例,而是需要根据不同的测试需求,结合不同的测试对象,使用适合的方法进行测试。因为对于不同复杂度的代码逻辑,可以衍生出许多种执行路径,只有适当的测试方法,才能帮助我们从代码的迷雾森林中找到正确的方向。本文介绍六种白盒子测试方法:语句覆盖、判定覆盖、条件覆盖、判定条件覆盖、条件组合覆盖、路径覆盖。
白盒测试的概述
由于逻辑错误和不正确假设与一条程序路径被运行的可能性成反比。由于我们经常相信某逻辑路径不可能被执行, 而事实上,它可能在正常的情况下被执行。由于代码中的笔误是随机且无法杜绝的,因此我们要进行白盒测试。
白盒测试又称结构测试,透明盒测试、逻辑驱动测试或基于代码的测试。白盒测试是一种测试用例设计方法,盒子指的是被测试的软件,白盒指的是盒子是可视的,你清楚盒子内部的东西以及里面是如何运作的。
白盒的测试用例需要做到:
·保证一个模块中的所有独立路径至少 被使用一次
·对所有逻辑值均需测试 true 和 false
·在上下边界及可操作范围内运行所有循环
·检查内部数据结构以确保其有效性
白盒测试的目的:通过检查软件内部的逻辑结构,对软件中的逻辑路径进行覆盖测试;在程序不同地方设立检查点,检查程序的状态,以确定实际运行状态与预期状态是否一致。
白盒测试的特点:依据软件设计说明书进行测试、对程序内部细节的严密检验、针对特定条件设计测试用例、对软件的逻辑路径进行覆盖测试。
白盒测试的实施步骤:
1.测试计划阶段:根据需求说明书,制定测试进度。
2.测试设计阶段:依据程序设计说明书,按照一定规范化的方法进行软件结构划分和设计测试用例。
3.测试执行阶段:输入测试用例,得到测试结果。
4.测试总结阶段:对比测试的结果和代码的预期结果,分析错误原因,找到并解决错误。
白盒测试的方法:总体上分为静态方法和动态方法两大类。
静态分析是一种不通过执行程序而进行测试的技术。静态分析的关键功能是检查软件的表示和描述是否一致,没有冲突或者没有歧义。
动态分析的主要特点是当软件系统在模拟的或真实的环境中执行之前、之中和之后 , 对软件系统行为的分析。动态分析包含了程序在受控的环境下使用特定的期望结果进行正式的运行。它显示了一个系统在检查状态下是正确还是不正确。在动态分析技术中,最重要的技术是路径和分支测试。下面要介绍的六种覆盖测试方法属于动态分析方法。
白盒测试的优缺点
1. 优点
·迫使测试人员去仔细思考软件的实现
·可以检测代码中的每条分支和路径
·揭示隐藏在代码中的错误
·对代码的测试比较彻底
·最优化
2. 缺点
·昂贵
·无法检测代码中遗漏的路径和数据敏感性错误
·不验证规格的正确性
六种覆盖方法
首先为了下文的举例描述方便,这里先给出一张程序流程图。(本文以1995年软件设计师考试的一道考试题目为例,图中红色字母代表程序执行路径)。
1、语句覆盖
1)主要特点:语句覆盖是最起码的结构覆盖要求,语句覆盖要求设计足够多的测试用例,使得程序中每条语句至少被执行一次。
2)用例设计:(如果此时将A路径上的语句1—〉T去掉,那么用例如下)
X Y 路径
1 50 50 OBDE
2 90 70 OBCE
3)优点:可以很直观地从源代码得到测试用例,无须细分每条判定表达式。
4)缺点:由于这种测试方法仅仅针对程序逻辑中显式存在的语句,但对于隐藏的条件和可能到达的隐式逻辑分支,是无法测试的。在本例中去掉了语句1—〉T去掉,那么就少了一条测试路径。在if结构中若源代码没有给出else后面的执行分支,那么语句覆盖测试就不会考虑这种情况。但是我们不能排除这种以外的分支不会被执行,而往往这种错误会经常出现。再如,在Do-While结构中,语句覆盖执行其中某一个条件分支。那么显然,语句覆盖对于多分支的逻辑运算是无法全面反映的,它只在乎运行一次,而不考虑其他情况。
2、判定覆盖
1)主要特点:判定覆盖又称为分支覆盖,它要求设计足够多的测试用例,使得程序中每个判定至少有一次为真值,有一次为假值,即:程序中的每个分支至少执行一次。每个判断的取真、取假至少执行一次。
2)用例设计:
X Y 路径
1 90 90 OAE
2 50 50 OBDE
3 90 70 OBCE
3)优点:判定覆盖比语句覆盖要多几乎一倍的测试路径,当然也就具有比语句覆盖更强的测试能力。同样判定覆盖也具有和语句覆盖一样的简单性,无须细分每个判定就可以得到测试用例。
4)缺点:往往大部分的判定语句是由多个逻辑条件组合而成(如,判定语句中包含AND、OR、CASE),若仅仅判断其整个最终结果,而忽略每个条件的取值情况,必然会遗漏部分测试路径。
3、条件覆盖
1)主要特点:条件覆盖要求设计足够多的测试用例,使得判定中的每个条件获得各种可能的结果,即每个条件至少有一次为真值,有一次为假值。
2)用例设计:
X Y 路径
1 90 70 OBC
2 40 OBD
3)优点:显然条件覆盖比判定覆盖,增加了对符合判定情况的测试,增加了测试路径。
4)缺点:要达到条件覆盖,需要足够多的测试用例,但条件覆盖并不能保证判定覆盖。条件覆盖只能保证每个条件至少有一次为真,而不考虑所有的判定结果。
4、判定/条件覆盖
1)主要特点:设计足够多的测试用例,使得判定中每个条件的所有可能结果至少出现一次,每个判定本身所有可能结果也至少出现一次。
2)用例设计:
X Y 路径
1 90 90 OAE
2 50 50 OBDE
3 90 70 OBCE
4 70 90 OBCE
3)优点:判定/条件覆盖满足判定覆盖准则和条件覆盖准则,弥补了二者的不足。
4)缺点:判定/条件覆盖准则的缺点是未考虑条件的组合情况。
5、组合覆盖
1)主要特点:要求设计足够多的测试用例,使得每个判定中条件结果的所有可能组合至少出现一次。
2)用例设计:
X Y 路径
1 90 90 OAE
2 90 70 OBCE
3 90 30 OBDE
4 70 90 OBCE
5 30 90 OBDE
6 70 70 OBDE
7 50 50 OBDE
3)优点:多重条件覆盖准则满足判定覆盖、条件覆盖和判定/条件覆盖准则。更改的判定/条件覆盖要求设计足够多的测试用例,使得判定中每个条件的所有可能结果至少出现一次,每个判定本身的所有可能结果也至少出现一次。并且每个条件都显示能单独影响判定结果。
4)缺点:线性地增加了测试用例的数量。
6、路径覆盖
1)主要特点:设计足够的测试用例,覆盖程序中所有可能的路径。
2)用例设计:
X Y 路径
1 90 90 OAE
2 50 50 OBDE
3 90 70 OBCE
4 70 90 OBCE
3)优点:这种测试方法可以对程序进行彻底的测试,比前面五种的覆盖面都广。
4)缺点:由于路径覆盖需要对所有可能的路径进行测试(包括循环、条件组合、分支选择等),那么需要设计大量、复杂的测试用例,使得工作量呈指数级增长。而在有些情况下,一些执行路径是不可能被执行的,如:
If (!A)B++;
If (!A)D--;
这两个语句实际只包括了2条执行路径,即A为真或假时候对B和D的处理,真或假不可能都存在,而路径覆盖测试则认为是包含了真与假的4条执行路径。这样不仅降低了测试效率,而且大量的测试结果的累积,也为排错带来麻烦。
总结
白盒测试是一种被广泛使用的逻辑测试方法,是由程序内部逻辑驱动的一种单元测试方法。只有对程序内部十分了解才能进行适度有效的白盒测试。但是贯穿在程序内部的逻辑存在着不确定性和无穷性,尤其对于大规模复杂软件。因此我们不能穷举所有的逻辑路径,即使穷举也未必会带来好运(穷举不能查出程序逻辑规则错误,不能查出数据相关错误,不能查出程序遗漏的路径)。
那么正确使用白盒测试,就要先从代码分析入手,根据不同的代码逻辑规则、语句执行情况,选用适合的覆盖方法。任何一个高效的测试用例,都是针对具体测试场景的。逻辑测试不是片面的测试正确的结果或是测试错误的结果,而是尽可能全面地覆盖每一个逻辑路径。
http://softtest.chinaitlab.com/tool/727465.html
yanglilibaobao收录,使用标签:软件测试,时间:2007-8-6 16:08:51 | 相关网摘,我也收藏
测试实践对于每个行业、每个公司、每个测试人员都是不一样的。但是大多数的测试项目在某些要素上是有共同之处的。让我们把那些有共性的要素称之为“普通测试”吧。在我们的经验中,普通测试包括根据某种规格说明书写一些测试用例。这些测试用例是松散地指导测试人员去测试一个产品的零散的计划或者过程。然后测试人员按照预期的那样在整个产品中执行那些测试用例,重复地,在项目过程中从头到尾地执行。
快速测试与传统测试主要有以下几方面的区别:
1. 首先,不浪费时间。最快速的行动是完全不行动。因此,在快速测试中,我们要消灭掉任何不必要的活动。比较起来,传统测试是比较臃肿的,随之也带来一定的混乱。当然,需要通过一些培训和经验来知道如何来对传统测试“瘦身”。一般地说,流线型的文档(应该是指大量的文档)和虔诚的测试是最容易发生风险的区域。不要因为别人告诉你重复是好的,你就来回的测同一个东西。确保你从每个测试中得到了好的、有价值的信息。要考虑每次测试活动的机会成本2.Mission.在快速测试中我们不是以Task为导向(如写测试用例),我们是以Mission为导向的。我们的目标可能是“快点找到重要的问题”。如果是这样,那么写测试用例可能不是最好的方式。另一方面,如果我们的目标是“使FDA听众满意”,那么我们不仅要写测试用例,还要按照指定的规格来写某几种测试用例。理解我们的Mission,然后估算一下我们的形势,并找到我们能朝着实现该目标立即开始执行的最快、最有用的行动。
3.技巧。做好任何的测试都要求技巧。普通测试不重视测试技巧的重要性,它更多关注测试文档的格式而不是测试的健壮性。快速测试,就像我们描述的,强调测试技巧。它不是像用微波炉炸爆米花那样的机械技术,或者是在DMV(机动车管理部门)填表格。健壮的测试是非常重要的,因此我们练习批判性思维和试验设计技巧。一个测试新手不会在测试中做得很好,除非有一个在测试艺术、技艺上有较高造诣的资深测试人员来监督和指导。我们希望本站点的一些文章能够在这些技巧上帮助你。
4.风险。普通测试关注功能和结构上的产品覆盖率。换句话说,如果产品能做什么,就测什么。快速测试更关注重要的问题。基于对产品的理解,找出那些我们认为的最可能发生并且发生后影响较大的问题。然后投入我们的主要精力来测试那些问题。快速测试往往意味着尽可能快的揭露最重要的信息。
5.探索。快速测试也是快速学习,因此我们使用探索性测试。我们避免先写测试用例,除非有明确和强制性的要求。我们更喜欢让上一个测试影响我们的下一个测试。这是一个好事情,因为我们并没有被预先设计好的测试步骤所禁锢,而且让我们发现了更好的测试思想。让测试快速地执行的其它方式,例如很多的测试自动化,总是有着这样的风险――即使运行了大量的非常快速的测试也不能在产品中帮助你找到重要的问题。
6.启发法。我们必须当心高估所测试的问题,因此我们使用启发法(简单的翻译成:拇指规则)来帮助我们避免思维短路,并且更快地测试。启发法本质上是反应――在某种意义上有偏差地反应――通常是帮助我们在正确地时间测试正确的东西。快速测试收集、记住并且练习使用有帮助的启发法。在普通测试中,启发法也有被使用,但是测试人员往往并不知道自己使用了这个方法,也不能完全地掌控这个方法。
7.团队合作。快速测试意味着作弊。至少,我们做的事情在以前小学老师的眼中就是作弊:我们尽可能事先弄清楚事情,我们借用其它人的工作,我们使用我们能找到的任何资源和工具。例如,快速测试的一个重要的技术就是成对测试:两个人,一台电脑。这个思想在XP(极限编程)的实践中被证明是有效的,并且在测试工作中也很适用。在普通测试的经验中,测试人员通常安静、独自的工作,而不是像一群迅捷的狼在捕猎bugs. 8.反省。我们的快速测试人员应该要经常问我们正在做什么和为什么这样做。我们要解析我们自己,并且讨论更好的测试策略和状况。
http://softtest.chinaitlab.com/skills/727467.html
yanglilibaobao收录,使用标签:软件测试,时间:2007-8-6 16:01:02 | 相关网摘,我也收藏
解析测试工程师职业发展瓶颈
经过这几年的发展,国内IT公司的测试水平有了很大的提高,但是与此同时,很多测试工程师也迎来了个人的发展瓶颈:很多人从测试工程师做到了测试经理的职位,不知道下一步如何发展;或者每天机械地从事着功能测试工作。
本文首先从分析测试工程师的发展现状和职业化过程遇到的问题入手;接着分析什么样的测试人员才是合格的;最后介绍测试人员的职业历程以及如何突破自己的职业发展瓶颈。
1 测试工程师帖子解析
下面是在一些测试网站上收集的帖子,主要是一些测试工程师介绍自己的成长历程或者对测试行业的看法。从这些帖子中,我们可以看出测试工程师职业发展遇到的一些问题。
帖子一:踏实地发展自己
我在北京工作有4年了。职业发展依次经历了测试员-测试工程师-测试分析师-测试经理。这就是我在北京的4年测试生涯。个人对测试工作有如下的观点:
1)软件测试不像一些人看起来那么简单,需要相当深厚的技术背景。但只要掌握要领,也不像我们一些人所认为的那么困难;
2)测试工程师和开发人员相比,可以有机会接触更多的、不同行业的项目,是一个大的优势。
3)测试工程师要想成功,更多的是靠平时的积累。不管是项目的积累,还是平时学习,两者都至关重要。
4)测试工程师要充分利用网络资源,与同行们充分交流,在互相帮助和学习的氛围中,可以加快自己成长速度。
点评:这是一位比较踏实的测试工程师,一步一个胶印地走着自己的测试之路,我们可以认为他是相对成功的典范。现实中我们很多测试工程师不是抱怨工资低,就是抱怨自己公司的测试环境不好。如果要想在测试领域走向成功,重要的秘诀就是踏踏实实地学习,认认真真地做好本职工作。
帖子二:执着的测试工程师
我做测试工作快6年了。刚开始的时候,我是公司的第一个测试员,虽然公司也在做ISO9000,但是什么规范都得自己摸索。可是,我仍然坚持下来了,而且大有收获,虽然在公司里不受重视。
但是随着测试工作的不断深入,自己对公司的主流业务(我们作的是行业软件)从外行变成了内行。而且还发现了一些产品的设计方面的欠缺,在老板和开发主管面前树立了自己的一些威信。至少在一些项目进行需求分析的时候,会来征求我的意见。而且,目前做到了不经测试的产品不给客户。当然,在我和开发经理发生分歧的时候,大部分时间老板还是支持开发经理,但我认为是正确的地方还是会据理力争的。
一句话,测试人员是孤独的,寂寞的。但只要坚持,总能有收获的,尤其是在发现了隐藏很深的一些BUG的时候,那种成就感和自豪感真的是一种很好的感觉。
实际上,做任何一行工作,都会遇到不公平。但为什么要去跟别人比呢,只要自己有提高,就是好的。
点评:“敢做冷板凳的人”才是勇敢的人,这位发帖子的测试工程师不但有勇气坐了冷板凳,而且能够坚持下来,直到取得了不错的成绩。“实际上,做任何一行工作,都会遇到不公平。但为什么要去跟别人比呢,只要自己有提高,就是好的。”几句朴实无华的话说出了如何做好测试工作的真谛。
测试人员一定要给自己正确的定位,既然选择了目前地位有些低的测试工作,就应该踏实的做好,这是走向成功的必由之路。
帖子三:好学而有信心的新手
我在一家外企作了一段时间的兼职测试,之前我从未接触过测试。开始的时候只作一些Manual test,后来就开始做Automated test,修改原有的test cases,或者重写一些test cases。然后test小组的leader建议我写测试文档,他说写文档有利于一个tester技术水平的提高。因为你必须要熟悉软件项目的整体框架,洞悉软件深层的结构才能写出高质量的测试用例。
于是,我在网上查了一些关于测试方面的资料,发现测试真的很重要。对一个软件项目而言,老外对软件测试尤其重视。我兼职的这个外企是有一半的员工是测试的,大概有7、8个人。个人认为,国内的软件企业对测试的重视程度还不够,但是毋庸置疑,测试是软件企业产品线上和开发同等重要的。可以预言,未来的国内软件行业,软件测试人员可能会占据软件团队半数左右。同时,对测试人员的能力要求也是比较高的。
综上,我觉得Software test很有前途。当然,这些都是一个测试新手的看法,可能比较片面,全当给大家打打气了。
点评:可以看出这是一位很有远见的测试工程师。现实中很多测试工程师是由于不能从事其它工作才从事测试的,因而工作中也是不断地抱怨待遇、团队环境等不能满足自己的要求。在此建议测试工程师,如果选择了这个行业,就应该认真地对待工作,抱怨永远解决不了问题。只有像这位测试工程师一样认真分析自己的行业,才可以有更好的职业化发展,否则还不如换一个自己喜欢的工作去试一试。
帖子四:郁闷而犹豫的测试工程师
我做软件测试也有两年多的时间了,但是在这两年中似乎没有太多的提高。因为都是黑盒测试,所以一般就是使用产品,最多也就是一些工具测试。可是这都是想到哪就测到哪,也没有真正按照测试文档执行。公司测的东西组合情况也很多,根本没办法。而且公司测试流程也不规范。
刚开始没签约的时候,还是专职测试,签了以后简直就变成打杂的了。什么都要做,连一些设计文档都推了过来,有时候还要去现场了解客户需求,真是非常佩服老板把一个人当三个人用的能力。总的感觉在小公司里,根本就没有测试员这个概念,测试员一般什么都得做。当你提出一堆建议的时候,老板总是会说,现在公司规模还不具备条件,以后会慢慢的改善的,可我感觉过两年公司可能已经倒闭了。
真是有点郁闷,改行做开发,又不想放弃这个被很看好的职业。可是光被看好又怎么样呢?说不定十几年前,扫大街的就像现在的做测试的。都是做质量保证,扫大街的要保证城市环境的质量,现在呢?他们又怎么样?
点评:其实测试和开发一样,都不是那么想当然的理想,国内开发环境也和测试一样混乱。而开发人员发展到高级程序员就会成为很多人的发展瓶颈,薪资和职务都很难再提升。而测试与开发相比的一个巨大优势在于它是一个新兴的领域,拥有更多的机会,测试人员工作三五年,再有一些管理经验,很容易做到测试主管,虽然薪资待遇相对低些,但是给个人的长期发展打好了基础。看准了就应该去做,实实在在的学到知识才是最重要的。
2 国内软件测试测试工程师职业发展现状
我们可以从两个方面来解析测试测试工程师的职业瓶颈问题:
l 从企业方面:多数企业较难招聘到满意的测试工程师,尤其在软件测试外包企业,人才问题成了这类企业的发展瓶颈,这些恰恰反映了整个测试行业的发展遇到了瓶颈;
l 从个人方面:很多测试人员薪资和职位到了一定阶段就很难得到提升,例如很多测试工程师做到测试经理后,几年内得不到提升。
职业发展尤其体现在待遇方面。下表是北京市一些IT企业测试工程师的月薪数据。这些数据主要从一些网站收集,由一些测试工程师发布。
职称名
税前月薪(人民币:元)
备注
少于100人
100~200人
200人以上
初级工程师
1500~2000
2000~2500
3000~4000
手工执行功能测试,这些人通常是一年以内工作经验的新手。
2000~3000
2500~3500
一年到两年经验的测试工程师,工作内容通常以功能测试为主。
中级工程师
2500~3500
3000~4000
2500~3500
编写并执行测试用例,通常工作两年以上。
3500~4000
4000~4500
3500~4500
编写并执行测试用例,能够使用一些测试工具。通常工作两年以上。
高级工程师
4000~5000
5000~6000
6000~7000
熟练使用测试工具,制定测试计划,编写并执行测试用例等。工作经验通常三年以上。
7000~8000
能编写自动化测试脚本与进行白盒测试工作。具有一定的开发技能。工作经验通常三年以上。
测试分析师
4000~5000
5000~6000
6000~7000
具有一定分析能力的高级测试工程师。工作经验通常四年以上。
6000~7000
7000~8000
能够设计测试方案,执行测试并对测试结果进行全面的分析,例如性能测试分析。
测试组长
4000~5000
6000~7000
6000~7000
带领10人以内的测试小组执行测试任务。
5000~6000
http://www.testing.ac.cn/html/18/0/516/1.htm
yanglilibaobao收录,使用标签:项目管理,时间:2007-8-6 15:51:27 | 相关网摘,我也收藏
伴随软件行业的发展,业界公司也不断对软件的研发开发模式进行了深入的探讨,形成目前业界比较流程的几个软件开发模式,例如MSF、RUP、XP、CMM,而这些模式都存在相应的优势和缺点;同时因为软件的类型多样、面向的客户对象不同、甚至使用的开发语言不同,决定了软件企业需要结合本公司的产品实际情况选择合适的开发模式;选择不当的开发模式会使软件的开发和测试陷入不断修改代码和测试软件的困境,增加了软件开发成本,延长了软件发布时间。
一、MSF模式
MSF于1994年首次引入,当时还是一个来自微软的产品开发过程中的一些最佳方法的松散集合,MSF产生后在微软产品组、交付中心、技术组和微软合作伙伴,甚至客户那里得到成功实施,并不断完善,既然MSF被众多软件企业所借鉴使用,MSF本身具备一些值得我们学习借鉴的优点,同时也存在一些需要注意,并进行适当改进的地方,接下来我们重点从组织、流程模型进行分析。
1.1、MSF小组模型
MSF 小组模型定义了小组同级成员的一些角色和职责,这些成员都在以相互依存的跨学科角色进行信息技术项目工作。下面的图表对该模型的逻辑进行了描述。
一个成功的软件项目需要同时实现多个目标,例如至少需要包含能够满足客户的要求、客户满意度要比较高;公司的投入要在一定范围内,要实现投资收益,毕竟商业企业的盈利是应该首要考虑的内容;项目的实际开发进行要受控不能无限拖延;最终的交付要有比较高的质量;整体软件系统的架构设计要比较合理、易于扩展、易于维护;同时还要求项目同时需要给公司带来新的技术积累、经验教训,从而使后续项目能够持续成功;而这些目标需要通过不同的活动来实现,软件项目的活动又具体划分为多种类型,大概主要划分为面向市场类活动、项目管理类活动、架构设计类活动、开发实现类活动、测试类活动、支撑类活动(例如配置管理等),而这些活动需要具备不同技能人员来执行,所以MSF具体定义了如下五类角色群:产品管理、程序管理、开发、测试、发布管理、用户体验,并且明确定义了五类角色具体职责,这些职责的定义对具体软件企业有很大的借鉴价值:
针对MSF小组模型定义,作者感觉重点体现如下优点:
1)按照角色群的方式定义,便于角色的分层管理;
2)角色、职责定义全面含盖了软件项目的所有必须的活动;
3)定义了根据不同项目情况,小组模式的扩展与收缩方法,对不同规模、不同类型的项目有很大的借鉴价值;
4)角色定义就体现了商业软件开发的市场驱动、财务驱动;
5)角色定义的层次体现了对测试、服务等职能工作的重视;
结合中国软件企业的管理现状,MSF在中国软件企业具体使用时就会暴露如下缺陷:
1)项目的责任主体不明确,缺少一个角色对项目的整体负责,产品管理对项目前端和后端负责、程序管理对项目中间负责,另MSF更强调的是大家协同共同为项目负责,甚至不同阶段项目的第一责任主体可以不同,体现了类似“生产模式”的研发。
2)MSF只定义了操作级项目模式,而没有详细定义项目决策团队的模式,在中国职能管理根深底厚,简单定义项目操作级的组织模式,项目实际执行中会因为人员来自不同职能部门产生比较多的冲突和相互配合问题。
1.2、流程模型
每个项目都要经过一个生命周期,这是一个包含项目里所有活动的过程,而这些活动的发生要到项目结束并过渡到操作状态才会结束。生命周期模型的主要功能是建立活动进行的顺序。正确的生命周期模型能够简化项目,并帮助确保每一个步骤都会让项目更加接近成功。下面是 MSF 过程模型生命周期的一个简图。
MSF 过程模型把来自传统的瀑布模型和螺旋模型的概念结合起来,以阶段和里程碑为基础,每个阶段都有其自身的特色,每个阶段的结束都代表了项目进展和中心点的变化。里程碑是检查和同步点,用来确定阶段的目标是否已经实现。同时MSF基于软件项目需求的特点(模糊性、易变性),建议采用版本化项目开发模式,具体就会出现多个版本的叠代开发,类似如下流程模型:
整体来讲MSF的流程模型定义是非常清晰的,也体现了软件项目的实际特征,作者感觉MSF流程模型集中体现了以下优点:
1)阶段化,将流程划分为明显的几个阶段,并且明确定义了阶段结束标准;
2)并行化,MSF流程模型把并行开发的思维体现的比较清晰,例如明确定义开发阶段就需要明确定义下阶段(稳定阶段)的集中测试所需要的测试用例;
3)阶段里程碑和内部小里程碑都被比较确切地定义,通过内部里程碑的控制可以有效控制项目构件的交付质量,通过阶段里程碑的控制可以有效解决构件之间的遗留问题,确保项目整体质量;
4)实现各个职能领域的交叉协同,流程模型中同时详细定义各个支撑角色(例如配置管理)的活动,实现了工程活动和管理活动的有效配合,从而从流程体系上保证了进度、质量、成本在一定范围的协调一致;
结合中国软件企业的管理现状,发现具体采用MSF流程模式进行实际项目开发时,也暴露出MSF需要具体补充和完善的地方,如下:
1)MSF流程模型是基于项目资源是有限的,而实际情况是一个公司往往同时会进行多个项目,MSF流程模式中没有对多项目管理进行详细描述,没有充分体现管道管理的思想,没有将管道管理贯彻在具体的项目流程模型中;
2)MSF流程模型中,缺少对公司高层管理团队活动职责的定义,更多局限在项目操作级,而实际情况是很多问题是在项目级无法解决的,所以如果没有明确定义高层的规范参与的话,无疑是增加了项目的风险;
整体来讲MSF是一个非常具有借鉴价值的软件开发模式,通过对MSF的分析研究,对实际公司研发体系的构建会有比较大的借鉴价值,后续我们会结合中国软件企业的实际情况,连续对RUP、CMM、XP、NPD-CMM等软件开发模式进行分析,敬请大家关注。
http://se.csai.cn/SPI/200707112058091237.htm
yanglilibaobao收录,使用标签:软件测试,时间:2007-8-6 15:07:51 | 相关网摘,我也收藏
如图 6,考虑到不同的需求,Abbot SWT提供了不同种类的匹配器,他们都实现了Matcher接口,如果有特别的需求,你也可以自行实现或者修改现有的匹配器的代码。
图6:Abbot SWT的匹配器
图 6 中展示了很多不同用途的匹配器,我们经常会使用 ClassMultiMatcher 和 TextMultimatcher。如果我们仔细的看看代码就明白 Abbot 是如何能够找到目标构件的,打开 abbot.finder.swt.BasicFinder 类,我们会看见 Abbot 会遍历 Hierarchy 上面的所有 UI 构件,直到找到匹配器的条件满足的构件返回。匹配器是如何匹配的呢?我们可以简单的看看 TextMatcher 的代码,在 TextMatcher 的 matches 方法中充满了诸如清单7中的判断代码。
清单 7:TextMatcher 的代码片断。
if (w instanceof Button) {
setWtext(((Button)w).getText());
}
if (w instanceof Combo) {
setWtext(((Combo)w).getText());
}
这说明所有TextMatcher对于不同的构件会有不同表现,比如 button就是 button的text,combo是选中项的text而tableItem的text则是所有列的text的一个数组。了解这些代码对你构造匹配器有着重要的意义,只有因势利导,你才能更好的使用Abbot。经常会有初学者将NameMatcher和TextMatcher 混淆,如果看看NameMatcher的代码就了解它是匹配构件的类型和变量名,而不是构件的文本。
细心的读者也许会发现一个问题,Abbot的匹配器过多的依赖于字符串的匹配,这要求最好所有的界面上的文本全部使用常量的方式定义(否则在不同的语言环境中,你的测试用例将寸步难行),这也符合Eclipse国际化的编程要求,可见使用Abbot测试用户界面,会迫使你编写国际化的插件(我们的新建java类测试用例为了简化,就没有做类似的处理)。
这里有个有趣的话题,我们可以通过使用键盘的快捷键避免使用太多的查找器,菜单的快捷键,Tab键和回车键的组合使用经常能够节省你不少的时间去寻找按钮和菜单,通过WidgetTester.actionKeyPress方法,你可以轻易的模拟键盘事件。同时这也是对你用户界面的Accessibility(易访问性)的极端测试,完全键盘操作是Eclipse界面设计的Accessibility的一项要求。如同我们刚刚提到的国际化的要求一样,这里我们的测试用例反过来能够对代码的质量提出一些强制要求,可见极限编程方法中优先编写测试用例的指导方针果然名不虚传(当然本文提到的国际化和易访问性只是一个无心插柳的副作用,优先编写单元测试代码会强制你的代码简单易读,接口和业务逻辑代码分离,层次清晰,结构明确,并迫使你重构代码,使设计更加细化。有兴趣的读者可以参考《重构,改善既有代码的设计》一书)。
Abbot 测试代码的同步执行
Java 语言本身就提供了多线程机制,因为很多的用户界面会通过不同的线程完成不同的任务,有些线程是同步的,有些线程是异步的,你很难保证你测试代码会被同步的执行。SWT要求对UI的操作都要放在新的线程中进行,在我们的JavaWizardTest测试用例中,我们必须使用新的线程去测试界面,同时为了保证测试的同步,我们要等待向导的出现,然后才能执行针对向导的测试代码。读者也许已经注意倒我们的测试用例中openJavaWizard这一方法并未在线程中被调用,那是因为在MenuItemTester中的actionSelectMenuItem操作中,选择菜单的操作已经启动了一个新的线程。
在我们的等待线程中,我们如果要执行UI构件相关操作的断言,就应当都把他们封装在一个Runnable里面使用“abbot.tester.swt.Robot.syncExec”保证他们被同步的执行,这最终使用到了“org.eclipse.swt.widgets.Dispaly”的syncExec方法。由于SWT单独有一个线程(主线程)处理界面显示,数据显示等,如果要在其他线程中操作界面元素,就必须使用Display的syncExec和asyncExec两个方法执行,即将另外线程的操作交给主线程处理,我们使用的syncExec方法表示要顺序的同步的执行相关的测试操作。对于形如“assertFalse(finishButton.isEnabled())”这样的断言,因为涉及倒了UI构件的操作,就需要包装在syncExec方法内,而形如” assertNotNull(finishButton)”这样的断言,则不需要任何包装,直接在测试线程的代码体内就可以执行。
研究 Abbot 的源码可以发现,Abbot 的测试器代码中都使用了 syncExec,所以我们唯一需要使用的地方就在执行UI构件相关的断言的地方,但是为了保证代码的统一和完整,我们尽量使用形如如本测试用例的写法。
在Abbot控制用户界面的测试同时,请不要使用鼠标或者键盘,同时尽量关闭可能会弹出对话框的后台程序,以免使 Abbot 失去用户界面的控制权。不过即使被别的用户界面打断,在有效的等待时间的内,你仍然可以手工帮助界面运行到等待线程的条件处,Abbot会检测到界面并能继续执行下去。如果你在测试 Eclipse RCP 应用,界面的产生可能会和网络条件或者数据量有关系,Abbot默认的等待时间可能不能满足需要。当在默认的时间到达前没有能够等到期望的界面时,Abbot 会抛出 WaitTimedOutError 异常。Abbot 默认的等待时间应该是一分钟,其值在 abbot.tester.swt.Robot 中有定义,见清单 8。
清单 8. 默认延时的定义
protected static int componentDelay =
Properties.getProperty( "abbot.robot.component_delay",defaultDelay, 0, 60000);
你也可以通过设置延时参数来调整你的测试代码,WidgetTester提供了一些等待方法,其中就可以通过参数来设置延时时间,见图 7。
图 7:WidgetTester的等待方法
如果你有特别的需要,不仅满足于等待对话框或者构件的出现(在RCP应用中常见的就是等待服务器端的返回数据),可以使用abbot.tester.swt.Robot的“wait(Condition condition)”方法。你只要实现自己特定需求的Condition接口就可以让用户界面在等待你期望的结果,如清单9所示,Condition接口相当的简单。
清单 9:Condition接口的定义
public interface Condition {
/** Return the condition state. */
boolean test();
/** Return a description of what the condition is testing. */
String toString();
}
你需要在 test 方法中检查条件,一旦条件满足就返回 true,否则返回 false, Abbot 的 wait 方法会不断去调用 test 方法查询条件是否满足,你在调用时设置超时条件(如 图 7 中的”wait(Condition,long)”方法),如果不设置,Abbot 仍然会使用默认的等待超时值。
抽丝剥茧:Abbot SWT 的体系结构和工作原理
通过以上的测试用例和概念介绍,你一定已经对 Abbot SWT 有了感性的认识,一个看似简单的测试用例,实际上并不简单,几乎包含了 Abbot 的所有主要内容。现在让我们继续深入下去,看看 Abbot SWT 的体系结构和工作的原理,以便更深刻的了解、掌握和使用它。
Abbot 测试器
我们首先从 Abbot 测试器开始,Abbot 测试器可以帮助我们对各种不同的UI构件进行测试,JavaWizardTest 测试用例使用到了文本测试器(TextTester)、按钮测试器(ButtonTester)、菜单项测试器(MenuItemTester)和通用的构件测试器(WidgetTester)。Abbot 为几乎所有的 SWT 构件提供了相应的测试器,我们可以从图 4 上看到这一点。
图 4:Abbot 提供的所有的 SWT 部件的测试器
从图 4 可以看出,Abbot SWT 的测试器相当的丰富,仅有少数的复合构件及 JFace 构件如 Section、Browser、Dialog 等没有测试类。但是这些复合构件都是由基本的UI构件组成的,我们可以通过简单的UI构件的测试完成符合构件的测试。当然你可能需要研究这些构件的源代码,才能找到组成他们的基本构件,从而使用相应的测试器对其进行测试。例如 对于Section,这个复杂的UI构件,目前Abbot并没有提供“SectionTester”,但Section的标题栏实际上是一个Label, 你可以使用查找器根据Section的标题找到Label,然后调用 LabelTeseter的测试方法actionClick来打开或者关闭一个Section,有兴趣的读者可以试试,这是笔者在实际项目中遇到的一个小问题,其他的问题可以类似的方法解决。
和几乎所有的JavaGUI 测试工具一样,所有的测试器最后都是通过java.awt.Robot的API去激活用户界面,我们可以通过查看Abbot SWT的源码,最终可以一直追溯到 java.awt.Robot 和RobotPeer,这正是JDK中里面为了方便界面测试提供给程序员的底层API,可见所有的Java图形用户界面测试工具无不是扩展自这个Robot。java.awt.Robot可以生成操作系统原生的事件和消息来创建自动测试,自运行的演示,或者出于其他目的需要让应用来控制鼠标和键盘,用途很是广泛,有兴趣的读者可以深入研究。
Abbot 查找器和匹配器
虽然通过测试器可以发出消息驱动UI测试的完成,但是如果没有查找器的帮忙,你很难获取到你要测试的构件的句柄。我们已经了解到Abbot主要是依靠查找器(Finder)和 匹配器(Matcher)去寻找测试目标,尽管他们在 Abbot的基础插件中已经有了实现,Abbot SWT插件还是要为SWT开发了专门的查找器和匹配器。我们可以通过图 5 了解Abbot SWT的查找器和匹配器的关系:
图5 :Abbot SWT的查找器和匹配器
如图,我们最常用的是BasicFinder和匹配器的配合,通过参数配置,BasicFinder可以支持广度优先和深度优先的查找。
循序渐进:编写高效的 Abbot 测试用例
至此我们已经能够基本掌握 Abbot 的用法,编写简单的测试用例,你也许没有注意到,我们的测试用例是否是有效的测试用例?我们是否可以用更简单的方式,编写更加高效的测试用例?在本文的这一部分,你将能够了解到这些内容。
使用自定义线程捕获异常
如果你使用JUnit测试过多线程程序,你会发现JUnit实际上不支持多线程的测试,所有在线程中的断言失败或者异常对该测试用例的结果没有影响。前面我们已经了解到,Abbot的测试通常是使用线程方式进行的,我们还沉浸在一开始就运行成功Java类向导测试用例的喜悦中,也许并没有发现,那并不是一个有效的测试用例。想知道事情的原委,读者可以将JavaWizardTest中findAndTestWizard方法的“assertFalse(finishButton.isEnabled()); ”更改为“assertFalse(!finishButton.isEnabled());”再次运行该用例,你将发现控制台中,JUnit捕获到了junit.framework.AssertionFailedError错误,但是用户界面却停滞不前,当你按下取消按钮帮助界面结束测试,你可以在JUnit视图中发现测试用例居然成功通过。
此时我们发现两个问题:
我们在线程中的断言和异常并不能真正的决定测试用例的成败,因此我们的测试用例是无效的。
在发生异常后,我们并没有考虑到如何使用户界面继续(我们需要手工帮助向导退出),后续的测试方法和测试用例将无法继续进行。
实际项目中,我们通常使用自定义的测试线程和增加在finally中对用户界面的清理 工作来解决上面两个问题,首先我们定义一个能够简单地显示运行是否成功的线程,请参见清单10。
清单10:测试线程TestThread
package abbottest.sample;
public class TestThread extends Thread {
private Throwable exp = null;
public boolean isSuccess() {
return (exp == null);
}
public Throwable getExp() {
return exp;
}
public void setExp(Throwable exp) {
this.exp = exp;
}
}
该测试线程能够记录在运行过程中发现的错误,并提供方法来检查在运行是否成功完成。如果我们将Abbot的测试代码封装在这样的线程中运行,就可以在主线程中判断测试线程运行是否有错误,从而实现测试的目的。
这样我们经过修改的代码,置于JavaWizardTest2.java,清单 11显示了使用测试线程的JavaWizardTest2的代码。
清单11:使用测试线程的测试用例
package abbottest.sample;
import junit.framework.TestCase;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Widget;
import abbot.finder.matchers.swt.ClassMultiMatcher;
import abbot.finder.matchers.swt.TextMatcher;
import abbot.finder.matchers.swt.TextMultiMatcher;
import abbot.finder.swt.BasicFinder;
import abbot.finder.swt.TestHierarchy;
import abbot.tester.swt.MenuItemTester;
import abbot.tester.swt.Robot;
import abbot.tester.swt.TextTester;
import abbot.tester.swt.WidgetTester;
public class JavaWizardTest2 extends TestCase {
public void testJavaWizard() {
final TestThread wizThread = new TestThread() {
public void run() {
WidgetTester.waitForShellShowing("New Java Class");
try {
findAndTestWizard();
} catch (Throwable e) {
e.printStackTrace();
setExp(e);
}
}
};
wizThread.start();
assertTrue(wizThread.isSuccess());
}
private void openJavaWizard() {
MenuItemTester menuItemTester =
(MenuItemTester) WidgetTester.getTester(MenuItem.class);
menuItemTester.actionSelectMenuItem( "&File/&New\tAlt+Shift+N/Class",
null, Display.getCurrent().getActiveShell(), 1000);
}
private void findAndTestWizard() throws Throwable{
TestHierarchy hierarchy = new TestHierarchy(Display.getCurrent());
final BasicFinder finder = new BasicFinder(hierarchy);
Button cancelButton=null;
try {
final Widget root = finder.find(new TextMatcher( "New Java Class"));
final Text nameText = (Text) finder.find(new ClassMultiMatcher(Text.class, 4));
final? Button finishButton=(Button)finder.find(root,
new TextMultiMatcher( "&Finish",1,Button.class));
cancelButton=(Button)finder.find(root,new TextMultiMatcher( "Cancel",1,Button.class));
abbot.tester.swt.Robot.syncExec(root.getDisplay(), null, new Runnable() {
public void run() {
TextTester textTester = new TextTester();
textTester.actionEnterText(nameText, "classname");
Robot.delay(1000);
assertFalse(!finishButton.isEnabled());
}
});
} catch (Exception e) {
e.printStackTrace();
throw(e);
}finally
{
WidgetTester.getWidgetTester().actionClick(cancelButton);
}
}
}
以上代码展示了新的测试线程的用法,并且我们在finnaly中退出该向导,可以保证该测试用例发生错误以后其余的测试用例能够继续运行。同时我们捕获的是Throwable,可以保证捕获到Abbot的异常和JUnit的断言失败。
至此一切无懈可击,此时你可以运行新的测试用例,等等,为什么运行以后虽然有异常,测试用例看起来还是成功的呢?请在 openJavaWizard();和assertTrue(wizThread.isSuccess());之间 加入清单12中的代码。
清单12:在测试方法中增加的代码。
while (wizThread.isAlive()) {
WidgetTester.getWidgetTester().actionDelay(100);
}
再次运行,终于成功通过测试,为什么会这样呢?这是因为测试线程的执行还没有完全完成,但是测试方法中的代码已经走到断言部分了,增加了以上的代码,可以保证在UI线程结束后在继续运行当前的测试方法内的代码,这就能保证测试方法内所有的代码(包括线程内的)都能够同步的被执行,有兴趣的读者可以研究一下Abbot的源码,了解详细的情况。如果你需要在一个测试方法中进行多个在新线程中顺序执行的界面测试行为,这种同步显得尤为重要,你都要使用以上的方法保证这些线程在当前线程的控制之下按顺序的执行,同时需要注意代码的写法,以保证所有的错误能够被当前线程捕捉到,真实地反映到当前的测试用例中。
基于以上方法,我们可以编写真正高效的测试用例,真正捕获到任何异常和错误,但是我们还有方法使你的测试用例更为简单。
使用 abbot.swt.eclipse 简化你的插件测试
abbot.swt在abbot的基础上增加了很多SWT的支持,类似的是,Abbot还提供了abbot.swt.eclipse插件为我们的Eclipse插件测试提供了一些便利的功能。abbot.swt.eclipse插件则在abbbot.swt插件的基础上增加了一些实用的测试方法和测试器(图1),极大的方便了对基于SWT的Eclipse插件用户界面的测试。
图1:新增的测试器
上图可以看出,abbot.swt.eclipse插件提供了Abbot SWT插件所没有的对话框测试器,能够方便的执行对话框的测试。同时在“utils”包中还提供了很多的实用类,见图2,
图2,abbot.swt.eclipse的utils包。
这些类的作用一目了然,的确可以帮助我们方便的进行插件的测试,例如,使用类InvokeNewWizard,我们可以很方便的打开新建Java类向导,只要一行语句就可以实现,见表清单13。
清单13:使用InvokeNewWizard打开新建Java类向导
InvokeNewWizard.invoke("Java/Class", Display.getCurrent().getActiveShell());
Abbot 还提供了更好的Abbot TestCase,内置一些测试器以及一些Shell之类Eclipse特有的对象,我们的测试用例可以从“abbot.swt.eclipse.tests.TestCase”派生,可以节省一些工作量。同时在“abbot.swt.eclipse.tests”包中还有很多的测试用例,读者可以学习一下,具有很大的参考价值。
总体上来说,使用abbot.swt.eclipse插件可以方便的进行Eclipse插件的用户界面测试,这里我们在使用方便之余也能体会到Eclipse插件编程思想中的插件分层思想。分层的思想,对于我们编程序和做项目具有很重要的意义,Eclipse的插件编程,严格的将界面、模型、核心、业务逻辑实用工具等分隔在不同的插件中实现,既能简化插件的开发,又能保证功能的简化。比如Abbot中的三个插件,abbot,abbot.swt,abbot.swt.eclipse,三者之间分工明确,协同工作就能完成Eclipse插件的用户界面测试,也可以单独使用。这里需要说明的是,abbot.swt.eclipse这个插件现在还没有太多的功能,不过我们可以预见,一定会为Eclipse插件的测试提供强大的支持。
更上层楼:构建复杂的插件测试用例
目前为止我们已经能够了解到Abbot SWT的全貌,也掌握了Abbot的一些高级用法,现在可以通过一个复杂的测试用例来看看如何在Eclipse环境中使用Abbot开发有效的用户界面测试用例。我们将使用Eclipse自带的一个插件示例作为被测试的插件,我们的测试用例就是要测试该插件的用户界面行为。
创建示例插件
我们将使用Eclipse的插件模板创建一个简单的示例插件,首先新建一个名为SampleView的插件项目,在向导的最后一页选择“Plug-in with a view”,点击“Finish”。该示例的目的是供使用者学习视图的编程和相关的操作,观察SampleView.java,我们发现很多的字符串变量,前面提到Abbot的搜索器和匹配器很大程度上依赖于字符串的匹配,为了便于测试,我们需要一定的重构,主要是字符串放到Message中,然后在测试用例中引用,重构后的代码请见附件。此时你可以启动Eclipse的Runtime,打开“Show View” 对话框,如图 3 所示。
图3:选择“Sample View”
选中“Sample Category”下的“Sample View”来打开该插件的视图,点击确认打开Sample View,该视图是一个简单示例,包含一个简单的Table,如图4。
图4:Sample View
双击table中的item会弹出对话框,在树节点上选择不同的右键菜也会弹出不同内容的对话框。这个就是这个示例插件的主要逻辑,对于初学者,这是一个很好的学习对象。对于我们,则可以用来作为一个待测试的插件,我们将使用Abbot构建一些端到端的测试用例,来测试该视图的功能。为了测试,你还需要添加给AbbotTest 插件添加新的Dependency,也就是SampleView插件。同时,请在SampleView插件的Runtime设置页中将如图的 2 个包作为导出的包,能够被测试插件引用到,如图5。
图5;导出sampleview的包
使用 Abbot 构建复杂的测试用例
通过对被测试对象的简单分析以后,我们需要创建两个测试方法,分别为测试视图中的双击事件和弹出菜单,同时需要一个方法能够打开视图。我们创建一个名为SampleViewTest的测试用例,详细代码请参考附件中的源码,下面分别介绍该测试用例中三个主要的方法。
显示视图
一旦我们运行 JUnit Plug-in Test ,JUnit 会启动一个新的 Eclipse Runtime,我们需要打开待测试的视图,因此清单 14 中的 openSmapleView 方法向我们展示了如何使用 Abbot API 打开一个视图。
清单14:SampleViewTest的openSmapleView方法
protected void openSmapleView() {
final Thread showViewThread= new Thread() {
public void run() {
WidgetTester.waitForFrameShowing( "Show View");
TestHierarchy hierarchy = new TestHierarchy(Display.getCurrent());
final BasicFinder finder = new BasicFinder(hierarchy);
try {
final Widget root = finder.find(new TextMatcher("Show View"));
final Text nameText = (Text) finder.find(new ClassMultiMatcher(Text.class, 1));
abbot.tester.swt.Robot.syncExec(root.getDisplay(), null, new Runnable() {
public void run() {
TextTester textTester = new TextTester();
textTester.actionEnterText(nameText,
Messages.SampleView_MSG_DLG_Title);
Robot.delay(1000);
wt.actionKeyPress(SWT.CR,Display.getCurrent());
Robot.delay(1000);
wt.actionKeyPress(SWT.CR,Display.getCurrent());
}
});
} catch (Exception e) {
e.printStackTrace();
fail(e.getMessage());
}
}
};
showViewThread.start();
wt.actionKey(SWT.ALT+
SWT.SHIFT+
(new Integer('q')).intValue(), Display.getCurrent());
Robot.delay(1000);
wt.actionKey( (new Integer('q')).intValue(), Display.getCurrent());
Robot.wait( new Condition(){
public boolean test() {
return isSampleViewOpened();
}});
}
注意我们用到了上文提到的快捷键的方式来激活显示视图对话框,并使用键盘操作来避免复杂的树操作,注意构件测试器的actionKey方法的用法,通过该方法,你可以方便的进行UI操作,而避免复杂的鼠标操作,对于测试鼠标操作无关的UI,使用快捷键操作可以避免书写冗余和重复的代码。
试着注释线程内的 delay 语句,你将发现,在树节点还未被选中之前按钮动作就被触发,所以界面无法继续下去。因此在使用Abbot时,我们需要树立一个观念,用户界面的打开或者显示绝对不是一瞬间的事情,我们经常会用等待方法保证用户界面在Abbot的指引下顺利的运行。
最后的Robot.wait方法向我们展示了自定义等待条件的用法,我们需要调用该方法确定视图已经被打开(因为视图的打开同样需要时间,如果不等视图完全显示就进行下一次操作,可能会导致错误)。在Abbot测试用例中你会经常使用该方法去等待业务操作的完成,实现一个简单的Condition接口就可以做到。
测试弹出菜单
使用打开视图方法可以打开示例视图,一旦确认示例视图被打开,我们就可以开始测试,清单15展示了弹出菜单的测试方法。
清单15:弹出菜单的测试方法。
public void testPopupMenu()
{
if(isSampleViewOpened()==false)
openSmapleView();
TestHierarchy hierarchy = new TestHierarchy(Display.getCurrent());
final BasicFinder finder = new BasicFinder(hierarchy);
try {
final TableItem item =(TableItem)finder.find(
new TextMultiMatcher(Messages.SampleView_One,1,TableItem.class));
final Menu m = item.getParent().getMenu();
Thread menuClick = new Thread() {
public void run() {
item.getDisplay().syncExec(new Runnable() {
public void run() {
new Robot().keyPress(KeyEvent.VK_ESCAPE);
}
});
}
};
menuClick.start();
// Right mouse click on TreeItem
itemTester.actionClick(item, 1, 1, "BUTTON3");
MenuItem[] aItems = new MenuTester().getItems(m);
Thread actionDialog = new Thread() {
public void run() {
WidgetTester.waitForFrameShowing("Sample View");
try {
final Widget root = finder.find(new TextMatcher("Sample View"));
Label label=(Label)finder.find(root,
new TextMultiMatcher(Messages.SampleView_Action2_executed,
1,Label.class));
assertNotNull(label);
Robot.delay(1000);
wt.actionKeyPress(SWT.CR, root.getDisplay());
} catch (Exception e) {
e.printStackTrace();
fail(e.getMessage());
}
}
};
actionDialog.start();
itemTester.actionSelectPopupMenuItem(aItems[1], 10, 10);
} catch (Exception e) {
e.printStackTrace();
fail(e.getMessage());
}
}
private boolean isSampleViewOpened() {
IWorkbenchPage activePage =
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
if (activePage.getActivePart() instanceof SampleView)
return true;
return false;
}
这里我们使用到了激活鼠标右键的操作:itemTester.actionClick(item, 1, 1, "BUTTON3"),该句语句中"BUTTON3"表示右键,这条语句能够执行右键单击的操作,从而显示右键菜单,类似的操作我们在Abbot的测试中会经常使用到。这里有个奇怪的menuClick线程,该线程的作用是点击键盘上的退出键,读者可以尝试注释该线程的代码,运行测试,观察用户界面的显示,你可以发现界面被阻塞在右键菜单处,无法选中菜单。在显示菜单后,执行menuClick线程就可以避免这种情况的出现。
读者也可以看见,我们会通过root参数缩小查找范围,这里的root就是弹出对话框的Shell。在“wt.actionKeyPress(SWT.CR, root.getDisplay());”语句中你同样需要使用该shell对象,该语句的意思是在弹出的对话框中按下回车键,显然比找到OK按钮,然后点击OK按钮要方便的多。同时因为测试线程只是进行了简单的判空操作,并没有直接执行UI构件的操作,所以我们不需要在使用Robot.syncExec方法执行测试代码,而是将其直接置于线程体内即可。在编写具体的测试用例的时候,我们应当尽量以简单易懂为原则,灵活的使用Abbot API,编写出简洁高效的测试用例。
测试双击事件
测试完右键菜单后,我们再看看如何测试表中的Item的鼠标双击事件,参见清单16的代码。
清单16:测试鼠标双击的方法
public void testDoubleClick()
{
if(isSampleViewOpened()==false)
openSmapleView();
TestHierarchy hierarchy = new TestHierarchy(Display.getCurrent());
final? BasicFinder finder = new BasicFinder(hierarchy);
Thread actionDialog = new Thread() {
public void run() {
try {
final TableItem item =(TableItem)finder.find(
new TextMultiMatcher(Messages.SampleView_One,1,TableItem.class));
assertNotNull(item);
wt.actionClick(item, 6, 3, "BUTTON1", 2);
WidgetTester.waitForShellShowing(Messages.SampleView_MSG_DLG_Title,2000);
final Widget root = finder.find(
new TextMatcher(Messages.SampleView_MSG_DLG_Title));
Label label=(Label)finder.find(new TextMultiMatcher(
NLS.bind(Messages.SampleView_Double_click,
Messages.SampleView_One),1,Label.class));
assertNotNull(label);
Robot.delay(1000);
wt.actionKeyPress(SWT.CR, root.getDisplay());
} catch (Exception e) {
????????? ?? e.printStackTrace();
fail(e.getMessage());
}
}
};
actionDialog.start();
//??safe join
while (actionDialog.isAlive()) {
wt.actionDelay(100);
}
}
双击事件的测试再次向我们展示了鼠标事件的用法,通过“wt.actionClick(item, 1, 1, "BUTTON1", 2)”这行代码,我们可以单击鼠标左键两次,注意如果不加以控制,这两次的点击会引起线程相关的问题,所以我们使用上文论述过的方法来控制代码能够同步执行。也就是我们首先启动测试线程,然后使用while方法等待,测试线程不执行完毕,当前线程就一直等待,这样可以保证的测试线程内的测试动作完全执行完成后再将控制权返回当前线程,继续运行。
本部分的代码展示一个复杂的测试用例中怎样进行一些复杂的测试,完整的测试代码请参见附件。读者也许已经意识到,编写复杂的Abbot测试用例会强迫测试人员对SWT用户界面的运行原理、事件、线程等机制有比较深入的了解,这也从另外一个方面促使你更加深刻的掌握SWT的用户界面编程。因此测试用例对我们的影响是多方面的,除了上文论及的国际化和易访问性,也会对我们的知识的深度进行一次洗礼,使你对被测试的代码和背后的运行原理,有更为深刻的认识。
总结
至此,我们已经能够了解 Abbot 的基本概念和原理,并能理解和执行一些测试用例。我们可以看出 Abbot 整个的逻辑非常的简单,无非是找到一些待测试的构件,执行相关的动作,比较测试的结果。如果组合使用这些简单的逻辑,就能够测试很复杂的界面。对于用户定制的特定的UI,比如使用 gc 直接绘制的构件,或者是一些特定的复合构件可能需要对 Abbot 做相应的扩展,你也可以分析 Abbot 的源代码,扩展相应的测试器来解决这些问题。在 Abbot 的测试中尤其需要注意线程同步的问题,文中我们很多地方都有讨论,读者在自己编写测试用例的时候需要注意。虽然编写端到端的用户界面测试用例比较麻烦,但是熟悉以后,你可以很快的写出高效的测试用例,显著提高你的开发和测试效率。
本文并没有讨论到Abbot的脚本和Swing的Abbot测试。各种各样的脚本在我们的编程中总是起着事半功倍的效果,通常我们使用的各种测试工具都会有脚本的概念,Abbot 也不例外。Abbot使用可以简单的xml文件记录测试过程,并提供了脚本编辑器来编辑和运行,有兴趣的读者可以从Abbot站点获取相关的资料。关于AWT和Swing的测试,Abbot的网站上提供了足够的示例,如果需要的读者可以访问Abbot站点。
从实用的角度来说Abbot插件的功能已经足够应付一般的Swing和SWT的测试,不过其针对Eclipse插件的测试相关的功能还在开发过程中,我们希望尽快能够有发布的版本,能够更全面的支持Eclipse插件的测试。
就像你为所有重要的功能编写了单元测试一样,我们建议你为重要的界面编写测试用例,尤其对于一些数据敏感的动态生成的用户界面,Abbot的测试非常重要。但是,我们不推荐像写单元测试一样写细粒度的用户界面测试,自动化的工具不能完全代替人去执行手工的测试。即使我们有100%覆盖的用户界面测试用例,也不能完全保证用户界面适用于真正的用户,何况,编写100%覆盖的用户界面测试用例是不可能的,UI的外观,易用性,单词的拼写错误,构件的对齐等很多问题,只能由人去测试。
但是Abbot等自动化测试工具的意义仍然非常重要,如同我们展示的一样,对于重复的界面测试,数据或者业务逻辑紧密联系的界面,界面的流程和操作等,使用工具或者脚本能够极大的减少测试人员的工作量。实际项目中,我们更倾向于通过UI的动作来测试业务逻辑,通过端到端的测试用例来实现功能的测试,除了自动化程度高以外,还可以避免代码的改变和业务逻辑的改变带来的测试用例的改变。对于一个设计良好的Eclipse插件,UI的变化频率应该远远的低于业务逻辑的变化(在某种的意义上),因此通过界面去测试业务逻辑是很好的一种选择。选择重要的流程,通过Abbot构建端到端的测试用例,能够覆盖到绝大多数的业务流程,这样的测试经过我们实践的证明是有效的。
http://www.51testing.com/?action_viewnews_itemid_17505_page_5.html
yanglilibaobao收录,使用标签:软件测试,时间:2007-8-6 15:02:46 | 相关网摘,我也收藏
Abbot 基本概念
我们将通过对以上测试用例的分析来向读者展示 Abbot 的基本概念,首先从最简单的部分入手来考察以该测试用例,我们选择 openJavaWizard 方法作为入手点,该方法使用了 MenuItemTester 类的 actionSelectMenuItem 方法,MenuItemTester 是 Abbot 众多的 Tester(我们称之为测试器)的一种,我们称为菜单项测试器,菜单项测试器的 actionSelectMenuItem 方法能够激活菜单栏的菜单项,就像我们鼠标选择该菜单项一样。根据我们的路径参数“&File/&New\tAlt+Shift+N/Class”,菜单项测试器找到我们期望的菜单项 --“新建 Java 类”菜单,从而打开“新建 Java 类”的向导。这里需要注意的是不同版本的 Eclipse 或者不同的透视图下,菜单项的路径是会不一样的,在笔者的 Eclipse 3.2.1 环境中,菜单项如 图 3 所示,因为 Abbot 完全是依赖文本的匹配去寻找 UI 构件,因此,其他情况下以此类推。
图 3. Eclipse 3.2.1 的菜单项
基于 图 3 的菜单项,我们菜单路径中的第二项子菜单才会出现“&New\tAlt+Shift+N”这样的路径,如果读者运行测试时在该方法内遇见“Widget not found ”或者超时的错误,你可能需要检查一下 Eclipse Runtime Workbench 的菜单是否和代码一致(不同版本的可能略有不同),如果不一致,稍加修改菜单项的路径参数就能使测试通过。
这里我们接触到的第一个概念就是 Abbot 的 Tester,我们称之为测试器,测试器用于执行不同的 UI 构件测试,可以用于触发 UI 构件的相应动作,效果和使用鼠标操作界面一样,只不过是由代码触发,这样就可以在测试代码中控制用户界面的行为,达到用户界面自动化测试的目的。如 清单 1 所示,通过使用菜单项测试器,openJavaWizard 方法中简单的两行代码就能打开我们需要测试新建 Java 类向导。
下面再看测试代码的主体部分,也就是 testJavaWizard 方法,读者也许奇怪该方法中代码的写法,我们将其摘录在 清单 2 中。
清单 2:testJavaWizard 的方法体
final Thread wizMain = new Thread() {
public void run() {
WidgetTester.waitForShellShowing( "New Java Class");
findAndTestWizard();
}
};
wizMain.start();
openJavaWizard();
该方法之所以要使用线程,并且使用一种先等待,后触发的怪异方式,主要是因为 SWT 要求对 UI 的操作在新的线程中进行,同时 Eclipse 会启动新的 UI 线程来打开向导,我们很难保证打开向导后刚好能够执行后续的操作,也就很难保证顺序地同步地执行 UI 的测试。使用这种特有的“等待-触发”方式,就能保证测试的顺序满足我们的需求。以上代码展示了我们先启动一个测试线程在等待一个名为“New Java Class”的 shell(也就是新建 Java 类向导),然后执行 openJavaWizard 方法,一旦该方法能打开向导,等待线程就能捕获到,自然就会执行 findAndTestWizard 方法进行对向导的测试。启用线程等待对话框或者向导,然后执行对他们的测试是使用Abbot 时非常常用的一种方法,我们会频繁的在测试方法中使用这样的代码结构。默认情况下,线程的执行是同步的,所以我们不用担心测试用例会在线程运行结束前结束。
了解这一点之后,我们就可以看看findAndTestWizard方法是如何进行测试,如同方法名所暗示的那样,它会先在界面上找到一些UI构件,然后测试这些构件。该方法的前两句(见 清单 3)是初始化一个BasicFinder,这是Abbot的数个Finder中的一种。Finder,我们称为查找器,它需要和匹配器(Matcher)一起协同工作,查找器和匹配器相配合就能在给定的UI阶层体系中寻找到目标构件。
清单3:新建一个查找器
TestHierarchy hierarchy = new TestHierarchy(Display.getCurrent());
final BasicFinder finder = new BasicFinder(hierarchy);
通过清单 3 的代码我们生成一个查找器,通过查找器我们可以找到相应的用户界面的构件,如清单4的代码所示。
清单 4:使用查找器查找 UI 构件
final Widget root = finder.find(new TextMatcher("New Java Class"));
final Text nameText = (Text) finder.find(new ClassMultiMatcher(Text.class, 4));
final Button finishButton=(Button)finder.find(root,
new TextMultiMatcher( "&Finish",1,Button.class));
final Button cancelButton=(Button)finder.find(root,
new TextMultiMatcher( "Cancel",1,Button.class));
找到界面上的构件,就可以让这些构件执行相应的动作,这就需要用到相应构件的测试器,在表 x 所示的测试代码中,我们首先创建文本测试器,然后可以在找到的文本框中输入文本,并创建按钮测试器,判断完成按钮是否被 Disable,然后我们取消该向导,代码如 清单 5。
清单 5:对向导用户界面的测试代码
TextTester textTester = new TextTester();
textTester.actionEnterText(nameText, "classname");
ButtonTester buttonTester= new ButtonTester();
Robot.delay(1000);
assertFalse(finishButton.isEnabled());
buttonTester.actionClick(cancelButton);
用户界面测试的常见的形式包括要判断构件是否正确生成、构件的状态和动作是否符合预定的需求、构件的数据是否满足测试的要求等等。
需要注意的是,SWT 对在线程中访问UI构件有特殊的要求,而我们的测试代码是在一个线程中访问UI的构件,我们需要将这些代码置于 Runnable 的 run 方法体内通过 abbot.tester.swt.Robot.syncExec执行, 否则会抛出“Invalid thread access”的异常。至于“Robot.delay(1000);”这条语句,主要是保证一定的延时,因为 Abbot 的测试执行过快,如果测试者希望亲眼看着测试一步一步的进行,就可能需要多增加一些这样的语句。
此时重新品味一下测试代码,你也许会不以为然(如果你能耐心读完本文,你就不会这样想),Abbot的用法非常的简单,无非是找到一些代测试的构件,执行相关的动作,比较测试的结果,这有何难?实际上这个例子是我们为了演示经过简化的,以上的测试代码虽然很简单,如果组合使用这些简单的逻辑,就能够测试很复杂的界面。就像你为所有重要的功能编写了单元测试一样,我们建议你为所有重要的界面编写测试用例,尤其对于一些数据敏感的动态生成的用户界面,Abbot的测试非常重要。
但是,我们不推荐像单元测试一样写纯界面的测试,在单纯测试UI的同时,你可以通过UI的动作来测试业务逻辑,通过端到端的测试用例来实现业务功能的测试,除了自动化程度高以外,还可以避免代码的改变和业务逻辑的改变带来的测试用例的改变。对于一个设计良好的Eclipse插件,核心的用户界面的变化频率应该远远的低于业务逻辑的变化,因此通过界面去测试业务逻辑是很好的一种选择。选择重要的流程,通过Abbot构建端到端的测试用例,能够覆盖到绝大多数的业务流程,这样的测试能够很好的测试系统的业务功能,并极大的减少重复劳动,提高测试的效率。如果读者使用Eclipse平台开发过RCP业务系统,一定会深有这样的体会。
在深入的掌握 Abbot 的复杂用法之前,我们先来读读 Abbot 的代码,研究一下 Abbot 的原理,这样做符合一个 XP 程序员的风格。
抽丝剥茧:Abbot SWT 的体系结构和工作原理
通过以上的测试用例和概念介绍,你一定已经对 Abbot SWT 有了感性的认识,一个看似简单的测试用例,实际上并不简单,几乎包含了 Abbot 的所有主要内容。现在让我们继续深入下去,看看 Abbot SWT 的体系结构和工作的原理,以便更深刻的了解、掌握和使用它。
Abbot 测试器
我们首先从 Abbot 测试器开始,Abbot 测试器可以帮助我们对各种不同的UI构件进行测试,JavaWizardTest 测试用例使用到了文本测试器(TextTester)、按钮测试器(ButtonTester)、菜单项测试器(MenuItemTester)和通用的构件测试器(WidgetTester)。Abbot 为几乎所有的 SWT 构件提供了相应的测试器,我们可以从图 4 上看到这一点。
图 4:Abbot 提供的所有的 SWT 部件的测试器
从图 4 可以看出,Abbot SWT 的测试器相当的丰富,仅有少数的复合构件及 JFace 构件如 Section、Browser、Dialog 等没有测试类。但是这些复合构件都是由基本的UI构件组成的,我们可以通过简单的UI构件的测试完成符合构件的测试。当然你可能需要研究这些构件的源代码,才能找到组成他们的基本构件,从而使用相应的测试器对其进行测试。例如 对于Section,这个复杂的UI构件,目前Abbot并没有提供“SectionTester”,但Section的标题栏实际上是一个Label, 你可以使用查找器根据Section的标题找到Label,然后调用 LabelTeseter的测试方法actionClick来打开或者关闭一个Section,有兴趣的读者可以试试,这是笔者在实际项目中遇到的一个小问题,其他的问题可以类似的方法解决。
??????? 和几乎所有的JavaGUI 测试工具一样,所有的测试器最后都是通过java.awt.Robot的API去激活用户界面,我们可以通过查看Abbot SWT的源码,最终可以一直追溯到 java.awt.Robot 和RobotPeer,这正是JDK中里面为了方便界面测试提供给程序员的底层API,可见所有的Java图形用户界面测试工具无不是扩展自这个Robot。java.awt.Robot可以生成操作系统原生的事件和消息来创建自动测试,自运行的演示,或者出于其他目的需要让应用来控制鼠标和键盘,用途很是广泛,有兴趣的读者可以深入研究。
Abbot 查找器和匹配器
虽然通过测试器可以发出消息驱动UI测试的完成,但是如果没有查找器的帮忙,你很难获取到你要测试的构件的句柄。我们已经了解到Abbot主要是依靠查找器(Finder)和 匹配器(Matcher)去寻找测试目标,尽管他们在 Abbot的基础插件中已经有了实现,Abbot SWT插件还是要为SWT开发了专门的查找器和匹配器。我们可以通过图 5 了解Abbot SWT的查找器和匹配器的关系:
图5 :Abbot SWT的查找器和匹配器
如图,我们最常用的是BasicFinder和匹配器的配合,通过参数配置,BasicFinder可以支持广度优先和深度优先的查找。
http://www.51testing.com/?action_viewnews_itemid_17505_page_3.html
yanglilibaobao收录,使用标签:软件测试,时间:2007-8-6 15:00:50 | 相关网摘,我也收藏
如今 Eclipse RCP 平台已成为 Java 平台上的富客户端首选,而 SWT 和 JFace 的高效率也让诸多 Java 界面开发者受益匪浅。在插件化已经成为一种潮流的今天,我们迫切需要一种自动化的界面测试工具去测试 Eclipse 的插件。Abbot 框架就是这样一种自动化的UI测试工具,它提供了一系列的能够执行 Swing 或者 SWT 界面测试的 API,并提供了脚本录制和编辑、运行的工具。Abbot 的 SWT 版本是基于 Eclipse 的插件形式发布的,天然的支持了 Eclipse 插件的自动化测试。
本文详细的描述了 Abbot SWT 插件的配置和使用,分析了 Abbot 的体系结构和工作原理,并给出复杂的测试用例来说明 Abbot SWT 的一些高级用法,同时还分享了作者的一些 Abbot 相关的最佳实践,相信会对从事 Eclipse 插件和 SWT 用户界面的开发和测试人员有一定的帮助。
引言
从 JDK 1.3 以来新增加的一个特性就是对图形用户界面自动化测试的支持,通过使用 Java.awt.Robot 类和相关的功能,程序员可以调用 JDK 的 API 可以直接实现用户界面的操作,常被用作用户界面的自动化测试,但是这些底层的 API 使用起来不是很方便,所以 JFCUnit 和 Jemmy 等测试工具都对 Java.awt.Robot 进行了包装,能够在 API 级别直接支持 AWT 和 Swing 的用户界面测试。但是到目前为止,它们尚不支持 SWT。SWT 作为 Eclipse 插件界面开发的首选,正日趋完善和流行,因此大量的程序员在开发 Eclipse 插件和 SWT 的用户界面时,往往需要千百次的点击鼠标去重复的测试图形用户界面,繁琐而且效率低下。
幸运的是,我们有 Abbot,一个可以自动化的测试 SWT 和 Eclipse 插件用户界面的测试框架。Abbot 是 sourceforge.net 站点上的一个优秀 Java GUI 测试框架,最初主要支持 AWT 和 Swing 的用户界面自动化测试,后来随着 SWT 的流行,就增加了对 SWT 的支持。为了与 Eclipse 更为紧密的集合,Abbot 目前已经以 Eclipse 插件的形式开发,Abbot 对 SWT 的支持是通过 Abbot SWT 插件来实现的。目前 abbot 的 SWT 插件尚未正式发布,我们将指引你从 CVS 上获取一个可用版本,目前的代码已经能够支持绝大多数的 SWT 和 JFace 构件的测试。笔者在一个项目中使用了数十个基于 Abbot 的 UI 测试用例,效果良好。实践证明,在 Eclipse RCP 和插件项目中,通过 Abbot 和 JUnit 的结合,构建用户界面的自动化测试用例,可以极大的减少测试人员重复的用户界面测试工作。
本文将带领读者走入 Abbot 的世界,学习使用这一有力的工具来增加我们插件开发的工作效率,在全面的介绍Abbot之前,我们先从一个简单的测试用例,感受一下 Abbot 的非凡魅力。
新手上路:开始一个简单的 Abbot SWT 测试
这一部分将介绍 Abbot SWT 插件的下载和配置,给出一个简单的测试用例使读者对 Abbot SWT 有个感性的认识,然后通过分析该测试用例来介绍 Abbot 的基本概念,使读者能够“观其大略”。本文所有的示例代码都在 Eclipse 3.2.1,JDK1.5 和 JUnit3.8.1 环境下通过测试,请读者先准备好开发环境。
Abbot plugin 测试环境配置
目前 Abbot SWT 的插件尚在开发过程中,没有可用的发行版本,我们将从 CVS 上获取源码。首先启动 Eclipse,指定一个新的空白的 WorkSpace,配置 Abbot 的 CVS 仓库,具体配置信息参见 图 1,使用匿名用户,无需输入密码即可。
图 1:Abbot 项目的 CVS 配置信息
点击确定以后打开 CVS 仓库的 HEAD,同时选中 abbot、abbot.swt 和 abbot.swt.eclipse 三项,点击右键,在弹出菜单中选择“check out”,下载被选中的三个插件项目。
切换到 PDE 透视图,你可以看见 WorkSpace 中的三个 Eclipse 插件,其中 abbot 插件是基础的插件,具有 Abbot 的基础功能和 AWT/Swing 的界面测试功能;abbot.swt 插件是在 abbot 基础上的扩展,增加了对 SWT 的支持;而 abbot.awt.eclipse 则在 abbot SWT 的基础上增加了对 Eclipse 插件测试的支持,比如一些常用的针对 Eclipse 的实用工具、JFace 对话框的测试支持等。
视你的 Eclipse 的环境不同,你可能需要做一定的配置才能保证编译通过,请不要介意那么多的 warning,因为你使用的是正在开发中的源代码。为了便于读者使用,笔者在附件中也附加了这三个插件及其源码,读者也可以直接下载,然后导入到 Eclipse 工作区中。
现在你的工作区中的三个项目已经通过编译了,下面我们将用一个简单的例子来展示 Abbot SWT 的用法和基本概念。
开发一个简单的测试用例
为了简化步骤,我们的测试用例将简单的测试 Eclipse 的新建 Java 类向导,这样做可以避免我们编写被测试代码。我们将创建一个测试插件,并在该插件中创建一个测试用例来执行新建 Java 类向导的界面测试。你可以遵循以下步骤来创建和运行该测试用例。
1. 创建测试插件
在刚刚配置好的包含 Abbot 插件的工作区中新建一个插件项目,命名为 AbbotTest,所有选项采用默认设置,给该插件增加如图 2 的 Dependency:
图 2: AbbotTest 插件的 Dependencies
请确认选用了 JUnit 的 3.8.1 版本,使用更高版本的 JUnit 可能会引起一些问题,本文的测试用例在 JUnit 3.8.1 版本上能够通过所有测试。
2. 在测试插件中创建测试用例
在该插件中新建一个 Java 类,类名为 JavaWizardTest,包名为 abbottest.sample,该类的 Java 代码如表 1 所示。
清单 1. JavaWizardTest.java 的代码
package abbottest.sample;
import junit.framework.TestCase;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Widget;
import abbot.finder.matchers.swt.ClassMultiMatcher;
import abbot.finder.matchers.swt.TextMatcher;
import abbot.finder.matchers.swt.TextMultiMatcher;
import abbot.finder.swt.BasicFinder;
import abbot.finder.swt.TestHierarchy;
import abbot.tester.swt.ButtonTester;
import abbot.tester.swt.MenuItemTester;
import abbot.tester.swt.Robot;
import abbot.tester.swt.TextTester;
import abbot.tester.swt.WidgetTester;
public class JavaWizardTest extends TestCase {
public void testJavaWizard() {
final Thread wizMain = new Thread() {
public void run() {
WidgetTester.waitForShellShowing( "New Java Class");
findAndTestWizard();
}
};
wizMain.start();
openJavaWizard();
}
private void openJavaWizard() {
MenuItemTester menuItemTester = (MenuItemTester) WidgetTester.getTester(MenuItem.class);
menuItemTester.actionSelectMenuItem( "&File/&New\tAlt+Shift+N/Class",null,
Display.getCurrent().getActiveShell(), 1000);
}
private void findAndTestWizard( ) {
TestHierarchy hierarchy = new TestHierarchy(Display.getCurrent());
final BasicFinder finder = new BasicFinder(hierarchy);
try {
final Widget root = finder.find(new TextMatcher( "New Java Class"));
final Text nameText = (Text) finder.find(new ClassMultiMatcher(Text.class, 4));
final Button finishButton=(Button)finder.find(root,
v new TextMultiMatcher( "&Finish",1,Button.class));
final Button cancelButton=(Button)finder.find(root,
new TextMultiMatcher( "Cancel",1,Button.class));
abbot.tester.swt.Robot.syncExec(root.getDisplay(), null, new Runnable() {
public void run() {
TextTester textTester = new TextTester();
textTester.actionEnterText(nameText, "classname");
ButtonTester buttonTester= new ButtonTester();
Robot.delay(1000);
assertFalse(finishButton.isEnabled());
buttonTester.actionClick(cancelButton);
}
});
} catch (Exception e) {
e.printStackTrace();
fail(e.getMessage());
}
}
}
从清单 1 中我们可以看出,JavaWizardTest.java 就是一个普通的 JUnit 测试用例,不过在测试方法里面使用了不少 Abbot 的 API。以上代码可以简单地演示如何测试 Eclipse 的新建 Java 类向导的一些特性,它使用到了 Abbot 提供的一些测试类,并通过启动新的 UI 线程对用户界面进行测试。具体代码的含义,我们将在下文介绍,这里主要希望运行这个测试用例来快速展示一下 Abbot SWT 的简洁用法。
3. 运行该测试用例
在“Package Exploer”视图中右键选中 JavaWizardTest.java,在右键菜单中选择“Run As -> JUnit Plug-in Test”,静静等待片刻。你将看见在新启动的 Eclipse Runtime 界面中,我们的测试用例自动的打开了新建 Java 类向导,并执行了我们预先定义的操作,输入一个类名,JUnit 执行断言,断言通过,取消该向导,测试成功。整个测试过程简单而快速,如果手工去做,可能需要点击多次鼠标和键盘,对于实际应用中复杂的用户界面,重复的手工劳动就不堪胜任。使用 Abbot,你可以轻松地重复执行标准化的测试,这正是自动化测试工具的优点。
以上的例子让我们了解到 Abbot 的用法也是相当简单的,我们只需要在 JUnit 测试用例中增加 Abbot API 的调用,就能捕获界面上的构件(Widget),通过 Abbot API 我们可以触发界面动作,获取用户界面的执行结果,并作出断言来判断界面的运行是否符合我们的期望。就是这个简单测试用例,涵盖了大部分的 Abbot 的基本概念和框架的主要用法,下面我们会逐一介绍。
http://www.51testing.com/?action_viewnews_itemid_17505.html
yanglilibaobao收录,使用标签:软件测试,时间:2007-8-6 14:52:35 | 相关网摘,我也收藏
Web应用程序的验收测试常常涉及一些手工任务,例如打开一个浏览器,并执行一个测试用例中所描述的操作。但是手工执行的任务容易出现操作人员人为的错误,也比较费时间。因此,尽可能将这些任务自动化,以消除人为因素,这是一种很好的做法。于是 Selenium 之类的测试工具就有了用武之地。Selenium 帮助您自动化验收测试,从而可以构建经过更严格测试、因而更为可靠也更易于维护的软件。
验收测试也称黑盒测试和功能测试,是测试和检验应用程序是否能按照涉众(stakeholder)的功能性需求、非功能性需求和其他重要需求来运行的一种方法。验收测试是单元测试和组合测试的补充,后两者通常是使用 xUnit 框架编写的。验收测试也可以使用编程语言来编写,但是 Selenium 和其他类似的工具,例如 Fitnesse,也支持用特定于工具的文档格式编写测试。
验收测试与单元测试和组合测试有以下不同之处:
应用程序是作为一个完整的端到端实体来测试的,而不是像单元测试和组合测试那样,只是测试一个类或一组类。
验收测试是在用户界面(例如一个浏览器)上执行的,而不是在 Web 应用程序界面上执行的。
编写测试用例的人不一定知道应用程序的内部结构,因此也被称作黑盒测试。非技术性用户也可以编写验收测试。
背景知识
在讨论 Selenium 之前,我要介绍关于以下三个话题的一些背景知识,因为这些话题虽然不是本文的主题,但是和本文密切相关:
持续集成
Ajax
Ruby/Ruby on Rails
持续集成
持续集成的目标是自动化构建和测试过程,以便每天自动运行一次或多次这些过程,而不是每个月手动地运行一次。使用持续集成的最大好处是,代码的更改会定期地自动被集成。如果系统受损,没有构建成功,Apache Continuum 和 Luntbuild 之类的持续集成工具可以自动通过发送电子邮件通知团队(见 参考资料)。
Ajax
Ajax 是 Asynchronous JavaScript and XML 的缩写,这是为相当老的技术新创造的一个术语。Ajax 背后的主要思想是,由于只需更新部分页面而不是整个页面,所以 Web 应用程序可以更快地对用户操作做出响应。
Ajax 将更多的复杂性引入到 Web 应用程序中,这一点也反映在测试中。这是因为 Ajax 就像它的名称所表明的那样,使用 JavaScript 和异步 HTTP 请求来更新页面内容。每个浏览器在实现中与其他浏览器相比有一些小小的不同。Selenium 是测试和检测这些差异的很好的工具,因为它在大多数流行的浏览器中都能够运行。
Ruby/Ruby on Rails
Ruby 是一种开放源码的解释型脚本语言,用于快捷、容易地进行面向对象程序设计。它提供了大量的库,而且简单易用,还具有可扩展性和可移植性。该语言是由 Yukihiro “Matz” Matsumoto 创造的,目的是让程序员将更多的注意力放在手头的任务上,摆脱语法的烦恼。
Rails 是由 David Heinemeier Hansson 创造的一种全栈的(full-stack)、开放源码的 Ruby Web 框架。Rails 的目标是使现实中的应用程序编写起来需要的代码更少,并且比 J2EE 和 XML 之类的语言更容易。所有层都能够无缝地一起工作,因此可以使用一种语言编写从模板到控制流乃至业务逻辑的各种东西。Rails 使用 YAML 而不是 XML 配置文件以及注释形式的反射和运行时扩展。这里不存在编译阶段 —— 程序修改后将直接运行。
什么是 Selenium?
Selenium 是 ThoughtWorks 专门为 Web 应用程序编写的一个验收测试工具。据 Selenium 主页所说,与其他测试工具相比,使用 Selenium 的最大好处是:
Selenium 测试直接在浏览器中运行,就像真实用户所做的一样。Selenium 测试可以在 Windows、Linux 和 MacintoshAnd 上的 Internet Explorer、Mozilla 和 Firefox 中运行。其他测试工具都不能覆盖如此多的平台。
使用 Selenium 和在浏览器中运行测试还有很多其他好处。下面是主要的两大好处:
通过编写模仿用户操作的 Selenium 测试脚本,可以从终端用户的角度来测试应用程序。
通过在不同浏览器中运行测试,更容易发现浏览器的不兼容性。
Selenium 的核心,也称 browser bot,是用 JavaScript 编写的。这使得测试脚本可以在受支持的浏览器中运行。browser bot 负责执行从测试脚本接收到的命令,测试脚本要么是用 HTML 的表布局编写的,要么是使用一种受支持的编程语言编写的。
Selenium 适用于以下浏览器:
Internet Explorer Mozilla Firefox Safari
Windows XP 6.0 1.6+, 1.7+ 0.8+, 0.9+, 1.0
Red Hat Linux 1.6+, 1.7+ 0.8+, 0.9+, 1.0+
Mac OS X 10.3 不支持 1.6+, 1.7+ 0.8+, 0.9+, 1.0+ 1.3+
Selenium 命令
通过 Selenium 命令,脚本编写者可以描述 browser bot 在浏览器中所执行的操作。可以将这些命令分成两类 —— 操作(action) 和断言(assertion):
操作模拟用户与 Web 应用程序的交互。例如,单击一个按钮和填写一个表单,这些都是常见的用户操作,可以用 Selenium 命令来自动化这些操作。
断言验证一个命令的预期结果。常见的断言包括验证页面内容或当前位置是否正确。
Selenium 模式
可以按两种模式来使用 Selenium:test runner 和 driven。这两种模式在复杂性和编写方式方面有所不同。driven 测试脚本编写起来往往要更复杂一些,因为它们是用编程语言编写的。但是如果使用 Python 或 Ruby 之类的高级动态编程语言,那么这种复杂性方面的差异就很小。
两种模式之间最大的不同点在于,如果使用 driven 脚本,测试有一部分在浏览器之外运行,而如果使用 test runner 脚本的话,测试是完全在浏览器中运行的。
不管是 test runner 还是 driven 测试用例,都可以与持续集成工具集成。
test runner 模式
Selenium test runner 脚本,也称测试用例(test case),是用 HTML 语言通过一个简单的表布局编写的,如 清单 1 所示。
清单 1. Selenium 测试用例的结构
〈table border="1"〉
〈tr〉
〈td〉First command〈/td〉
〈td〉Target〈/td〉
〈td〉Value〈/td〉
〈/tr〉
〈tr〉
〈td〉Second command〈/td〉
〈td〉Target〈/td〉
〈td〉Value〈/td〉
〈/tr〉
〈/table〉
test runner 脚本通常与所测试的应用程序(AUT)部署在同一个服务器上。这是因为 browser bot 使用 JavaScript 来模拟用户操作。这些脚本在一个受限制的沙箱环境中运行。如果需要绕过这些限制,可以使用一个代理。
test runner 脚本使用与 xUnit 框架相同的测试套件(test suite)和测试用例概念。测试用例和命令按照它们在测试套件和测试用例中出现的顺序依次执行。在 清单 1 中:
第一列包含命令 或断言。
第二列包含命令或断言的目标(target)。这里可以用多种受支持的组件定位符中的一种来指定目标。通常使用的是组件的 ID 或名称,但 XPath 和 DOM 定位符也是受支持的。
第三列包含用于为命令或断言指定参数的值。例如,当使用 type 命令时,这一列可能就是一个文本域所期望的值。
即使对于非技术人员来说,test runner 脚本也易于阅读和编写。当在一个浏览器中打开 清单 1 中的例子时,将得到类似这样的一个表:
First command Target Value
Second command Target Value
接下来,我将描述如何使用命令和断言编写一个简单但是完整的测试用例。
测试用例实例
执行 清单 2 中的测试脚本时,它将执行以下操作:
通过进入 /change_address_form.html 打开变更地址页面。
在 ID 为 address_field 的文本框中输入 Betelgeuse state prison。
单击名为 Submit 的输入区。注意,这里使用 XPath 找到 Submit 按钮,这导致表单数据被发送到服务器。
验证页面是否包含文本 Address change successful。
清单 2. 在测试用例中使用命令和断言的例子
〈table〉
〈tr〉
〈td〉open〈/td〉
〈td〉/change_address_form.html〈/td〉
〈td〉〈/td〉
〈/tr〉
〈tr〉
〈td〉type〈/td〉
〈td〉address_field〈/td〉
〈td〉Betelgeuse state prison〈/td〉
〈/tr〉
〈tr〉
〈td〉clickAndWait〈/td〉
〈td〉//input[@name='Submit']〈/td〉
〈td〉〈/td〉
〈/tr〉
〈tr〉
〈td〉verifyTextPresent〈/td〉
〈td〉Address change successful〈/td〉
〈td〉〈/td〉
〈/tr〉
〈/table〉
测试套件
要达到对应用程序的完全测试覆盖,通常需要不止一个测试用例。这就是 Selenium 使用测试套件的原因。测试套件用于将具有类似功能的一些测试用例编成一组,以便让它们按顺序运行。
测试套件和测试用例一样,都是用简单的 HTML 表编写的。Selenium 执行的缺省测试套件的名称是 TestSuite.html。清单 3 展示了一个测试套件,该套件像通常的用户一样测试应用程序。注意,测试套件使用一个只包含一列的表,表中的每一行指向一个包含某个测试用例的文件。
清单 3. 测试套件示例
〈table〉
〈tr〉
〈td〉Test suite for the whole application〈/td〉
〈/tr〉
〈tr〉
〈td〉〈a href="test_main_page.html"〉Access main page〈/a〉〈/td〉
〈/tr〉
〈tr〉
〈td〉〈a href="test_login.html"〉Login to application〈/a〉〈/td〉
〈/tr〉
〈tr〉
〈td〉〈a href="test_address_change.html"〉Change address〈/a〉〈/td〉
〈/tr〉
〈tr〉
〈td〉〈a href="test_logout.html"〉Logout from application〈/a〉〈/td〉
〈/tr〉
〈/table〉
接下来我将把目光转移到 driven 测试脚本。
driven 模式
driven Selenium 脚本是用多种受支持的编程语言中的一种编写的 —— 目前可用的有 Java、Ruby 和 Python 驱动程序。这些脚本在浏览器之外的一个单独的进程中运行。驱动程序的任务是执行测试脚本,并通过与运行在浏览器中的 browser bot 进行通信来驱动浏览器。驱动程序与 browser bot 之间的通信使用一种简单的特定于 Selenium 的连接语言 Selenese。
driven 脚本比 test runner 脚本更强大、更灵活,可以将它们与 xUnit 框架集成。driven 脚本的缺点(与 test runner 脚本相比)是,这种脚本编写和部署起来更复杂。这是因为驱动程序必须执行以下任务:
启动服务器。
部署所测试的应用程序(AUT)。
部署测试脚本。
启动浏览器。
发送命令到 browser bot。
验证 browser bot 执行的命令的结果。
driven 脚本更依赖于应用程序运行时环境。例如,Java 驱动程序使用一个嵌入式 Jetty 或 Tomcat 实例来部署所测试的应用程序。目前,已经有人在致力于将 Selenium 集成到 Ruby on Rails 中,但是在我撰写本文之际,这个集成版本还没有被发布。
清单 4 摘自一个使用 Ruby 驱动程序的 driven 测试脚本。注意,我省略了用于启动服务器和浏览器的步骤,这个测试脚本代码几乎和 test runner 脚本一样简单。
图 1. 从命令提示符下运行 Ruby on Rails
现实中的用例
在本节中,我将列出示例应用程序的用例。通过这些简化的用例,可以编写模拟用户所执行步骤的验收测试,并验证这些步骤的结果是否与预期相符。股票报价应用程序实现了以下四个用例:
登录
查看股票
查看股票细节
退出
实现这些用例的代码已经编写好了;可以在 app 目录中找到该代码,测试用例在 public/selenium/tests 文件夹中。
登录用例
大多数人都知道登录页面是如何工作的 —— 输入用户名和密码,然后将数据提交到服务器。如果凭证有效,就可以成功登录,并看到受安全保护的资源。在示例应用程序中,这个测试用例包含以下用户操作和断言,必须将它转换成一个 Selenium 测试用例:
单击登录链接。
验证系统是否要求用户进行登录。
输入用户名。
输入密码。
按下登录按钮。
验证是否登录成功。
图 2 展示了用于这些需求的 Selenium 测试用例。注意,我是在运行测试之后截取屏幕快照的。绿色箭头表示成功地通过验证的断言。
图 2. 登录和查看股票测试用例
查看股票测试用例
查看股票页面显示一个公司列表。用于这个页面的测试用例非常简单,所以被包括在前一个测试用例的后面。该测试用例验证当前位置是否为 /main/list_stocks,以及页面是否包含文本 Click on a company name to view details。
查看股票细节用例
查看股票细节用例是在查看股票页面上触发的。用户在一个公司名称上单击鼠标时,就触发了到服务器的一个 Ajax 请求。服务器的响应包括该公司的详细信息,这些信息将插入到当前页面中,而不必重新装载完整的页面。用于这个用例的测试用例执行以下用户操作:
单击公司名称 Acme Oil。
验证页面上是否显示该公司的详细信息。
单击公司名称 Acme Automotive。
验证页面上是否显示该公司的详细信息。
由于使用了 Ajax,请求是异步发生的。在一般的 Web 应用程序中,所有东西通常都是同步的,因此这一点提出了一种不同的挑战。可以像测试其他功能一样来测试 Ajax 功能。惟一的不同是,必须让 Selenium 暂停,等待 Ajax 命令完成。为此,可以使用 pause 命令来等待 Ajax 命令的完成。另外,Joseph Moore 在他最近的 blog 贴中提到,还可以使用 waitForValue 和 waitForCondition 命令代替 pause 命令(见 参考资料)。
图 3 展示了被转换成 Selenium 用例的需求。
图 3. 查看股票细节测试用例
注意 pause 命令:必须使用这些命令,以便等待异步请求完成和更新页面内容。如果没有 500 毫秒的暂停,测试将失败(如 图 4 所示)。
图 4. 失败的查看股票细节测试用例
pause 命令还测试 Ajax 功能的非功能性需求。500 毫秒对于 pause 命令是一个很好的值,因为 Ajax 请求应该快速地执行和完成。可以试着去掉 pause 命令,看看结果如何。如果测试在您的机器上失败,那么试着将这个值增加到 1000 毫秒。
退出用例
退出用例很容易实现,简单来说只有以下两步:
单击退出链接。
验证是否成功退出。
图 5 展示了最后这个测试用例。
图 5. 退出用例
所有测试都被添加到 图 6 左侧显示的缺省测试套件中。
图 6. 示例应用程序的测试套件
执行测试套件
最后要做的是在 Mozilla Firefox 和 Microsoft Internet Explorer 中执行测试套件。为此,在浏览器中打开 http://localhost:3000/selenium/TestRunner.html,然后单击 图 6 中所示的 All 按钮。失败的测试用例和断言将被标记为红色,但是这里,在两个浏览器中所有用例都应该可以成功完成(同样见 图 6)。注意,我使用的是 Mozilla Firefox 1.0.7 和 Internet Explorer 6.0。
还可以单步调试测试套件,这意味着 Selenium 将很慢地执行测试套件,这样当测试套件在浏览器中执行时,就可以看到它的每一步。
结束语
Selenium 是软件工程师、设计人员和测试人员的工具箱中又一个有用且重要的工具。通过将该工具与持续集成工具相结合,团队就可以将验收测试自动化,并构建更好的软件,因为他们可以更容易、更早、更频繁地发现 bug。Selenium 的另一个优点是可以节省时间,使开发人员和测试人员不必将时间花在本可以(也应该)自动化的手工任务上,从而让团队将精力放在更有价值的活动上。
http://testing.csai.cn/testtech/200707262100391020.htm
yanglilibaobao收录,使用标签:.NET,时间:2007-8-6 14:44:05 | 相关网摘,我也收藏
对于许多使用 Microsoftreg; .NET Framework 的开发人员来说,他们印象中认为这类应用程序与可以在该应用程序中使用的库相关联,比如,控制台应用程序不能使用 Windowsreg; Forms 类。其实,这些概念是独立的。当您在 Visual Studioreg; 中创建新的控制台应用程序时,它将应用程序输出类型设置为“控制台应用程序”(等同于给 csc.exe 的 /t:exe 命令行参数)。当您在 Visual Studio 中创建新的 Windows 窗体应用程序时,它将应用程序输出类型设置为“Windows 应用程序”(等同于 /t:winexe),同时添加引用至 System.Windows.Forms.dll。但是很多人没有意识到的是,在控制台应用程序中可以添加对 System.Windows.Forms.dll 的引用,而且与之相反,您可以更改 Windows 窗体项目并编译为“控制台应用程序”。应用程序类型实际只规定是否将为进程启动默认的控制台窗口。
所有这些的意思是,在控制台应用程序中,您可以使用来自 System.Windows.Forms 的类帮助您达成目标。我将创建一个依赖 Windows 窗体类的类使之变得很容易,将用由 winmm.dll 导出的 mciSendString 函数作为此类的测试案例,因为它提供的功能与您在使用时存在困难的 API 相似。同样的解决方案,但是会让您的控制台应用程序能处理各种 Windows 消息。例如,为了利用 Windows Vista; 中提供的一些新的电源管理功能,您可能希望处理 WM_POWERBROADCAST 消息。本专栏中提供的类可以帮您解决这个问题。
我将从我正在进行的设计开始。从 System.dll 导出的 Microsoft.Win32.SystemEvents 类提供了大量静态事件,用于监视各种系统状况。其中一些由广播消息引发。例如,DisplaySettingsChanging 和 DisplaySettingsChanged 事件是由 WM_DISPLAYCHANGE 消息引发。
当 SystemEvents 在用户交互式应用程序中初始化时,如果引发 SystemEvents 初始化的应用程序线程被标记为 STA(单线程单元,即 Windows 窗体应用程序的默认设置),SystemEvents 将预期此线程有一个消息循环,以便 SystemEvents 附带发生。(因此,如果您错误地将某控制台应用程序标记为 STA,而且没有人工弹出消息,就无法引发 SystemEvents 上任何基于消息的事件。)但是,如果初始化线程是 MTA(多线程单元,即控制台应用程序中的默认设置),SystemEvents 将生成自己的线程。该线程将执行一个消息循环。在任何一种情况下,SystemEvents 都会创建一个隐藏窗口,与运行消息循环的线程相关联。此窗口的窗口过程负责处理相关消息和引发相关事件。
由于我的实现不会像 SystemEvents 那么复杂,因此一个与 SystemEvents 类似的基本设计将使一切顺利开始工作。我的 MessageEvents 类提供了三个公共和静态成员:
public static class MessageEvents
{
public static void WatchMessage(int message);
public static event EventHandler
MessageReceived;
public static IntPtr WindowHandle;
...
}
WatchMessage 方法用来通知 MessageEvents 类有关(我作为使用者实际关心的)消息。(相比较而言,SystemEvents 已将这些硬编码到它的实现中。)当收到其中一条消息时,就会引发 MessageReceived 事件;此事件携带一个 MessageReceivedEventArgs 实例,并进而关联到代表所接收消息的相关 System.Windows.Forms.Message 结构。最后,如果 API 的目标是将向其发送消息的特定窗口,MesssageEvents 类将为 MessageEvents 用于接收和处理事件的底层窗口公开句柄。(通常那些使用窗口消息进行通知的 API 也能与 HWND_BROADCAST 或 0xFFFF 配合使用,如果使用后者的话,可以导致通知消息到达我们的底层窗口。)
该底层窗口由 MessageEvents 创建。SystemEvents 能够智能化地决定是否创建一个附加窗口,如果不必这么做就节省资源。与之不同的是,我采用的是总是创建新线程和窗口的简单方法。如果您通过测量数据发现这损害了应用程序的性能,请放心使用必要的附加逻辑适当地扩展类型。
MessageWindow 是 MessageEvents 中的一种私有嵌套类型。它由窗体派生而来,因此也继承了所有控件实例可用的底层 NativeWindow 功能(我可以将 MessageWindow 重新编写得精简一些,并直接操作 NativeWindow,但我还是把它留给您作为练习,如果您觉得合适的话)。MessageWindow 提供了一个公共成员 RegisterEventForMessage,它所做的仅仅将提供的 messageID 存入一个由 Dictionary 表示的消息集。该类也会覆盖 WndProc 方法,后者是对接收消息的窗口做出响应时执行的方法。这种覆盖会检查接收的消息是否在消息集内,并因此判断它是否已是为要引发的事件所注册的消息。如果是,WndProc 引发 MessageReceived 事件。不管如何,所有消息将传递到基本类的 WndProc 实现。
关于如何引发事件,有几个有趣的地方值得注意。首先,MessageReceived 是 MessageEvents 上的一个事件,而非 MessageWindow 的。通常,一个类不能通过直接访问引发另一个类上的事件。这是 C# 编译器玩的一个小技巧。当我声明 MessageReceived 事件时,实际上我在做两件事:声明一个公共事件和声明一个私有委托。公共事件提供 add 和 remove 访问器,这就是为什么任何类都能用事件注册和注销处理程序。私有委托是在我“引发事件”时实际所调用的内容,即使在 C# 源代码中看上去像是我直接调用了事件(有关详细信息,请参见 msdn.microsoft.com/msdnmag/issues/06/11/NETMatters)。由于委托是私有的,因此其他类应该无法对其进行访问。但是,由于 MessageWindow 是 MessageEvents 中的嵌套类,因此 MessageWindow 确实可以访问 MessageEvents 私有实现的详细信息。在这种情况下,MessageWindow 可以访问私有的 MessageReceived 委托,允许它被 MessageWindow 直接调用。
需要注意的另外一点是,事件不会在执行 WndProc 的同一线程上引发。相反,WndProc 在初始化后会使用 MessageEvents 获取的 SynchronizationContext 实例(稍后将详细讨论此问题)。SynchronizationContext 用来在应用程序的同步环境中(不管是什么),通过异步 Post(而不是同步 Send)引发事件。在控制台应用程序中,SynchronizationContext.Post 所做的仅仅是执行 ThreadPool 线程上的委托。在 Windows 窗体应用程序中,WindowsFormsSynchronizationContext.Post 通过将调用封送处理回 UI 线程来执行委托。
MessageEvents 类仅包括静态成员,事实上类本身就声明为静态类(如果尝试添加非静态成员,编译器将报错)。在内部,它将一个静态引用存储到 MessageEvents 初始化时创建的单独的 MessageWindow。
private static MessageWindow _window;
private static IntPtr _windowHandle;
MessageEvents 的两个公共方法的实现十分简单,因为大多数工作都分解成独立的初始化方法。WatchMessage 方法调用 EnsureInitialized,然后将消息 ID 传递给 MessageWindow 的 RegisterEventForMessage 方法。WindowHandle 属性也调用 EnsureInitialized,但只返回初始化时创建的窗口句柄。
public static void WatchMessage(int message) {
EnsureInitialized();
_window.RegisterEventForMessage(message);
}
public static IntPtr WindowHandle {
get {
EnsureInitialized();
return _windowHandle;
}
}
现在我们需要考虑如何实现 EnsureInitialized。EnsureInitialized 负责使 MessageEvents 类开始运行,使之监视和响应所有相关消息。因此,它需要生成新的线程,创建将接收消息的广播窗口以及启动消息循环运行。
EnsureInitialized 仅运行初始化逻辑一次;如果它发现 MessageWindow 已经创建,就放弃运行。如果需要执行初始化,它会从获取与当前线程关联的 SynchronizationContext 开始。它可以使用 SynchronizationContext.Current 达成此目的;但是,如果没有预先配置好环境,SynchronizationContext.Current 将返回空值。实际上,我使用 AsyncOperationManager.SynchronizationContext,它是对 SynchronizationContext.Current 的一个简单包装,如果 SynchronizationContext 不存在,它首先会设置一个默认的;这样,当继续返回 SynchronizationContext.Current 值时,将永远不会返回空值。
配置好环境之后,EnsureInitialized 就会创建一个新的线程。此线程会创建 MessageWindow 并存储它,同时将窗口的句柄存入一个单独的静态成员,如之前所示,该成员用作从 WindowHandle 属性返回的值。此时,窗口打开并运行,我需要启动消息循环运行。为达此目的,我可以编写自己的消息循环,但是利用 .NET Framework 中现有的则更为方便。Application.Run 方法开始在当前线程中运行标准应用程序消息循环,所以我只需要调用该方法即可开始此进程。
以上是我的 MessageEvents 类。要观察它的运行,我将使用前面提到的 winmm.dll 中的 mciSendString 函数。mciSendString 函数是 Windows 中媒体控制接口 (Media Control Interface, MCI) 的一部分,可以用来与各种多媒体设备交互:
[DllImport(“winmm.dll”, EntryPoint = “mciSendStringA”,
CharSet = CharSet.Ansi)]
private static extern int MciSendString(
string lpszCommand, StringBuilder lpszReturnString,
int cchReturn, IntPtr hwndCallback);
要用 mciSendString 播放 WAV 文件,可使用如下所示的典型命令序列:
open “C:\Windows\Media\chimes.wav” type waveaudio alias 12345
play 12345 wait
close 12345
第一个命令会打开指定设备,为其分配一个别名(在本例中是 12345),以便稍后在调用其他命令时引用它。第二个命令告知系统启动设备,在命令完成执行之前不会返回。最后一个命令告知系统关闭设备。但是请注意,我为 MciSendString 创建的 P/Invoke 签名有一个 hwndCallback IntPtr 参数。它与通知标志配合使用,该标志可添加至包括 play(播放)在内的多个命令中。
play 12345 notify
由于还未指定等待标志,此命令将开始播放并立即返回。但是,由于指定了通知标志,因此在命令异步完成的时候,一个 MM_MCINOTIFY 消息会发送至通过该 hwndCallback 过程指定的 HWND。
MciSendString(“play 12345 notify”, null,
0, MessageEvents.WindowHandle);
请注意我如何提供 MessageEvents.WindowHandle 作为目标 HWND。执行该命令之前,我可以配置 MessageEvents 类以接收那些回调消息:
MessageEvents.WatchMessage(MM_MCINOTIFY);
MessageEvents.MessageReceived += delegate(
object sender, MessageReceivedEventArgs e)
{
Console.WriteLine(“Message received: “ + e.Message.Msg);
};
有了这些代码之后,当通知命令完成时,关于结果消息的信息将写入控制台。当然,虽然我并不一定需要 MessageEvents 类在 Windows 窗体应用程序中完成此任务,但它在此类应用程序中也应该同样正常工作,就像在没有自己的消息循环的控制台应用程序中一样。
http://www.enet.com.cn/article/2007/0803/A20070803756369.shtml
yanglilibaobao收录,使用标签:数据库,时间:2007-8-6 14:40:37 | 相关网摘,我也收藏
“竞争对手的技术比我们落后五年。”
为期4天的甲骨文技术与应用大会(Oracle OpenWorld Asia)在上海召开,会上,甲骨文不改其咄咄逼人的气势,从数据库、中间件、应用软件三大业务展示了其实力,并不忌讳评价竞争对手。
迹象表明,甲骨文加紧了对中国的布局。会议期间,甲骨文宣布在上海成立甲骨文亚洲研发中心。这是甲骨文在中国的第三个研发中心,也是亚洲第七个研发中心。同时,甲骨文还宣布在北京成立甲骨文合作伙伴解决方案中心,其目的是“帮助合作伙伴从其他平台迁移到甲骨文的平台”上。
数据库拦截IBM、微软
数据库业务的重要性对甲骨文怎么强调都不为过。携带最新发布的数据库产品11g,甲骨文公司总裁Charles Phillips专程发表了演讲,“甲骨文的技术比竞争对手领先五年。”他说。
7月11日,甲骨文在全球推出了其数据库11g,声称是“甲骨文最具创新性和质量最高的产品”,现在台下坐着8000名听众,无疑是推广11g最好的机会。
作为“发家之本”,数据库在甲骨文的业务中占有很大的比例。同时,作为数据库的老大,甲骨文优势地位虽然一时半会儿不会改变,但却不得不小心后面虎视眈眈的IBM和微软。
IDC数据显示,2006年全球数据库市场规模达到了165亿美元。其中,甲骨文的销售额为73亿美元,同比增长14.7%,占到了44.4%,排名首位;IBM位居第二,其DB2数据库的销售额为35亿美元,同比增长11.9%;微软排名第三,营收额为31亿美元,但涨幅高达25%,市场份额为18.6%;Sybase和NCR Teradata分别列居第四和第五位。
去年夏天,IBM代号“毒蛇”的数据库DB2 9发布,该数据库支持原生XML文档,被看做IBM窥视甲骨文的市场。但当记者问到是否因为受到IBM的压力,才抓紧推出11g时,甲骨文服务器技术执行副总裁Chuck Rozwat不以为然。他认为DB2 9的特点是XML的支持,但目前11g也有类似的功能,但11g的其他功能,DB2 9却没有。他表示,XML不一定是数据库发展的方向,甲骨文将在关系数据库的基础上,创建一些新的模式。
谈到另外一个竞争对手微软,Chuck Rozwat也不在意。虽然微软的SQL Server的优势并不明显,但其低价的策略,对吸引低端用户很有帮助,而且,凭借微软强大的营销能力,SQL Server增长迅猛。微软在软件界强大的实力和野心,都让甲骨文和IBM认为,他们最大的竞争对手不是对方,而是微软。
除了IBM和微软,甲骨文在数据库方面的另外一个潜在的对手是开源数据库My SQL。凭借“开源”的号召力,My SQL受到了很多开源粉丝的拥护。Chuck Rozwat认为,My SQL曾经是可怕的竞争对手,但目前看来难以成气候,因为其没有办法吸引更多的软件开发商在其平台上全身心地给予其支持。但11g率先推出的恰恰是支持Linux的版本,不得不让人联想这是甲骨文对My SQL放出的炮弹。
中间件较劲IBM、 BEA
在中间件领域,IBM是老大、甲骨文是老二、BEA是老三。“但我们的增长率远远超过了他们。很快我们就会超过IBM。”甲骨文Oracle融合中间件副总裁Hasan Rizvi说。根据甲骨文出示的IDC的数据显示,2006年在中国应用服务器市场,甲骨文增长了53%,其两个竞争对手IBM 、BEA分别增长了32%和23%。
甲骨文亚太区Oracle融合中间件销售副总裁Roland Slee 认为,高速的增长来自于亚太区销售队伍的扩大,以及与甲骨文数据库、应用部门的合作。同时,数据库市场不断成熟,对商业智能的需求大大提高,厂商不需要像几年前那样去“教导”市场。而且甲骨文加大了对当地市场的投资,以中国为例,2006年至2007年,甲骨文在中国共建立了8家分公司。
谈到与IBM、BEA的中间件产品的区别,Hasan Rizvi认为,这两个竞争对手的产品都不如甲骨文全面,比如没有“身份管理”等软件。但如何去超越IBM?一直喜欢并购的甲骨文是否会选择并购BEA来超越?对于这样的问题,Hasan Rizvi拒绝做出回应。
有意思的是,甲骨文在应用软件领域的竞争对手SAP,却被其认为是数据库和中间件的合作伙伴。“SAP的所谓的中间件只能算是应用软件。SAP是我们数据库和中间件的客户。有竞争也有合作,这是一个趋势。”
应用软件的旧敌新恨
“我每天都用RSS看SAP的新闻。” 甲骨文应用产品战略高级副总裁Jesper Andersen在演讲时说。在甲骨文的三大业务领域里,应用软件起步最迟,优势也最不明显。在过去的几年里,甲骨文并购了30多家应用软件企业,来补其短板。其中,包括很多著名的并购如Peoplesoft、Sieble等。
但不得不承认,SAP还是这个领域的老大。尽管曾有传闻,甲骨文也要买下它。但用户最为关心的是,甲骨文吃下这些产品后,会继续提供支持吗?Jesper Andersen的回答是“当然”。甲骨文承诺对收购来的产品提供终身的支持服务,同时,甲骨文将在2008年推出“融合管理软件”,该软件包括了甲骨文所有产品的功能。为推动此项工作的进展,甲骨文还专门成立了融合项目组。
除了SAP,让甲骨文坐卧不安的还有像Salesforce.com这样新兴的以Saas为模式的厂商。Salesforce.com本是甲骨文原高管离职创办的,不曾料到有一天会与甲骨文形成竞争。
目前,除了以传统的方式卖软件外,甲骨文也涉足了托管型软件领域,即Saas模式领域。但是,6月份,Salesforce.com的亚太区负责人接受本报记者采访时曾抨击,传统软件公司转型做Saas,前景并不客观,因为面临着销售模式、运作方式的全新改革。
当然,Jesper Andersen也对此进行了回应。“Salesforce.com他们只有那么几招,而我们有很多招。既可以帮助客户内部部署,也可以托管。我们的Sieble CRM-ON Demand 比他们的还好。”
所谓树大招风,涉足的领域越多,竞争对手也将越多。甲骨文从来不惧怕对竞争对手的攻击,同样,它也会越多招致竞争对手的回击。
http://www.enet.com.cn/article/2007/0806/A20070806759987.shtml
yanglilibaobao收录,使用标签:数据库,时间:2007-8-6 13:38:52 | 相关网摘,我也收藏
近日Campware公司已经发布了Cream系统的一个新版本,Cream是一个适合媒体公司的开源客户关系管理(CRM)系统。新版本使用MySQL数据库,而且有很多其他的新功能和改变。
自从在2003年发布1.0版以来,Cream已经变得越来越复杂。3.0版现在开始使用MySQL数据库,并且与了Campware公司的旗舰Capmpsite多语言发布系统来管理客户的订阅服务。
在这个最新版中还包括:联系人、任务和机会管理等功能,还有外部HTML表单支持和信息请求功能;几个新的表单域和屏幕视图选项;分页列表和一个新的用户界面。
对于小型媒体公司来说,Cream是一个充满特色功能的系统,它提供了很多有用的功能。它可以跟踪销售订单、支付、发货、服务和在线打印订阅服务等功能,另外还提供用户界面友好的报表和分析工具来策略推广活动的效率。Cream可以使客户更容积的进行交流,它包括一个包括进出电子邮件、Web表单、基于模板的电子邮件和一个所见即所得(WYSIWYG)的编辑器。
据Campware的母公司MDLF表示,Cream具有一个非常具有针对性的目标市场,即独立的新闻媒体组织。不过实际上在其目标市场之外的很多公司也使用它。
除了Cream CRM和Campsite发布系统以外,Campware还提供一个针对打印出版的分布式管理系统Dream和一个无线电管理应用程序Campcaster。
http://tech.it168.com/m/2007-08-03/200708031951734.shtml
yanglilibaobao收录,使用标签:.NET,时间:2007-8-6 13:33:38 | 相关网摘,我也收藏
两个“属性”引起的歧异-property和attribute的区别
这虽然没有“一个馒头引发的血案”那么严重,但是也足以成为.net程序员的技术盲点之二。
对于property和attribute这两个名词都叫“属性”的问题,来源于国内it书籍翻译界的疏忽。
其实它们来源于两个不同的领域,attribute属于OOA/OOD的概念,而property属于编程语言中的概念。下面我们来说明它们的异同。
Attribute
Attributes是Microsoft .NET Framework文件的元数据,可以用来向运行时描述你的代码,或者在程序运行的时候影响应用程序的行为。
Property
属性是面向对象编程的基本概念,提供了对私有字段的访问封装,在C#中以get和set访问器方法实现对可读可写属性的操作,提供了安全和灵活的数据访问封装。关于属性的概念,不是本文的重点,而且相信大部分的技术人员应该对属性有清晰的概念。以下是简单的属性
区别
可以说两者没有可比性,只不过我们国家的语言特点才引起的歧异,其实只要记住Attributes是派生于System,Attributes类之下,它的主要作用是描述,比如在某一个自定义控件中的“属性”就是指Attribute,
如
[DllImport("User32.dll")]
Attribute也有很多系统的“默认”属性,见下表
预定义的属性 有效目标 说明
AttributeUsage Class 指定另一个属性类的有效使用方式
CLSCompliant 全部 指出程序元素是否与CLS兼容
Conditional Method 指出如果没有定义相关联的字符串,编译器就可以忽略对这个方法的任何调用
DllImport Method 指定包含外部方法的实现的DLL位置
STAThread Method(Main) 指出程序的默认线程模型为STA
MTAThread Method(Main) 指出程序的默认模型为多线程(MTA)
Obsolete 除了Assembly、Module、Parameter和Return 将一个元素标示为不可用,通知用户此元素将被从未来的产品
ParamArray Parameter 允许单个参数被隐式地当作params(数组)参数对待
Serializable Class、Struct、enum、delegate 指定这种类型的所有公共和私有字段可以被串行化
NonSerialized Field 应用于被标示为可串行化的类的字段,指出这些字段将不可被串行化
StructLayout Class、struct 指定类或结构的数据布局的性质,比如Auto、Explicit或sequential
ThreadStatic Field(静态) 实现线程局部存储(TLS)。不能跨多个线程共享给定的静态字段,每个线程拥有这个静态字段的副本
而Property是指编程过程中的字段,也即类的成员。
如:
private int hour; //定义私有变量表示"小时",外部是访问不到的.}
public int Hour// 定义Hour程序接口
{
set { hour=value; }
get { return hour;}
}
http://blog.csdn.net/edisundong/archive/2007/08/05/1727760.aspx
yanglilibaobao收录,使用标签:.NET,时间:2007-8-6 13:31:49 | 相关网摘,我也收藏
C#中有三个关键字-ref,out ,params,虽然本人不喜欢这三个关键字,因为它们疑似破坏面向对象特性。但是既然m$把融入在c#体系中,那么我们就来认识一下参数修饰符ref,out ,params吧,还有它们的区别。
NO.1 params
一个可以让方法(函数)的拥有可变参数的关键字。
原则:在方法声明中的 params 关键字之后不允许任何其他参数,并且在方法声明中只允许一个 params 关键字。
示例(拷贝到vs2005中即可用,下面不再说明)
public partial class Form1 : Form
...{
public static void UseParams(params int[] list)
...{
string temp = "";
for (int i = 0; i < list.Length; i++)
temp = temp +" " +list[i].ToString();
MessageBox.Show(temp);
}
public static void UseParams2(params object[] list)
...{
string temp = "";
for (int i = 0; i < list.Length; i++)
temp = temp + " " + list[i].ToString();
MessageBox.Show(temp);
}
public Form1()
...{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
...{
UseParams(1, 2, 3);//看参数是3个
UseParams(1, 2); //看参数是2个,可变吧
UseParams2(1, 'a', "test");
int[] myarray = new int[3] ...{ 10, 11, 12 };
UseParams(myarray); //看也可以是容器类,可变吧:)
}
}
NO.2 out
这是一个引用传递L。
原则一:当一个方法(函数)在使用out作为参数时,在方法中(函数)对out参数所做的任何更改都将反映在该变量中。
原则二:当希望方法返回多个值时,声明 out 方法非常有用。使用 out 参数的方法仍然可以返回一个值。一个方法可以有一个以上的 out 参数。
原则三:若要使用 out 参数,必须将参数作为 out 参数显式传递到方法。out 参数的值不会传递到 out 参数。
原则四:不必初始化作为 out 参数传递的变量,因为out 参数在进入方法(函数)时后清空自己,使自己变成一个干净的参数,也因为这个原因必须在方法返回之前为 out 参数赋值(只有地址没有值的参数是不能被.net接受的)。
原则五:属性不是变量,不能作为 out 参数传递。
原则六:如果两个方法的声明仅在 out 的使用方面不同,则会发生重载。不过,无法定义仅在 ref 和 out 方面不同的重载。例如,以下重载声明是有效的:
class MyClass
{
public void MyMethod(int i) {i = 10;}
public void MyMethod(out int i) {i = 10;}
}
而以下重载声明是无效的:
class MyClass
{
public void MyMethod(out int i) {i = 10;}
public void MyMethod(ref int i) {i = 10;}
}
有关传递数组的信息,请参见使用 ref 和 out 传递数组。
示例附后
NO.2 ref
ref仅仅是一个地址!!!
原则一:当一个方法(函数)在使用ref作为参数时,在方法中(函数)对ref参数所做的任何更改都将反映在该变量中。
原则二:调用方法时,在方法中对参数所做的任何更改都将反映在该变量中。
原则三:若要使用 ref 参数,必须将参数作为 ref 参数显式传递到方法。ref 参数的值可以被传递到 ref 参数。
原则四:ref参数传递的变量必须初始化,因为ref参数在进入方法(函数)时后还是它自己,它这个地址指向的还是原来的值,也因为这个原因ref参数也可以在使用它的方法内部不操作。
原则六:如果两种方法的声明仅在它们对 ref 的使用方面不同,则将出现重载。但是,无法定义仅在 ref 和 out 方面不同的重载。例如,以下重载声明是有效的:
class MyClass
{
public void MyMethod(int i) {i = 10;}
public void MyMethod(ref int i) {i = 10;}
}
但以下重载声明是无效的:
class MyClass
{
public void MyMethod(out int i) {i = 10;}
public void MyMethod(ref int i) {i = 10;}
}
有关传递数组的信息,请参见使用 ref 和 out 传递数组。
示例
public static string TestOut(out string i)
...{
i = "out b";
return "return value";
}
public static void TestRef(ref string i)
...{
//改变参数
i = "ref b";
}
public static void TestNoRef(string refi)
...{
// 不用改变任何东西,这个太明显了
refi = "on c";
}
public Form1()
...{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
...{
string outi;//不需要初始化
MessageBox.Show(TestOut(out outi));//返回值
//输出"return value";
MessageBox.Show(outi);//调用后的out参数
//输出"out b";
string refi = "a"; // 必须初始化
TestRef(ref refi); // 调用参数
MessageBox.Show(refi);
//输出"ref b";
TestNoRef(refi);//不使用ref
MessageBox.Show(refi);
//输出"ref b";
}
http://blog.csdn.net/edisundong/archive/2007/08/03/1724678.aspx
yanglilibaobao收录,使用标签:Java,时间:2007-8-6 13:19:30 | 相关网摘,我也收藏
现在炒得正热的Ajax其实是一种新瓶装旧酒的过渡技术,相信未来一到两年之内将被新的技术所代替,它解决问题的方法与手段很难形成一种可高度抽象的框架级解决方案,而JSF则是一种可扩展的框架级解决方案。在J2EE下一代规范Java EE 5.0中,JSF被放到了一个非常重要的地位,J2EE社区试图通过JSF来统一Web应用的开发模式与方法。作为JCP组织成员的金蝶中间件,即将推出中国人作出的Apusic JSF…
1.前言
在J2EE下一代规范Java EE 5.0中,JSF(Java Server Faces)技术被放到了一个非常重要的地位。J2EE社区试图通过JSF来统一Web应用的开发模式与方法。
相对于传统的基于JSP/Servlet的开发模型,JSF能够带来许多好处,譬如:
可定制的丰富的UI组件
良好的事件响应机制
表达式语言(Expression Language)
表单数据的自动转换与验证
基于MVC的框架模型等等...
但同时我们也不得不看到,业界常见的JSF引擎普遍存在着一些缺陷与不足(并不是说JSF技术模型本身的局限,而是常规的实现机制所产生的一些问题),而这些不足将可能对JSF成为日后Web应用主流开发技术带来一定的负面影响,包括:
常规的JSF实现机制,运行期性能并不是非常理想
缺少良好的工具支持
作为JCP组织成员,金蝶中间件对待JSF技术又是如何思考的?是否打算对JSF进行全面支持?又是通过怎样的解决方案来克服上述不足的呢?
2.JSF与Ajax
一谈到Web开发技术,就不得不提Ajax。这是目前在整个IT界都红得发紫的概念了。在这里我想没必要再去阐述一遍什么是Ajax,但我们可以对Ajax有一个基本的认识。
Ajax其实是一种新瓶装旧酒的技术,它的好处是通过Java Script与DHTML,提供了一种异步编程模型,从而使我们的Web应用给客户带来更好的人机体验。但Ajax解决问题的层面很低;或者说,它解决问题的方法与手段,很难形成一种可高度抽象的框架级解决方案,而JSF则是一种可扩展的框架级解决方案。
事实上,我认为Ajax是一种过渡技术,相信在未来一到两年之内将被新的技术所代替,是微软的XAML、Mozilla的XUL、还是任何可能的RIA标准,实际上整个业界都在观望。但不管采用什么技术,JSF都能适应,对JSF来说适应一个新技术只是更换一个Render Kit而已。举一个例子,如果想在网页中实现图表功能(Chart),MSIE有VML,Gecko和Opera有SVG,而在服务器端只需要简单地判断一下浏览器类型就可以选择一个Render Kit生成不同的markup来完成相同的功能,这是用常规JSP技术很难完成的任务。
3.Apusic JSF:中国人做出的JSF引擎
3.1 容器级别的Ajax支持
目前有JSF + Ajax这种思路的,恐怕也不是金蝶中间件一家,但很多第三方的JSF + Ajax实现是提供一个组件库以及一个附加的Servlet 或Filter来处理Ajax请求,而我们是直接由JSF容器来处理Ajax请求的。我们会根据请求的类型来判断这是一个正常的HTTP请求还是一个 Ajax请求。如果是一个常规HTTP请求就运行JSP页面,生成页面文档(并且我们会在生成的页面文档中嵌入Ajax所必须的Java Script代码,后文提及);如果该请求是一个Ajax请求,服务器对请求参数正常解码,并执行JSF中除页面输出阶段以外的所有其他阶段,这时将生成一个JSF的组件树,遍历该组件树,从中找出发生变化的数据,并将这些数据打包成一个Ajax应答,并由客户端来更新这些修改的数据,甚至改变页面外观。
在JSF规范中,JSF页面输出阶段所采用的Render Kit是可替换的,默认的HTML_BASIC Render Kit输出的是标准的HTML语法,不包含任何Java Script代码,Apusic JSF引擎实现了一个 AJAX Render Kit,可以在HTML文档中嵌入Java Script代码来实现Ajax特性,而替换Render Kit只需要修改配置文件即可。
那么,这样能够带来怎样的好处呢?
3.2 提升JSF运行性能,带来更好的人机体验。
常规JSF容器在状态维护方面的通用做法是:基于Session的,或者基于请求传递型的,这就意味着,当每进行一次客户端与服务器端的响应时,都需要把所有的状态传来传去,这无疑会对系统的运行性能带来较大的负面影响,从而使开发人员误以为:JSF是一种重量级的技术模型。
而通过Apusic JSF的Ajax特性,我们能够只把发生变化的数据打包成Ajax请求发送给服务器端,而服务器端也只会将发生变化的数据打包成Ajax应答,从而大大提升系统的运行效率。并且,传统的JSF请求应答将刷新整个页面,而Apusic JSF将只更新发生变化的客户端组件,从而给客户带来更好的人机体验。
3.3 简化Ajax的开发
以前要写Ajax应用要写很多Java Script代码,开发、调试、维护这些 Script脚本都相当烦琐,如果采用Apusic JSF技术,你不再需要编写任何Java Script代码,引擎已经帮你生成了这些代码。Apusic JSF所有的标准组件都是支持Ajax的,某些第三方组件可能本身并不支持 Ajax,但使用一个名为的标签,可以立即将这个第三方组件转换成Ajax Enabled。例如Apache myfaces的Tomahawk项目提供了一个Tree组件,这个组件本身并不支持Ajax,每当按下一个Tree结点都将重新刷新整个页面,使用标签后则只刷新Tree部分,而不刷新页面的其他部分。当然更好的方式是提供一个本身就支持AJAX的Tree组件,以减少冗余数据的传递。
Apusic JSF还提供了一个名为的标签,可以接受在发送和完成一个Ajax请求时触发的事件,缺省的实现是在发送 Ajax请求之前显示一个HTML片段,在完成Ajax请求之后显示另一个HTML片段,这些HTML片段可以包含文字和图片。更高级的用法是可以设置 标签的onstart和onstop属性,在开始和完成Ajax请求时执行一段Java Script代码以实现更复杂的效果。
此外,我们还实现了一个 标签,可以以RPC方式调用服务器端Java对象的某个方法。
3.4 其它特性
采用Apusic JSF还包含其它一些差异化特性,包括:控件的换肤功能,控件对IE、Mozilla(Firefox)、Opera等多浏览器的支持,以及强大的布局功能等。
在〈图一〉中所展示的是通过Apusic JSF生成的完全基于HTML, CSS, Java Script的界面,它们和一个真正的窗口系统几乎完全一样,包括移动位置、改变堆叠顺序、以及模式对话框等,但实际上他们都是“假”窗口。另外需要着重指出的是,这里完全采用CSS布局,避免了旧有的依赖于HTML Table嵌套的布局方式,从而使我们的程序具有更好的可维护性。针对这样一个界面,我们可以观察一下具体的实现代码,以〈图一〉中的Main Window为例:
〈w:window jsvar="mainWnd" label="Main Window" show="true"
left="0" top="0" width="500" height="350"
contentPaneStyleClass="normal-window"
showIcon="true" showMinimize="true" showMaximize="true" showClose="true"
onclose="window.location='index.html'"〉
〈h2〉Main Window〈/h2〉
〈p〉
〈button onclick="createWindow()"〉Create Window〈/button〉
〈button onclick="dlgTest.show()"〉Show Dialog〈/button〉
〈/p〉
〈%@ include file="/WEB-INF/apusicjsf.inc" %〉
〈/w:window〉
我们发觉,通过Apusic JSF,实现上述UI界面其实并不复杂,只需要通过简单的几行代码,就能够完成复杂的UI界面。
Apusic JSF还支持控件的换肤功能。以上述界面为例,我们可以查看Mac OS风格的界面样式,如图二所示。
4.开发工具的支持:Apusic Studio
在JSF规范中,有这样一句话:“JSF is designed to be tooled”。换言之,JSF规范从设计初开始,就非常强调对工具支持的依赖。幸运的是,目前对JSF提供支持的开发工具也逐渐丰富了起来,包括Oracle JDeveloper、Sun Java Studio Creator等等。金蝶中间件也同样提供了一个基于Eclipse的集成式开发环境:Apusic Studio,并通过该工具,能够给JSF的开发带来良好的支持,包括:语法加亮、代码辅助、断点调试、可视化的设计等等。
http://java.ccidnet.com/art/3539/20070803/1167005_1.html
yanglilibaobao收录,使用标签:数据库,时间:2007-8-6 13:15:30 | 相关网摘,我也收藏
Auto Memory Management是Oracle10g提出来的一个新特性,在最新的Oracle11g数据库中又得到了进一步的发展。通过使用自动内存管理,Oracle数据库中的PGA和SGA内存之间可以互相转换,根据当前的工作负载来自动设定Oracle内存区域中的PGA和SGA的大小。这种间接的内存转换依赖于操作系统的共享内存的释放机制来获得内部实例的调优。目前这种技术可以应用于Linux, Solaris, HPUX, AIX 和Windows等操作系统上。
首先我们来回顾下Oracle10g的自动内存管理特性。在Oracle10g的数据库中,只有SHARED_POOL_SIZE、DB_CACHE_SIZE、LARGE_POOL_SIZE、JAVA_POOL_SIZE、STREAMS_POOL_SIZE五个SGA组件可以被自动调整,其中PGA的最大值由初始化参数PGA_AGGREGATE_TARGET决定,SGA的最大值由初始化参数SGA_TARGET决定。
在Oracle11g数据库中,使用自动内存管理特性不再需要设定参数PGA_AGGREGATE_TARGET和SGA_TARGET,因为这两个参数都已经被修改成自动调优的,除非想指定PGA和SGA的最小值才需要设定这两个参数。在Oracle11g数据库中,则需要设置一个叫做MEMORY_TARGET的初始化参数,这个参数是指整个Oracle实例所能使用的内存大小,包括PGA和SGA的整体大小,在MEMORY_TARGET的内存大小之内,PGA和SGA所用的内存可以根据当前负载情况自动相互转换。如果当初始设定的MEMORY_TARGET的内存不够当前数据库使用的时候,Oracle11g还提供了另外一个初始化参数MEMORY_MAX_TARGET,当原始设定的内存不够使用的时候,可以手工来动态 调节MEMORY_TARGET的大小,但是不允许超过MEMORY_MAX_TARGET的值。下面这张图简单明了的描述出了Oracle11g数据库内存大小的设定参数。
此外,Oracle11g数据库还提供了几个用于监控自动内存管理的视图:
V$MEMORY_DYNAMIC_COMPONENTS:描述当前所有内存组件的状态
V$MEMORY_RESIZE_OPS:循环记录最后800次的SGA大小调整请求
X$KMGSTFR:循环记录最后800次的SGA的转换地址
_MEMORY_MANAGEMENT_TRACING=23:对于所有的内存转换调整行为均记录保存为跟踪文件
http://tech.it168.com/db/2007-08-06/200708060924875.shtml
yanglilibaobao收录,使用标签:Java,时间:2007-8-6 13:10:23 | 相关网摘,我也收藏
对于任何一个完整的应用系统,完善的认证和授权机制是必不可少的。Acegi Security(以下简称Acegi)是一个能为基于Spring的企业应用提供强大而灵活安全访问控制解决方案的框架,Acegi已经成为Spring官方的一个子项目,所以也称为Spring Security。它通过在Spring容器中配置一组Bean,充分利用Spring的IoC和AOP功能,提供声明式安全访问控制的功能。虽然,现在Acegi也可以应用到非Spring的应用程序中,但在Spring中使用Acegi是最自然的方式。
Acegi可以实现业务对象方法级的安全访问控制粒度,它提供了以下三方面的应用程序的安全:
? URL资源的访问控制
如所有用户(包括其名用户)可以访问index.jsp登录页面,而只有授权的用户可以访问/user/addUser.jsp页面。Acegi允许通过正则表达式或Ant风格的路径表达式定义URL模式,让授权用户访问某一URL匹配模式下的对应URL资源。
? 业务类方法的访问控制
Spring容器中所有Bean的方法都可以被Acegi管理,如所有用户可以调用BbtForum#getRefinedTopicCount()方法,而只有授权用户可以调用BbtForum#addTopic()方法。
? 领域对象的访问控制
业务类方法代表一个具体的业务操作,比如更改、删除、审批等,业务类方法访问控制解决了用户是否有调用某种操作的权限,但并未对操作的客体(领域对象)进行控制。对于我们的论坛应用来说,用户可以调用BbtForum#updateUser(User user)方法更改用户注册信息,但应该仅限于更改自己的用户信息,也即调用BbtForum#updateUser()所操作的User这个领域对象必须是受限的。
Acegi通过多个不同用途的Servlet过滤器对URL资源进行保护,在请求受保护的URL资源前,Acegi的Servlet过滤器判断用户是否有权访问目标资源,授权者被开放访问,而未未被授权者将被阻挡在大门之外。
Acegi通过Spring AOP对容器中Bean的受控方法进行拦截,当用户的请求引发调用Bean的受控方法时,Acegi的方法拦截器开始工作,阻止未授权者的调用。
对领域对象的访问控制建立在对Bean方法保护的基础上,在最终开放目标Bean方法的执行前,Acegi将检查用户的ACL(Aeccess Control List:访问控制列表)是否包含正要进行操作的领域对象,只有领域对象被授权时,用户才可以使用Bean方法对领域对象进行处理。此外,Acegi还可以对Bean方法返回的结果进行过滤,将一些不在当前用户访问权限范围内的领域对象剔除掉——即传统的数据可视域范围的控制。一般来说,使用Acegi控制数据可视域未非理想的选择,相反通过传统的动态SQL的解决方案往往更加简单易行。
从本质特性上来说,Servlet过滤器就是最原始的原生态AOP,所以我们可以说Acegi不但对业务类方法、领域对象访问控制采用了AOP技术方案,对URL资源的访问控制也使用了AOP的技术方案。使用AOP技术方案的框架是令人振奋的,这意味着,开发者可以在应用程序业务功能开发完毕后,轻松地通过Acegi给应用程序穿上安全保护的“铁布衫”。
http://tech.it168.com/j/2007-05-22/200705221448921.shtml
yanglilibaobao收录,使用标签:数据库,时间:2007-8-6 13:03:31 | 相关网摘,我也收藏
7月30日,甲骨文全球大会亚太站(Oracle OpenWorld Asia Pacific)在上海开幕。
此次会议包括50多场互动展示活动、160多场分会议,共有8000多名来自中国政府、客户、合作伙伴、学术机构及开发人员的代表出席。这将成为甲骨文全球大会亚太站历史上规模最大的一次盛会。
此前,甲骨文全球大会曾分别于2002年在北京和2004年在上海召开。这是第三次在中国召开,也是第二次在上海召开。大会历时4天,至8月2日结束。
出席此次会议的高层包括甲骨文总裁Charles Phillips、执行副总裁Chuck Rozwat、高级副总裁Jesper Andersen以及副总裁Hasan Rizvi。
甲骨文公司亚太区高级副总裁Brian Mitchell表示:“在上海举办全球大会,是甲骨文进入中国18年来又一重大战略性投资。未来,甲骨文将继续致力于在商业和教育项目以及本地研发设施上持续投资,促进中国软件业发展。”
本次大会有60多家甲骨文的合作伙伴出席,包括神州数码、日立、惠普、埃森哲、EMC和东软。有趣的是,此次会场出现了甲骨文最大竞争对手SAP的展台。
甲骨文全球大会亚太站上出现SAP展台
分析人士表示,这个现象体现了软件厂商之间微妙的竞合关系。尽管SAP和甲骨文并不存在合作伙伴关系,但作为最大的竞争对手,两家厂商并不能忽视对方的产品和技术。另外,甲骨文的这种高姿态也是对于其“Off SAP”战略的又一种诠释。
http://tech.it168.com/db/2007-08-06/200708060916796.shtml
yanglilibaobao收录,使用标签:数据库,时间:2007-8-6 11:31:21 | 相关网摘,我也收藏
1. JDBC的介绍。
JDBC是Java的开发者——Sun公司制定的Java数据库连接(Java Data Base Connectivity)技术的简称,是为各种常用数据库提供无缝联接的技术。在Web和Internet应用程序中的作用和ODBC在Windows系列平台应用程序中的作用类似。同时JDBC和ODBC也可以称之为数据连接中间件,现在JDBC被融合在J2EE的框架中,简化了J2EE框架下应用开发过程中对数据库的调用和移植。JDBC对于现在主流的数据库都可以支持,例如:MySQL、PostgreSQL(两个开源产品)和DB2、Oracle、SQL Server、Sybase(商业化产品)等。
使用JDBC来完成对数据库的访问包括以下四个主要组件:Java或J2EE的应用程序、JDBC驱动管理器、JDBC驱动程序(以jar为后缀的Java类库)和数据源。SUN公司在设计JDBC时只定义了统一标准的SQL数据存取接口。程序员开发应用时,只需要按照标准的接口访问数据,而不需要关心后台的数据库由谁提供,从而屏蔽了数据库的差异性,减少了应用与数据库的藕合,增强了应用本身的可扩展性和可移植性。提供这种可移植性的正是JDBC的驱动程序,这个部分一般由DBMS厂商提供,应用软件只需要将提供的JAR包加载到系统中就可以实现对该数据库访问的支持。
Apusic V4.0.3不仅提供了JDBC的支持,还提供了对最新规范JDBC 3.0的支持,从而为JDBC开发中数据库连接提供了连接池管理。只需要在JNDI中配置JDBC连接,就可以自动享受连接池管理带来的好处,避免在应用中通过JDBC直接访问时因为创建和关闭连接带来的资源消耗。同时,还为数据库连接提供了自动检测与失效恢复技术,当程序代码中使用连接后没有返回给连接池,系统会检测到这样的失误并自动完成连接回收工作,从而避免了软件开发中的失误,增强了应用的健壮性。
2. JDBC的使用。
JDBC的使用主要有三种:直接通过JDBC与数据库连接;客户端远程通过iiop协议由Apusic应用服务器的JNDI-〉JDBC与数据库连接;Apusic应用服务器端直接通过JNDI-〉JDBC与数据库连接。
2.1. 直接通过JDBC与数据库连接。
这种连接方式无论是在客户端还是服务器端撰写的方式都一样,我这里建个连接Oracle数据库的例子:
Class.forName("oracle.jdbc.driver.OracleDriver");//Oracle的加载JDBC驱动程序的方法
Connection con=DriverManager.getConnection("jdbc:oracle:thin:@MyDbComputerNameOrIP:1521:ORCL",ID,Pwd);
// 与Oracle数据库建立连接的方法。使用的是Oracle在JDBC中的连接串。
// 前面两行代码仍然与数据库有绑定关系,不同数据库的加载类不同,数据库连接串也会不同。
// 如果大家需要了解其他数据库的情况,可以在网上搜索“JDBC使用过程”或者“常用数据库JDBC连接写法”可得。
Connection.createStatement();
// 创建一个Statement对象。Statement对象执行一个查询并从DBMS返回一个包含响应结果的ResultSet对象。
// 除了Statement对象外,还有其他对象这里不再介绍,大家可以搜索“使用JDBC访问数据库”或者“JDBC优化”可得。
con.close(); // 关闭数据库连接!这个必须要做,否则数据库连接资源会在一次次打开中耗尽。
2.2. 客户端通过iiop协议由JNDI-〉JDBC与数据库连接。
这种方式程序就会享受到使用数据库连接池的好处,程序会减少创建和关闭数据库连接时消耗的时间。但是,我只觉得应该是客户端程序中才这样使用,因为服务器端会有更加简单的使用方式,而且代码中还存在与应用服务器绑定的问题,如何绑定的我将在Apusic中使用的例子说明:
Hashtable env=new Hashtable();
Context initCtx=null;
DataSource datasource=null;
try{
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.apusic.naming.jndi.CNContextFactory");
// JNDI的上下文工厂类,不同的应用服务器这里不同。
// WEBLOGIC是weblogic.jndi.WLInitialContextFactory可以通过查看那些产品的相关类可知。
env.put(Context.PROVIDER_URL,"iiop://APUSIC_ADDRESS:6888");
// 连接所用的协议与端口,也与应用服务器有关,WEBLOGIC就是T3.
// 以上与服务器相关的信息包括下面的部分最好都采用XML定义的方式,以便未来容易配置。
//插入相关验证信息
env.put(Context.SECURITY_CREDENTIALS,"your_username" ) ;
env.put(Context.SECURITY_PRINCIPAL,"your_password");
initCtx=new InitialContext(env);
//通过RMI 取得数据库连接
datasource=(DataSource)initCtx.lookup("jdbc/sample")
Connection conn=datasource.getConnection();
}catch(Exception e)
这样的连接在部署时还需注意为相应的连接设置用户认证标记,在APUSIC_HOME/config/datasources.xml 中添加〈remote-acl〉标记及其元素。范例如下:
〈remote-acl〉
〈user〉username〈/user〉
〈/remote-acl〉
或
〈remote-acl〉
〈group〉groupname〈/group〉
〈/remote-acl〉
2.3. 服务器端直接通过资源管理器(数据库连接池)-〉JNDI-〉JDBC与数据库连接。
这种方式是JDBC 3.0规范中要求的,因此所有的应用服务器都遵守同样的标准,代码中不会存在与服务器绑定的信息。系统移植时最主要是修改配置信息,这个我在后面会具体介绍,下面是使用的例子:
try{
//取得InitialContext 对象
Context ctx=new InitialContext();
//通过JNDI 取得数据源
DataSource datasource=(DataSource)
ctx.lookup("java:comp/env/jdbc/oracle");
//通过数据源取得数据库连接
Connection conn=datasource.getConnection();
...
}
这段代码不做任何修改就可以在任何标准的应用服务器上运行,大家肯定注意到了代码中有个JNDI连接串,但没有建议采用XML文件配置,如果两个应用编写了同样的JNDI串是否会发生冲突?不会,原因是将会在后面的配置中通过名称映射来解决冲突。
如果采用这种技术访问JDBC,除了需要通过JNDI注册JDBC信息外,还需要在应用的apusic-application.xml 和web.xml 文件中对“资源引用”进行定义。对于移植的应用最好的定义方法是用Apusic部署工具打开编写的应用,然后展开使用这个JDBC连接的WEB或者EJB模块,选中“资源引用”就可以添加。其中“资源引用名称”就是在代码中使用的名称“jdbc/oracle”,JNDI名称就是在应用服务器上为JDBC连接配置的JNDI名称。通过这样的映射就完成了代码与应用服务器的JNDI名称的关联,也避免了应用中重复使用JNDI名称而造成的冲突,影响到的配置参数主要是〈res-ref-name〉相关的部分。
3. 如何配置JDBC。
Apusic应用服务器的JDBC配置有两种方式。一种是通过http://localhost:6888/admin进入管理界面,然后对数据源直接进行配置;另一种就是用编辑器打开datasources.xml文件手工配置。配置之前先准备好数据库连接串,数据库驱动类,用户名/密码和数据库驱动程序(JAR包)。JAR包有两种载入方式,一种是拷入到APUSIC_HOME/lib目录下,在系统启动时一起载入;另一种是配置过程中将驱动程序载入,采用何种方式可以由系统管理员决定,但是这个都与系统的ClassLoader有关,感兴趣的朋友可以去网上搜索一下。
http://www.builder.com.cn/2007/0801/443074.shtml
yanglilibaobao收录,使用标签:.NET,时间:2007-8-6 11:17:59 | 相关网摘,我也收藏
如约而至;时间是争取来的,这回是把若干零碎的时间利用起来成文的,完成对Asp.net身份验证、访问授权等内容的梳理,可能漏掉的东西会比较多,漏掉的还是希望大家来补充。顺便说一下上次Ajax 那点事,题目我认为很平实很低调了,没料到还是被批评了,这回考虑一下用了一个白开水一样的题目,没有问题了吧?言归正传……
观其大略:
1. Asp.net是依存于IIS的一个服务,说到Asp.net的安全相关的话题当然要有一个整体上的思路:IIS接收—》IIS验证—》IIS授权---》ASP.net验证---》Asp.net授权---》资源返回给用户
IIS从网络上接收到一个HTTP WEB请求可以使用SSL技术来保证服务器的身份,此外SSL也可以提供一个安全通道来保护客户端和服务器端的机密数据的传送。IIS使用基本 摘要式 或者其它的方式验证用户,如果网站中的大部分内容不需要验证就可以设定为匿名验证。验证之后是授权,通过授权IIS来允许或者禁止用户访问某资源。IIS可以使用ACL定义的NTFS权限或者针对IP地址的客户端授权。
IIS把Windows存取令牌传送到Asp.net,如果IIS使用的是匿名验证则为匿名Internet使用者的存取令牌(IUSER_MYMACHINE)。 ASP.net验证访问者(有三种方式)。Asp.net授权所需要的资源或者操作(有两种授权方法)。
Asp.net中程序的代码使用特别的身份来存取本机或者远程资源。
2. 身份验证:身份验证就是一个解决谁有权力进入系统的问题,通常的做法就是跟系统维护的用户名单进行核对,这样转化为一个实际的技术问题:如果有效的判断一个用户是不是系统的有效用户。这个过程就是—Authentication(身份验证)
专业说法:接收用户凭据,并根据指定的颁发机构来验证凭据的过程成为身份验证
3. Asp.net 提供三种身份验证方式:Windows验证 、Forms验证 、Passport验证
身份验证的使用是通过配置Web.config文件的〈 Authentication 〉配置节来实现的。
4. 授权:授权就是确认用户拥有足够的权限来访问请求的资源
5. Asp.net提供两类授权服务:文件授权服务 、URL授权服务
说点细节
1. 〈 Authentication 〉配置节mode可用的参数:None Windows Forms Passport
2. Windows:IIS根据程序的设置执行身份的验证(基本 简要 或者集成Windows)
注意使用这种验证方式IIS中必须要禁用匿名访问。
3. Windows验证适用于受控环境中,比如企业的Intranet
4. Foms这种验证使用Cookie保存用户凭证,并将未将验证的用户重定向到登录页。通常这是的IIS配置为匿名访问
5. Forms适合部署于互联网的网站应用
6. Passport验证是通过微软的集中身份验证服务执行的,它为成员站点提供单点登录和配置文件服务
7. Passport适用于跨站点应用,一旦用户注销所有的护照信息就会清除,可以在公共场所使用它。
8. ACL面向的是文件,IIS提供通过验证的用户,通过比较ACL调用标记完成授权
9. URL授权检查的根据是URL本身而不是URL对应的文件,URL授权可以是应用程序像基于窗体的身份验证或者Passport的身份集成验证,因为这些验证中的用户和计算机或者域中的账户并不对应。还控制对虚拟资源的访问。
10. URL授权的配置:授权指令:allow deny 对应操作对象是roles users 还可以使用Verb属性区别不同的HTTP行为(POST /GET)
11. 通常情况下用户访问一个网站都是使用匿名访问,匿名访问的用户都会转化为操作系统上的一个帐号来访问服务器
〈authentication mode=”windows”〉
〈identity impersonate=”true”〉
12. Windows验证用户通过了验证之后,Asp.net会触发Global.asax文件中的WindowsAuthentication_OnAuthenticate事件,可以在这里添加代码把用户信息附加到请求上。
13. Passport验证的问题是有多少网站愿意把自己的用户数据放在微软的数据库中?
14. Asp.net使用基于角色的安全:把用户映射到一个角色组里面,这个角色组对应一定的权限,这样就实现了对一个群体的权限管理。所以角色应该是对一组具有相同权限用户的抽象。
上面所说的都是可以应用于实践的,问题集中在web.config的配置,和一些初始化操作,大家和容易找到相关内容,我还是提供一个纲要,
http://www.cnblogs.com/me-sa/archive/2007/08/06/au.html
共986个网摘 [
1 2 3 4 5 6 ...
33 ]
下一页