最近在写年度总结时,对目前项目iPhone客户端的测试流程做了一些分析。由于无线客户端产品和传统的Web端测试所注重的点大有不同,客户端的测试更关注视觉和交互,和各种场景下客户端的处理逻辑,所以测试流程也需要和传统的Web测试有所区别。
希望对无线测试有兴趣的同学们,一起讨论,一起分享。。。
1、目前客户端测试流程。(单线程流程)

1)单线程流程存在问题:
(1)测试需要关注视觉,交互,功能等所有的问题,对测试自身专业技能要求过高。测试资源投入和产出比不高。
(2)BugBash阶段发现交互,视觉,需求问题,修改成本过大,可能直接导致项目周期不可控,项目质量风险过大。
(3)QA在测试过程中提出视觉,交互,需求不足或需求缺少时,很难有主导性。
(4)没有单元测试,开发自身很难判断代码修改后是否会引起的Bug,同时对项目质量造成隐患。
(5)大部分开发对需求细节不清晰,到测试阶段发现逻辑上的问题,修改成本太大。
(6)客户端FixBug,回归次数过多。
2)总结:
(1)单线程流程,测试资源投入和产出比不高。
(2)交互,视觉,和PD介入验收测试阶段滞后,导致修改成本过大,项目周期和项目质量风险不可控。
2)客户端测试流程设想(并发流程)
※ 适合项目,不合适小日常。

Step1:测试提供TC,由开发做单元测试,发现问题全部修改再提交测试。
优点:能够在提测前走通所有业务逻辑,能够更好的帮助开发发现需求上的问题,降低后期Bug修改成本。开发FixBug后,先执行单元测试后,再交由测试验证。
要求:测试提供TC,覆盖率要高。
Step2:开发提交测试后,交互走查,视觉走查,PD需求验证,功能测试并行执行。
优点:参与测试的角色,从原来单一的测试工程师到项目的各各环节的负责人。测试变的更全面。
要求:需要测试很好协调交互,视觉,产品经理的投入资源问题。测试在前期需要指导交互,视觉,PD如果进行走查,验收。制定CheckPiont
Step3:测试收集交互,视觉,需求,功能上所有问题。对问题进行解答,过滤,整理,跟踪,验收。
优点:尽早的发现交互和视觉,需求的问题,相当于把bug-bash提前。同时,能够降低测试的工作量,测试可以有时间关注场景测试,异常测试等...
要求:需要测试完全了解产品各个细节,能够对问题做出详细的解答。
总结:
1、在并发流程中,测试可以减少资源投入50%。
2、能提高Team的质量意识和提高交互,视觉,PD对主流程以外的关注。
3、把发现Bug的时间,提前一个阶段。缩短项目周期,质量风险可控。
4、项目测试如果可以能好的执行并发流程。测试owner可以渐渐转型为测试Supporter。
生成器模式属于对象结构型模式,其意图是将一个复杂对象的构建与他的表示分离,使得同样的构建创建过程可以创建不同的表示。
适用性:
1. 当创建复杂对象的算法应该独立于该对象的组成部分以及他们的装配方式时
2. 当构造过程必须允许被构造的对象有不同的表示时。
参与者:
生成器Builder:为创建一个Product对象的各个部件指定抽象接口(AnimalBuilder)。
具体生成器ConcreteBuilder:实现Builder接口一构造和装配该产品的各个部件。
定义并明确他所创建的表示,
提供一个检索产品的接口(getAnimal)。
导向器:构建一个适用Builder对象的接口。
Product
(Checken,pig
):
表示被构造的复杂对象。ConcreteBuilder
创建该产品的内部表示并定义它的装配过程,包含定义组成部件的类,包括将这些部件装配成最终的产品。
图1
当客户需要一个产品时,把需要的参数传递个导向器,导向器根据传递的参数调用具体的生成器,具体的生成器通过一系列的操作(getAnimal()通过调用其它的接口方法实现)最会返回一个产品。
Bridge模式的优点:
1. 它使你可以改变一个产品的内部表示,Builder对象提供给导向器一个构造产品的抽象接口,该接口使得生成器可以隐藏这个产品的表示和内部结构,他同时也隐藏了该产品是如何装配的,因为产品时通过抽象接口构造的(注:本例子把接口的实现放在具体类中了),你在改变该产品的内部表示时所要做的只是定义一个新的生成器。
2. 他将构造代码和表示代码分开,这有点类似于模板模式。所不同的是模版模式最终是完成复杂的工作,而生成器的目的是要生成一个复杂的对象。
3. 他使得你对构造过程进行更加精细的控制。Builder模式是在导向者的控制下一步一步构造产品的,仅当该产品完成时,导向者才从生成器中取回产品。因此Builder模式能更好的反映产品的构造过程,使你可以更精细的控制构建过程,从而能更精细的控制所的产品的内部结构。
代码示例:
AnimalBuilder:代码
package builder;
public interface AnimalBuilder{
public void buildName();
public void buildLegs();
public void buildMeat();
public Animal getAnimal();
}
ChickenBuilder的代码:
package builder;
public class ChickenBuilder implements AnimalBuilder{
private Animal chicken = null;
public ChickenBuilder(){
chicken = new Animal();
}
public void buildName(){
chicken.setName("chicken");
}
public void buildLegs(){
chicken.setLegs(2);
}
public void buildMeat(){
chicken.setMeat("chicken");
}
public Animal getAnimal(){
buildName();
buildLegs();
buildMeat();
return chicken;
}
}
Animal代码:
package builder;
public class Animal{
private String name;
private String meat;
private int legs;
public String getName(){
return name;
}
public String getMeat(){
return meat;
}
public int getLegs(){
return legs;
}
public void setName(String aName){
name = aName;
}
public void setMeat(String aMeat){
meat = aMeat;
}
public void setLegs(int alegs){
legs = alegs;
}
}
Director代码:
package builder;
public class Director{
public Animal getAnimal(String aName){
if(aName.equals("chicken")){
return new ChickenBuilder().getAnimal();
}
else if(aName.equals("pig")){
return new PigBuilder().getAnimal();
}
else return null;
}
}
客户端代码:
package builder;
public class Client{
public static void description(Animal animal){
String desc = "This animal is called "+animal.getName();
desc +="\n Its meat is "+animal.getMeat();
desc +="\n And it has "+animal.getLegs()+" legs.";
System.out.println(desc);
}
public static void main(String[] args){
String aName = "chicken";
Director d = new Director();
Animal animal =d.getAnimal(aName);
description(animal);
aName = "pig";
animal = d.getAnimal(aName);
description(animal);
}
}
结论:生成器模式使得客户可以更加精细的控制一个对象的构建,每个具体的创建者包含了创建和装配一个特定产品的所有代码,这些代码只用写一次,便可以被不同的Director使用,从而可以装配出不同的对象。
Builder 模式的重心在于分离构建算法和具体的构造实现,从而使构建算法可以重用。
Builder 模式的构成分为两部分:一部分是Builder接口,定义了如何构建各个部件,并装配到产品中去;另一部分是Director,定义如何来构建产品,Director 负责整体的构建算法,而且通常是分步来执行的。
注:这里的构建算法是指:通过什么样的方式来组装产品;构建产品指的是:构建一个复杂对象。
Builder 模式就是将构建产品部件和组装产品的过程分开,即实现了产品部件和组装产品过程的解耦,可以使得组装产品过程得到复用

public class ExportHeaderModel {
private String depId;
private String exportDate;
省略getter 和 setter
}
public class ExportDataModel {
private String productId;
private double price;
private double amount;
省略getter 和 setter
}
public class ExportFooterModel {
private String exportUser;
省略getter 和 setter
}
/**
* 生成器接口,定义一个输出文件对象所需的各个部件的操作
*
*/
public interface Builder {
/**
* 构建输出文件的Header部分
* @param ehm 文件头的内容
*/
public void buildHeader(ExportHeaderModel ehm);
/**
* 构建输出文件的Body部分
* @param mapData 要输出文件的数据内容
*/
public void buildBody(Map<String, Collection<ExportDataModel>> mapData);
/**
* 构建要输出文件的Footer部分
* @param efm 文件尾的内容
*/
public void buildFooter(ExportFooterModel efm);
}
/**
* 实现导出数据到文本文件的生成器
*
*/
public class TxtBuilder implements Builder {
/**
* 用来记录构建文件的内容,相当于产品
*/
private StringBuffer buffer = new StringBuffer();
@Override
public void buildHeader(ExportHeaderModel ehm) {
buffer.append(ehm.getDepId() + "," + ehm.getExportDate() + "\n");
}
@Override
public void buildBody(Map<String, Collection<ExportDataModel>> mapData) {
for(String tblName : mapData.keySet()){
buffer.append(tblName + "\n");
for(ExportDataModel edm : mapData.get(tblName)){
buffer.append(edm.getProductId() + "," + edm.getPrice() + "," + edm.getAmount() + "\n");
}
}
}
@Override
public void buildFooter(ExportFooterModel efm) {
buffer.append(efm.getExportUser());
}
public StringBuffer getResult(){
return buffer;
}
}
public class XmlBuilder implements Builder {
private StringBuffer buffer = new StringBuffer();
@Override
public void buildHeader(ExportHeaderModel ehm) {
buffer.append("<?xml version='1.0' encoding='gb2312' ?>\n");
buffer.append("<Report>\n");
buffer.append(" <Header>\n");
buffer.append(" <DepId>" + ehm.getDepId() + "</DepId>\n");
buffer.append(" <ExportDate>" + ehm.getExportDate() + "</ExportDate>\n");
buffer.append(" </Header>\n");
}
@Override
public void buildBody(Map<String, Collection<ExportDataModel>> mapData) {
buffer.append(" <Body>\n");
for(String tblName : mapData.keySet()){
buffer.append(" <Datas TableName=\"" + tblName + "\">\n");
for(ExportDataModel edm : mapData.get(tblName)){
buffer.append(" <Data>\n");
buffer.append(" <ProductId>" + edm.getProductId() + "</ProductId>\n");
buffer.append(" <Price>" + edm.getPrice() + "</Price>\n");
buffer.append(" <Amount>" + edm.getAmount() + "</Amount>\n");
buffer.append(" </Data>\n");
}
buffer.append(" </Datas>\n");
}
buffer.append(" </Body>\n");
}
@Override
public void buildFooter(ExportFooterModel efm) {
buffer.append(" <Footer>\n");
buffer.append(" <ExportUser>" + efm.getExportUser() + "</ExportUser>\n");
buffer.append(" </Footer>\n");
buffer.append("</Report>\n");
}
public StringBuffer getResult(){
return buffer;
}
}
/**
* 指导者,指导使用生成器的接口来构建输出的文件对象
*
/**
* 指导者,指导使用生成器的接口来构建输出的文件对象
*
*/
public class Director {
/**
* 持有当前需要使用的生成器对象
*/
private Builder builder;
public Director(Builder builder){
this.builder = builder;
}
/**
* 指导生成器构建最终的输出文件的对象
* @param ehm 文件头的内容
* @param mapData 数据的内容
* @param efm 文件尾的内容
*/
public void construct(ExportHeaderModel ehm, Map<String, Collection<ExportDataModel>> mapData,
ExportFooterModel efm){
//构建Header
builder.buildHeader(ehm);
//构建Body
builder.buildBody(mapData);
//构建Footer
builder.buildFooter(efm);
}
}
public class Client {
public static void main(String[] args) {
ExportHeaderModel ehm = new ExportHeaderModel();
ehm.setDepId("一分公司");
ehm.setExportDate("2011-06-12");
Map<String, Collection<ExportDataModel>> mapData = new HashMap<String, Collection<ExportDataModel>>();
Collection<ExportDataModel> coll = new ArrayList<ExportDataModel>();
ExportDataModel edml = new ExportDataModel();
edml.setAmount(80);
edml.setProductId("产品001号");
edml.setPrice(100);
coll.add(edml);
ExportDataModel edm2 = new ExportDataModel();
edm2.setAmount(60);
edm2.setProductId("产品002号");
edm2.setPrice(120);
coll.add(edm2);
mapData.put("销售记录表", coll);
ExportFooterModel efm = new ExportFooterModel();
efm.setExportUser("张三");
TxtBuilder txtBuilder = new TxtBuilder();
Director director = new Director(txtBuilder);
director.construct(ehm, mapData, efm);
System.out.println("输出到文本文件的内容:\n" + txtBuilder.getResult());
XmlBuilder xmlBuilder = new XmlBuilder();
Director director2 = new Director(xmlBuilder);
director2.construct(ehm, mapData, efm);
System.out.println("输出到Xml文件的内容:\n" + xmlBuilder.getResult());
}
}
● 使用生成器模式创建复杂对象:
① 由于使用Builder 模式来创建某个对象,因此就没有必要再定义一个Builder接口,直接提供一个具体的构建器类就可以了。
② 对于创建一个复杂的对象,可能会有很多种不同的选择和步骤,干脆去掉“Director”,把Director的功能和Client 的功能合并起来,也就是说这个时候,Client 相当于指导者,它来指导构建器类去构建需要的复杂对象。
public class ConcreteBuilder {
private String contractId;
private String personName;
private String companyName;
private long beginDate;
private long endDate;
private String otherData;
/**
* 构造方法 传入必填数据
* @param contractId 保险合同号
* @param beginDate 保险开始生效的日期
* @param endDate 保险失效的日期
*/
public ConcreteBuilder(String contractId, long beginDate, long endDate){
this.contractId = contractId;
this.beginDate = beginDate;
this.endDate = endDate;
}
/**
* 选填数据,被保险人
* @param personName 被保险人名
* @return 构建对象
*/
public ConcreteBuilder setPersonName(String personName){
this.personName = personName;
return this;
}
/**
* 选填数据,被保险公司
* @param companyName 被保险公司名
* @return 构建对象
*/
public ConcreteBuilder setCompanyName(String companyName){
this.companyName = companyName;
return this;
}
/**
* 选填数据,其它数据
* @param otherData 其它数据
* @return 构建对象
*/
public ConcreteBuilder setOtherData(String otherData){
this.otherData = otherData;
return this;
}
public InsuranceContract build(){
if(contractId == null || contractId.trim().length() == 0){
throw new IllegalArgumentException("合同编号不能空!");
}
boolean signPerson = (personName != null && personName.trim().length() > 0);
boolean signCompany = (companyName != null && companyName.trim().length() > 0);
if(!(signPerson ^ signCompany)){
throw new IllegalArgumentException("一份保险不能没有签订对象,且不能同时与人和公司签订!");
}
if(beginDate <= 0){
throw new IllegalArgumentException("合同必须有保险开始生效的日期!");
}
if(endDate <= 0){
throw new IllegalArgumentException("合同必须有保险失效的日期!");
}
if(endDate <= beginDate){
throw new IllegalArgumentException("保险失效日期必须大于生效日期!");
}
return new InsuranceContract(this);
}
public String getContractId() {
return contractId;
}
public String getPersonName() {
return personName;
}
public String getCompanyName() {
return companyName;
}
public long getBeginDate() {
return beginDate;
}
public long getEndDate() {
return endDate;
}
public String getOtherData() {
return otherData;
}
}
/**
* 保险合同对象
* @author joe
*
*/
public class InsuranceContract {
/**
* 保险合同编号
*/
private String constractId;
/**
* 被保险的人
*/
private String personName;
/**
* 被保险的公司
*/
private String companyName;
/**
* 保险开始生效的日期
*/
private long beginDate;
/**
* 保险失效的日期
*/
private long endDate;
/**
* 其它数据
*/
private String otherData;
/**
* 构造方法,访问级别是同包能访问
* @param builder
*/
InsuranceContract(ConcreteBuilder builder){
this.constractId = builder.getContractId();
this.personName = builder.getPersonName();
this.companyName = builder.getCompanyName();
this.beginDate = builder.getBeginDate();
this.endDate = builder.getEndDate();
this.otherData = builder.getOtherData();
}
public void someOperation(){
System.out.println("Now in Insurance Contract someOperation == " + this.constractId);
}
}
public class Client {
public static void main(String[] args) {
ConcreteBuilder builder = new ConcreteBuilder("001", 82345L, 67890L);
InsuranceContract contract = builder.setPersonName("张三").setOtherData("test").build();
contract.someOperation();
}
}
在前面的几篇文章中,我们了解软件项目验收管理在软件质量保障中的重要性以及工作内容,怎样做好软件项目验收管理工作是本文的重点,山东省软件评测中心根据多年验收管理经验,总结了做好软件项目验收管理的一些方法,希望能给大家带来帮助。
1、在软件项目实施过程中注重里程碑的确定,制定阶段性目标
如果要做好一个软件项目,完成项目的验收条件,主要还是以业务是否可用作为衡量的。不是一定得实现所有用户的需求(这里指的是口头上的需求,如果落实到文字上的还是要实现的),也不是只有将一些所谓的技术难点解决用户就会同意验收,而是可以完成一定的阶段应用业务目标。
进行需求调研的时候就要主动控制项目的边界,将一个一个业务流根据客户方的实际情况合理组织实施顺序,形成软件项目实施计划中的里程碑点,明确达到里程碑点的条件,并得到双方一致正式认可。
没有双方高度达成一致的里程碑认可,也就是没有项目目标约定,没有目标约定的项目实施计划一定会经常变更内容、变更初始设定目标,导致计划不可控制,更谈不上验收。
很多人希望通过详细的系统需求规格说明书来定义项目要实现的内容和业务目标,这是很有必要的,但需求规格说明书得到认可并非是通过用户审核就可以的结果,应该想办法让用户一起参与到需求规格说明书的制定过程中来,变成用户自己推导出来的业务实施目标,未来才不容易变形。
2、积极主动地与客户进行沟通
项目中一定要有沟通策略,和高管如何汇报工作进展,取得支持?和中层如何就业务目标不断确认,逐步清晰?和基层如何就项目应用操作模式达成一致,持续改进?都需要通过沟通反馈完成。
沟通的作用对于高管是让他们清楚项目一直按照目标前进,每个阶段工作进展是否顺利,影响项目正常运做原因是什么,需要哪些资源帮助。和高管沟通比较多的话,第一个好处是高管经常听汇报就知道项目进展程度,可以安排反馈检查,看是否具备项目所说的进展,这样一旦认可了各个阶段目标后,最终要求高管签字确认也就顺理成章了。
给高管汇报技巧就是简洁明了,真实客观,有理有据分析问题,提出对策建议请其决策即可。
中层往往是项目主要的推动力量和实际执行者,也往往是对具体业务需求最主要的要求者,他们对企业实际运做过程最清楚,提出要求最具体,而且项目验收与否没有中层的同意往往也是不太容易做到的。
往往通过前期业务调研只能对企业项目目标有一个大的,宏观的认识,但如何细化并最终落实并非是一步到位的过程。因此在整个项目过程中,双方项目组要不断沟通,特别是企业中层沟通,才能逐步认识越来越深刻,最终达成一致。
和基层的沟通主要体现对最终用户的关怀,定期主动和最终用户沟通,消除一些怨气,让用户能坚持用下去,这个时候往往发现很多用户真的是非常好相处,尽管软件还有很多值得改进的地方,但他们一旦认可团队,反而会尽心尽力帮助推动项目的进行。
目前一般要求每个项目经理在项目进行中都要填写详尽的项目月报,反映项目的进度,与计划的偏差,完成的项目内容,投入人力,目前项目存在的问题,以及预计项目下月的进度等等。将进度月报交部门负责人、项目管理中心、总经办审阅。
类似地也要制定针对客户的月报甚至是周报,将相关的信息反应到客户方的负责人,及相关高层。可以先发邮件,然后还要电话落实收到并口头简要汇报,特别是高管层,千万不要以为发了就等于别人会去看,一定要口头跟进汇报一次,保证客户各方面负责人对项目进展做到心中有数。
在项目的过程中,也需要注意平时做人的积累,比如要做到讲诚信,讲原则。主要是三条:1)做不到的事情千万别随意承诺;2)承诺的事情一定要努力做到;3)每次做到的事情都进步一点点。按这三条做事,即使在系统的使用过程中总会有这样或那样的一些不方便,用户也会慢慢接受稍微长一点的响应周期,也会用更多积极性眼光看现在的问题,也相信问题一定有人响应,也一定可以得到解决。进而使卖方和客户之间形成一种较为和谐的关系。
3、写好备忘录和问题跟踪记录
在一个漫长项目周期中,很多工作做了也就做了,认可了也就认可了,时间一长也就忘记了很多承诺和约定,到了验收的时候就可能重新翻出来,这种事情很多人可能都经历过,明明说可以先不做的内容最终验收的时候又成了必要条件。
每次备忘录要口头交流认可后才打印签字确定阶段性工作成果。下次工作则根据前次备忘录的双方约定继续进行,保障项目在每次工作基础上不断前进,并用备忘录约束双方的行为。
同时建议在收集项目出现的各种问题时,采用问题跟踪记录表的形式,这样可以一目了然地显示出曾经收集到的各种问题,目前的解决情况,以及还有什么问题没有解决,准备什么时候解决。这样客户和卖方都会对目前的情况非常了解,通过不断地解决出现的问题,来收敛可能出现的问题,当存在的问题越来越少时,也就表示系统已经在接近验收的标准了。
4、验收阶段的准备工作及注意事项
当系统经过一段试运行,具备验收的各项条件之后,就需要着手验收阶段的准备工作了。首先需要把到目前为止完成的工作进行一个总结,列出已经完成的各项目工作成果、各类文档,对合同以及各类约定的技术文档中的相关内容进行自查,要彻底了解系统目前完成的情况如何,是否已经完成了与客户方达成的各项书面约定以及口头约定,没有完成的,如果是书面约定,准备采取什么策略去进一步完成。
做一个详细的验收计划是非常必要的,可以用来作为验收阶段的工作指导。这就需要与客户进行详细的沟通,再次明确验收前需要完成的工作,尽量避免客户方在此阶段提出过多的更改需求,这是极为重要的。验收计划中不光要有需要继续完成的工作,还需要有一个相对固定的工期,使双方都继续朝着这个方向去努力,防止无限制的拖延。
项目验收对任何一个项目管理者都是一个极大的挑战,即使已经采取本文提到的几种手段,也不能保证项目能够顺利验收,本文中提出的这些建议,是希望能够起到抛砖引玉的效果,希望各位同仁可以提出更多更好的方法来促进项目如期验收。
版权声明:本文出自山东省软件评测中心 张凯丽,51Testing软件测试网原创出品,未经明确的书面许可,任何人或单位不得对本文进行复制、转载或镜像,否则将追究法律责任。
http://www.51testing.com
序言:做自动化测试的时候,一直没想过要去做性能,等到现在做性能的时候,才明白这本身就是一个必须都要经历的过程,就像编程一样,编写小型软件的时候,我们不用过多关注架构和性能,但是等成长到一定时候,就会需要关注软件的可复用性(这是由开发成本决定,这点可以在软件架构上去改善,常说的自动化框架也是为了增强脚本的可复用性和可维护性)、性能瓶颈(这是由系统资源成本决定,空间和时间的调配)、可测试行(这能大大提高测试人员的测试效率,很多时候我们要求开发提供一种测试的接口来方便测试人员进行测试)、可部署性(利用make、ant或者maven,能够大大提高软件发布效率,这也是持续集成中的一种手段)等,因此,测试中的发展其实可以有很多的,不仅关注测试手段,还要关注如何在更多的途径上提高测试效率。下面是对本次性能测试项目至今的一些简单总结,欢迎指正。
一、性能测试项目的背景
性能测试缘起于产品存在大量背景数据时,程序响应时间过慢,而且在特定的情况下有可能会造成一些数据上报丢失,所以需要定位。
产品为C/S架构,采用的协议是snmp协议,运行在jvm上。
二、性能测试的策略
1、测试目的的确定
1)系统监控,包括cpu、内存、线程使用情况,在大数据情况下,发现问题,帮助修正代码结构,系统结构,提高系统的运行效率。
2)确定软件运行资源需求指标。
2、性能测试指标确定
1)确定指标来源,主要包括:产品规格、行业标准、客户需求与故障场景等
2)确定测试特性,例如:系统容量、及时性、稳定性、抗压性、资源利用性等,这些特性可以根据行业性能测试特性以及产品的相关特性来决定。
3)确定具体指标,包括数目和单位。
3、性能测试技术储备
其实性能测试可以算得上是自动化测试的一种大数据测试
1)测试场景准备:准备测试场景,可以理解为对背景数据的构造,其实可以将这种构造理解为另类的接口测试,例如:我们的软件服务器是应用SNMP协议进行通信,设备端有一个agent,专门用来与软件服务器端通信,那么可以虚拟出这么一个agent,保存相应的设备信息,虚拟过程可以通过对在网的实际设备进行录制,然后生成。
互联网中,客户端与服务器的交涉是基于http接口协议,其一般的性能测试都是发送大量的http请求,其实这种过程有一个问题就是无法模拟真实的背景数据,因为报文过于单一,而印象很深的是新浪一位朋友开发的tcpcopy工具,在传输层,将线上数据复制到测试场景下,从而成功模拟了真实场景环境,这是一种很好的测试方法。
(还有一种准备工作就是对测试服务器的选型,包括操作系统类型、CPU内核数目、内存数目等)
2)测试数据准备:这其实就是接口数据,在互联网中,这方面的模拟比较简单,用很多工具,例如LR、jmeter、soaupi等都可以成功构造模拟http报文,从而查看服务器的响应。因为我们采用的是snmp协议,所以业内没有这样的snmp接口工具,所以就自己基于snmp协议包开发了其snmp报文模拟工具。
3)性能测试监控:性能测试过程中,对软件系统服务器的监控是关键,例如:web测试中,往往会对web服务器和数据库服务器、操作系统的指标性能进行监控,因为我们的软件是运行在jvm上,所以直接采用jconsole或者jprofiler监控服务器的内存使用、cpu使用、各个线程使用情况,还有对数据库和操作系统的监控等。
4、性能测试方法
1)基于指标,进行测试数据构造测试,查看系统是否工作正常以及监测是否没有问题。
2)基于指标,在基于测试数据测试的同时,由测试人员参与进行操作,测试在特定环境下的系统工作情况。
3)客户场景模拟测试。
4)随机测试,利用算法进行大量随机数据构造。
三、性能测试调优
1、性能测试是一个不断探索和不断完善的一个测试过程。
调优步骤:衡量系统现状、设定调优目标、寻找性能瓶颈、性能调优、衡量是否到达目标(如果未到达目标,需重新寻找性能瓶颈)、性能调优结束
2、衡量现状,系统性能主要存在问题
1)内存泄露
2)内存占用过大,响应速率慢
3)线程数不断增加,出现死锁或空闲线程
4)某些类实例化数目过多,占用多余的内存空间
3、内存泄露
1)检验方式:内存泄露需要进行时长测试,既将监控界面及系统界面全部打开,进行长时间运行(如12小时),观察系统类的增长情况。
2)问题定位:若出现JVM的Heap持续增长或者Memory views经过时长测试,出现较大规模的红色部分(增长部分),且无法GC。
4、系统内存占用大
1)检验方式:进行某些特定的操作,系统进行大量内存占用或者数据读写操作。
2)问题定位:若系统内存数突发性的增长,且之后不回落,说明某些模块在持续性的占用系统资源。或者出现JVM的Heap有增长,虽不是持续增长,但一直无法回落。
5、线程数目过多或死锁
1)检验方式:进行某些特定的操作,可以使系统产生大量线程操作。
2)问题定位:若系统线程数突发性的增长或持续增长,且之后不回落,说明某些模块在持续性的占用线程。或者观察是否有许多线程来自同一个模块、长期处于waiting或block状态
6、性能调优原则
调优过程中,充分而不过分使用硬件资源、不要一遇到问题就去改善资源环境,然后,合理调整JVM,需要重点掌握一些JVM的参数,并且要善于分析系统架构和JVM的底层工作机制。
总结:性能测试是一个很漫长的过程,不管是做JVM性能测试、WEB架构方面的性能测试,其实道理是相通的,个人觉得,要做好性能测试,不仅要对测试理解,更要对软件架构和底层的服务器工作机制特别理解,不然,往往,你只能去简单做一些所谓的性能测试操作,但是却无法针对很多场景提供有效的测试策略和调优建议。好的性能测试工程师应该是能够快速搭建场景定位问题、提供指标,并且能够对软件系统架构提出有效建议。共勉之
版权声明:本文出自 散步的SUN 的51Testing软件测试博客:http://www.51testing.com/?382641
原创作品,转载时请务必以超链接形式标明本文原始出处、作者信息和本声明,否则将追究法律责任。
摘要: 四、举例 这里就以去餐馆吃饭为例详细的说明下享元模式的使用方式。去菜馆点菜吃饭的过程大家一定都是轻车熟路了,这里就不赘述。在例子中我使用了一个list来存放外蕴状态和内蕴状态的对应关系,而且提供了查询每个客人点菜情况的方法。内蕴状态在这里代表了菜肴的种类,而外蕴状态就是每盘菜肴的点菜人。 A 让我们先来看看单纯享元模式的实现吧。 先看下抽象...
阅读全文
最近在读《软件测试》 一书,对叫这个名字的书多,我读的是Ron Patton 写的那本,对!2002年的中文版,已经十年了,虽然没《软件测试的艺术》那么经典,那么有深度,但绝对也是一本不错的好书。读到第三章“软件的实质”,觉得确实不错,这里摘录分享与大家分享。
实质,哲学中把本质,又称为“实质”是指某一对象或事物本身所必然固有的。说的通俗点也就是说软件测试的本来的面目。
软件缺陷的定义
来看一下Ron Patton 为我们的软件缺陷所下的定义。
1、软件没有实现产品的说明书所描述的功能。(个人觉得“描述”比“宣称”更贴切)
2、软件实现了产品说明书描述不应有的功能。
3、软件执行了产品说明书没讲的操作
4、软件没有实现产品说明书没讲但应该实现的功能。
5、从软件测试员的角度来看,软件难以理解、不易使用、运行缓慢,或者最终用户认为不对。
为什么一个定义要这么多条来描述?这个“缺陷”的定义有这么复杂么?不,它其实并不复杂,作者只是想更加全面的来给缺陷下定义。下面我们来以建一栋房子为例,来说明一下每一条定义的意思。需要说明的是没有十分完美而且一成不变的产品说明说,而且在实际项目中,它可能非常简陋,模棱两可,甚至经常变动。
1、软件没有实现产品说明书的描述的功能。房子的主要希望有一个落地的大窗户,让阳光更好的照进屋子里,而且他特意在房子的设计图纸中画出来,并且还加以说明。结果,他看到的是四面全是墙壁,只有一个小门的房子。那么对于测试人员来说,他就是一个缺陷。
2、软件实现了产品说明书中描述的不应有的功能。由于房子的主人生活在南方,天气温暖,而请来的泥瓦匠是北方的,结果给主人建造的房子具然有一个大大的取暖的烟筒,而且主要主要特意在房子的设计图纸中说明,自己的房子不要烟筒。那么对于测试人员来说,这也是个缺陷。
3、软件执行了产品说明书没讲的操作。与第二条类似,不同的是第二条是主人已经明确说了自己不要烟筒,而这一条强调的是在主人没说的情况下。泥瓦匠自作聪明的加了一个烟筒上去。对于测试人员来说,画蛇添足的功能同样被视为缺陷。
4、软件没有实现产品说明书没讲但应该实现的功能。房子的主要对屋子的高度、格局,材料,颜色描述的非常清楚。泥瓦匠在建造房子的时候发现,主人没有提地基这回事,为了使房子牢固,所以,所有的房子都是必须要先打地基的,虽然主人没有说,但地基的功能必须要做。如果因为没有描述没有去做,但这又一件必须去做的事。对于测试人员来说,也可以视其这缺陷。
6、从软件测试员的角度看,软件难以理解、不易使用、运行缓慢,或者最终用户认为不对。软件测试员是软件除了测试软件运行的缺陷,同样是作为一个用户在再对软件进行使用。如果感觉自己都很难使用,或软件效率非常低且界面丑陋等情况,也可以认为其存在缺陷。或者是最终用户拿到产品时发现这根本不是自己想要的东西,也可以现其为缺陷。当然,用户说不是自己想要的东西,也不能凭借一面之词,可以拿合约,产品说明书来评估。
Ok ,上面分析一下缺陷的定义,如何去判定一个缺陷。下面来看一下测试的原侧,我们可以视其为软件测试过程中的“交通规则”。它会有助于我们更好的进行软件测试的工作。
全完测试程序的可能性
初做软件测试者可能认为拿到软件后就可以进行完全测试了,找出所有软件缺陷,并使软件完美。遗憾的是这是不可能的,我们无法对一个软件进行完全测试,即使最简单的程序也不行。主要原因如下:
● 输入量太大
● 输出结果太多
● 软件实现的途径太多
● 软件说明书没有客观标准。从不同的角度看,软件缺陷的标准不同。
这样说有些耸人听闻,又不能全部测试,不测试又会漏掉软件缺陷。软件终归要分布的,此时测试就要停止,但是如果这么快停止下来,还有测试没做。怎么办?

如上图所示,纵轴是表示缺陷的数量,横轴表示测试工作量。缺陷的数量随着测试工作的进展在不段减少;但测试有费用也随着工作量在不段提高。
也就是说要想发现更多的缺陷就必须投入更多费用(这个费用包括时间、人才,物力), 对一个新项目,我们前提可能1天发现10个缺陷。到后面可能10天发现1个缺陷,或者发现一个缺陷所需要的时间更长,我们有必要是去为发现一个缺陷而继续增加费用么?本来就不存在完美的产品,我们的目标是找到最合适的测试量,使投入(测试费用)与回报(修复缺陷数)达到最优。
测试无法显示潜在的软件缺陷
仔细理解一下这个标题。当测试人员对一个软件进行测试时,他发现了很多缺陷,功能的,界面的,兼容性能。然后,测试人员可以好不忧郁的说,这个软件存在缺陷。
当又测试人员又对另一个软件进行测试时,他用尽各种测试方法,测遍所有功能(当然,这是不可能,上面已经说了无法完全测试),他投入了大量的时间和精力却最终没有发现一个缺陷。那么测试人员不能保证这个软件是没缺陷的,当然,他更无法去报告这些潜伏的缺陷。也就是说测试人员只能报告已经发现的缺陷,对于未知的谁也不能肯定。
(这也是测试人员遭受质疑的地方,你既然不能保证软件的质量,要你何用。测试人员可以提高软件的质量,至于能提高多少,全凭测试人员的能力决定了。不像开发人员对于一个功能的实现,能或不能是很明确的两个答案。)
找到的软件缺陷越多,就说明软件的缺陷越多
我们先来体会下面两句话。
“找到的软件缺陷越多,说明软件遗留的缺陷越少”
“找到的软件缺陷越少,说明软件遗留的缺陷越少”
不管是开发还是测试,我们期望软件遗留的缺陷少。但是上面的两句话都不成产。我们发现缺陷的多少和最终软件遗留的缺陷多少毫无关系。那么为什么会有上面两种推断呢?
“找到的软件缺陷越多,说明软件遗留的缺陷越少”这种情况,假设缺陷在一定数量的情况下,测试人员业务非常精通,测试极其认真,发现越多的缺陷 ,说明还遗留的缺陷就越少。那么,我也可以假设随便这么一测,就发现这么多缺陷,那这个软件应该还有很多。
“找到的软件缺陷越少,说明软件遗留的缺陷越少”这种情况,假设经验非常丰富,而且工作非常认少,测试人员花费了很大的时间和精力都不能找到缺陷,说明软件本身的质量很少,也就是说其本身遗留的缺陷越少。那么,我也可以假设为,是不是我对业务不够熟悉,经验不够丰富,为什么发现不了缺陷。那么可能软件遗留的缺陷还有很多,只是我没有发现。
更严谨说法,或者更准确的说法是“找到的软件缺陷越多,就说明软件的缺陷越多。”我们发现有缺陷越多,只能说明软件的缺陷多。并无法正明软件还遗留的缺陷的多少。
当然,也并不是无法评估软件遗留缺陷的多少,我们可以根据开人员的工作经验与技术能力,测试人员的工作经验,测试技能,对业务的熟悉程度以及后以往完的成项目质量进行评估。
并非所有软件缺陷都能修复
“每一个测试人员都一颗追求完美的心”,当我们发现了一个缺陷时,我们希望它能够被修复,我们不能容忍被发现的缺陷眼睁睁的存在着而无法得到修复。在软件测试中,令人沮丧的现实是,即使拼尽全力,也不是所有的软件缺陷都能修复。
这并不意味着软件测试员未达到目的,或者项目小组将发布质量有缺陷的产品。其真正的含义是要软件测试员具备本文开头(缺陷的定义)中所描述的测试的素质---进行良好的判断,搞清楚在什么情况下不能追求完美。项目小组需要每对一个软件缺陷进行取舍,根据风险决定哪些要修复,哪些不要。
不需要修复软件缺陷的原因:
* 没有足够的时间。在任何一个项目中,通常是软件功能较多,而代码编写人员和软件测试人员较少,而且在项目进度中没有编制和测试留出足够的空间。
* 不算真正的缺陷。或者有人说,这不算缺陷,而是一项新的功能。在某些特殊场合,错误理解、测试错误或说明书变更会把软件缺陷当作附加功能来对待。
* 目前技术无法解决。你不会相信,人类有丰富的想象力,很多时候是受制于技术上无法实现。
* 修复的风险太大。这种情况非常常见。软件本身是脆弱的、难以理清头绪。修复一个软件缺陷可能导致其它软件缺陷出现。在紧迫的产品发布进度压力之后,修复软件将冒引入更多缺陷的情况下。我们只能不去理睬现有的缺陷。
* 修复成本太高,当我们使用一种技术去完成一项工作,当技术将要完成的时候发现一个缺陷无法解决,需要换用另一个技术才能有效规避这个缺陷。那这个修复成本将非常高。迫于时间与成本的压力。我们需要暂时不去理会。
* 不值得修复。虽然有些不中听,但这是真的。不常出现的软件缺陷和在不常用的功能中出现的缺陷,或都出现也不会造成什么影响,那么就不值得去修复。这些都要归结为商业风决策。
软件说明书不断变化
软件开发者面临一个难题。整个行业变化太快,去年还很时髦的产品今年就过时了,同时,软件变得更庞大、更复杂,功能越来越多,导致软件开发周期不断变长。这两种反作用力形成了矛盾,结果是产品说明书一变再变。
除了紧跟变化没有其他方法。假定我们的产品有一个不得更改的最终产品说明书。经过两年按部就班的开发快要完工时,结果竞争对也手发布了一个产品,结果从功能性能用户体验都要优于我们即将完工的产品。我们是继续完成一个失去竞争力的产品,还是重新讨论产品功能,重写产品需求,并开发修订产品?明智的选择是后者。
软件测试员必须要想到产品需求可能改变。未曾计划的特性会增加,经过测试并报告软件缺陷的特性可能发生变化甚至被删除。这些者是可能的。
软件测试术语
准确与精确
关于软件准确与精确之间是存在区别的。我的理解在保证准确的基础上求精确。拿一个计算器来做例子。我最喜欢拿一个计算器来输入10除以3 ,如查等于3.0(四舍五入)了,那么它就不够准确。如果计算的结果是3.3 那么要我看他的小数点后面有几个3 ,3越多表示越精确。(个人认为在软件测试中,这个用到的不多)
验证和合法性检查
虽然验证和合法性检查常常互换使用,但是他们有不同的定义。其中的差别对软件测试很重要。
验证是保证软件符合产品需求的过程。合法性检查是保证软件满足用户要求的过程。
验证更多的是站在产品需求的角度去测试软件,合法性(或叫“合理性”合适)是站在用户的角度是测试软件,当他们发生冲突时,就需要对产品时行衡量。但我偏向于用户角度,因为产品的最终目的是给用户使用,而不是为了符合需求文档。
质量和可靠性
质量解释为“优秀程度”或者“超越同类的”。如果说软件产品质量高,就是指它能够满足客户要求。客户会感到该产品性能卓越,优于其他产品。
如果在测试过程一直稳定、可靠,就会认为这是高质量的产品。这样理解错误。可靠性只是质量的一个方面。那么产品在各种机型上是否一样运行稳定。是否有技术支持,是否使用方便且性能优秀,这些灰是质量的组成部分。
测试与QA
软件测试人员的目标是找出软件的缺陷,尽可能早的发现并确定修复缺陷。
QA的主要职责是创建和加强促进软件开发并防止软件缺陷的标准和方法。
随着公司组织架构的调整,战略调整,产品的实现技术不断变化,现在的测试人员可以说是什么都可以干。
有些人做产品,有些人做平台,有些人做工具......
有些人有点象专职开发,有些人有点象专职运营......
Facebook,google的一些敏捷测试理念中,测试人员应该致力于提出测试解决方案,研究各种测试工具为主,具体的测试执行工作,由coding的开发同学去做。
变化后面也有很多不变的,测试手段无外呼白盒测试,黑盒测试,静态测试,动态测试,单元测试,集成测试,系统测试,安全测试,性能测试等等。那些奋斗在一线的测试工程师的工作内容实则没有什么大的变化,访谈的结果是大家觉得自己也没有成就感,工作很累。
这一切都让我迷惑了,很多人象我一样也迷惑了,测试人员的核心价值到底是什么?
测试人员的职业发展是什么?特别是focus在业务上的测试人员的核心价值是什么?在这里仅表达下我个人的观点,欢迎大家一起拍砖。
● 核心价值一:测试设计能力
最基本的也是最重要的价值就是测试设计。无论是采用白盒,黑盒,手工还是自动化等不同的方式,精华都在测试设计中。测试设计能力入门容易,做深难,需要耐得住寂寞,不断的学习积累,同时需要的知识面非常广。
下面几点可以提升测试设计能力:
1、对产品的熟知程度
2、对用户的了解程度
3、技术实现/依赖产品/中间件/DB设计/缓存机制/安全机制等技术的深入了解程度
4、产品运行环境(包括服务端,客户端,浏览器,系统并发量,吞吐量等)
5、bug回溯(定位/分析)
非常值得一提的是bug回溯,是一项非常有意义的活动。很多公司特别重视线上bug的预防,分析,却忽略了线下bug的回溯。而实际上,大家都有这样的印象,发现bug的不一定是你设计的TC,而是在执行TC时发散的其他测试场景。通过bug原因分析,可以更精准的帮助你识别易出问题的点。而且现在的技术,环境都是多样性的,总会出现一些你意想不到的bug,它的存在一定是有原因的。这些东西需要通过bug回溯不断的积累。
Bug回溯 与测试设计形成良性循环

● 核心价值二:制定测试策略
大家都知道测试是不能穷举的。在有限的人力、时间、资源情况下,如何更快,更全面的覆盖被测对象,是需要策略的。
我记得以前天彤说过,专家级的测试工程师可以对被测对象进行“精准爆破”,非常认同。对于象淘宝这么庞大复杂的系统来说,如果不能做到精确设计,精确测试,为了保障大用户量大数据量的并发下,想最大程度的规避可能出现的风险,让测试同学以眉毛胡子一起抓的方式进行测试就是在劳民伤财。
不同产品,不同的团队,产品成熟度,人员的成熟度,所采用技术的成熟度等等,都可能导致测试策略的不同。制定测试策略的过程,就是对当前的项目、团队进行量体裁衣。
影响测试策略的因素:
△ 项目类型。如:新产品,完善功能,重构型的,底层升级,数据库升级,不同的项目类型,测试重点也不同,采用的测试工具和测试类型也不尽相同
△ 产品成熟度。主要考虑产品的业务是否稳定,成熟。是属于创新型,试水产品,是否是成熟行业,需求明确稳定等等?
△ 使用研发技术和研发平台。采用新的研发工具,新的研发技术,还是公司成熟的技术,工具,使用什么样的数据库设计,包括产品的设计思想,产品架构等
△ 团队能力及默契度。稳定型团队?新团队?半新半旧,人员技术能力如何?人员特点如何?(特别需要说明的是,通过bug回溯可以发现团队开发或测试人员的技术能力,代码质量,业务掌握情况,逻辑清晰等这些个人特质,针对不同的人可以在测试时做不同的重点验证)
△ 研发模式。采用什么研发模式,传统的瀑布,还是敏捷,迭代等。这种研发模式以往常出现的问题是什么?该模式在该团队的运行是否成熟,稳定?
△ 产品线上运行环境。包括服务端和客户端的运行环境,负载机制,缓存机制,服务器分布等
△ 产品线上并发量,吞吐量等指标。关注目前指标及增长趋势
△ 产品使用用户。使用产品的用户人群众分布?目前的使用满意度如何?用户的计算机使用水平如何?用户反馈的最大问题是什么?用户的使用习惯是什么?竞争产品在用户中的优势是什么?
△ 测试过程保障。上线前测试依赖的环境、数据、技术、平台、工具保障,有现成的,还是需要开发?
测试策略的方面
△ 测试类型
△ 各种测试类型的测试程度、测试通过/停止标准
△ 使用测试技术
△ 依赖平台、工具
在工作中,大家对一些事情存在一些误区:
1、编码能力。我们不盲目崇拜编码能力,而是随着测试手段不同,测试深入程度不同,需要我们有能力去识别代码中存在的风险,对产品的技术实现有更深入全面的掌握,才能更有针对性的进行测试,所以,我们必须具备编码能力。
2、创新。我们不能为了创新而创新,而是在工作过程中,技术结合业务,为解决实际的问题自然而然的生长出来的新东西。这个创新一定是解决我们工作中的问题或用户的问题的。
3、工具。工欲善其事,必先利其器,随着我们被测试对象的复杂化,多样化,使用技术的差异化,一些常规手段无法测试的内容,一些重复的劳动密集性的事务,需要让工具代替手工去做,自然而然的就会产生工具。所以,我们不是迷信工具,也不是崇拜工具,工具是为我们服务,带来价值的。如果这个工具不能给我们带来价值,就算做一个工具,没有人使用,又有什么意义呢?
在实现测试设计与测试策略制定过程中,我们为解决实际问题自然会生出一些工具,平台,我们要鼓励大家用创新的思维去思考和解决问题,这样的产出是非常有价值的。
相关链接:
软件测试的核心价值是什么?
动态代理
java中动态代理机制的引入使得代理模式的思想更加完善与进步,它允许动态的创建代理并支持对动态的对所代理的方法进行调用。Java动态代理类位于Java.lang.reflect包下,一般主要涉及到以下两个类:
(1). Interface InvocationHandler:该接口中仅定义了一个方法Object:invoke(Object obj,Method method, Object[] args)。在实际使用时,第一个参数obj一般是指代理类,method是被代理的方法,如上例中的request(),args为该方法的参数数组。这个抽象方法在代理类中动态实现。
(2).Proxy:该类即为动态代理类,作用类似于上例中的ProxySubject,其中主要包含以下内容:
Protected Proxy(InvocationHandler h):构造函数,估计用于给内部的h赋值。
Static Class getProxyClass (ClassLoader loader, Class[] interfaces):获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组。
Static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):返回代理类的一个实例,返回后的代理类可以当作被代理类使用(可使用被代理类的在Subject接口中声明过的方法)。
所谓Dynamic Proxy是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然啦,这个Dynamic Proxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。 下面我们通过动态代理来重新实现上面发送信息的例子!

在上面的例子基础上,我们先添加一个通过短信来发送消息的处理类:
public class SmsMessage implements MessageHandler {
@Override
public void sendMessage(String msg) {
// TODO Auto-generated method stub
System.out.println("SMS Message :" + msg+" sent !");
}
}
//动态代理类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DynamicMessageProxy implements InvocationHandler {
private static int count;
private MessageHandler msgHandler;
public DynamicMessageProxy(MessageHandler handler) {
msgHandler = handler;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
System.out.println("++++++++=============+++++++++");
System.out.println("proxy:" + proxy.getClass());
System.out.println("method:" + method);
System.out.println("++++++++=============+++++++++");
if (args != null && args.length == 1 && checkMessage((String) args[0])) {
count++;
System.out.println("Message sent:" + count);
return method.invoke(msgHandler, args);
}
return null;
}
private boolean checkMessage(String msg) {
return msg != null && msg.length() > 10;
}
}
//下面是调用
import java.lang.reflect.Proxy;
public class MainClass {
private static void runProxy(MessageHandler handler) {
handler.sendMessage("message for test");
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
// runProxy(new EmailMessage());
// System.out.println("++++++++++++++++Proxy++++++++++++++++++");
// runProxy(new MessageProxy());
MessageHandler handler = new EmailMessage();
runProxy(handler);
MessageHandler proxy = (MessageHandler) Proxy.newProxyInstance(
MessageHandler.class.getClassLoader(),
new Class[] { MessageHandler.class }, new DynamicMessageProxy(
handler));
runProxy(proxy);
System.out.println("++++++++++++++++++++++++++++++++++");
// 短信方式
handler = new SmsMessage();
runProxy(handler);
proxy = (MessageHandler) Proxy.newProxyInstance(MessageHandler.class
.getClassLoader(), new Class[] { MessageHandler.class },
new DynamicMessageProxy(handler));
runProxy(proxy);
}
}
下面为以上方法的输出:
message for test send!!
++++++++=============+++++++++
proxy:class $Proxy0
method:public abstract void MessageHandler.sendMessage(java.lang.String)
++++++++=============+++++++++
Message sent:1
message for test send!!
++++++++++++++++++++++++++++++++++
SMS Message :message for test sent !
++++++++=============+++++++++
proxy:class $Proxy0
method:public abstract void MessageHandler.sendMessage(java.lang.String)
++++++++=============+++++++++
Message sent:2
SMS Message :message for test sent !
以上例子中,通过调用Proxy.newProxyInstance方法创建动态代理对象,该方法需要传入一个 类加载器、一组希望代理实现的接口列表、InvocationHandler 接口的一个具体实现。动态代理可以将所有调用重定向到调用处理器,通常我们会向该处理器传递一个时间对象的引用。invoke()方法中传递进来了代理对象,当你需要区分请求来源时这是非常有用的,例如你可以通过判断传入的方法名屏蔽掉某些方法的执行!动态代理机制并不是会很频繁使用的方法,它通常用来解决一些特定情况下的问题,因此不要盲目的为了使用而使用,要根据自己的实际需求来决定!
动态代理的应用十分广泛,在struts2的拦截器中就用到了动态代理机制。还是以买车票为例子,现在用动态代理来实现,其中不懂的接口方法查查Java api就会明白了,在此不多做解释,代码如下(注意PassengerProxy类):
import java.lang.reflect.*;
public interface Passenger {
public void buyTicket();
}
public class RealPassenger implements Passenger {
@Override
public void buyTicket() {
// TODO Auto-generated method stub
System.out.println("购买了车票");
}
}
// 用动态代理实现
//InvocationHandler
是代理实例的调用处理程序 实现的接口。 每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到//它的调用处理程序的 invoke
方法。
public class PassengerProxy implements InvocationHandler {
public Object obj;
// 把obj交给代理类
public Object obj(Object obj) {
this.obj = obj;
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
System.out.println("通过代理");
method.invoke(obj, args);
return null;
}
}
public class Client {
public static void main(String[] args) {
PassengerProxy proxy = new PassengerProxy();
Passenger passenger = (Passenger) proxy.obj(new RealPassenger());
passenger.buyTicket();
}
}
再来一个更牛的例子
- /**
- * 相亲接口
- *
- * @author zhengt
- * @time Jun 3, 2095 3:13:03 PM
- */
- public interface XiangQinInterface {
- /**
- * 相亲方法
- */
- public void xiangQin();
- }
- /**
- * 张三相亲实现类
- *
- * @author zhengt
- * @time Jun 3, 2095 3:14:48 PM
- */
- public class ZhangSanXiangQinInterfaceImpl implements XiangQinInterface {
- public void xiangQin() {
- System.out.println("张三去相亲,娶个漂亮老婆。");
- }
- }
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
-
- /**
- * 相亲可是一辈子的大事,相亲前要准备一下,打扮得帅气些。
- *
- * @author zhengt
- * @time Jun 3, 2095 3:15:48 PM
- */
- public class ReadyInvocationHandler implements InvocationHandler {
- //相亲接口的实现类,也就是张三相亲类
- private Object zhangSan = null;
-
- public ReadyInvocationHandler(Object realSubject) {
- this.zhangSan = realSubject;
- }
-
- public Object invoke(Object proxy, Method m, Object[] args) {
- Object result = null;
- try {
- /**
- * 动态代理类$Proxy0调用xiangQin方法时会调用它自己的xiangQin方法,
- * 而它自己的xiangQin方法里面调用的是super.h.invoke(this, , ),也就是父类Proxy的h的invoke方法,
- * 也就是ReadyInvocationHandler类的invoke方法。
- * 所以,invoke(Object proxy, Method m, Object[] args)种的proxy实际上就是动态代理类$Proxy0,
- * 如果你将其强转成XiangQinInterface然后调用它的xiangQin方法,然后它就会调用super.h.invoke(this, , ),这样就会死循环。
- */
- /**
- * 网上关于这里最多问题就是Object proxy放在这里用来做什么呢?这个我也不知道,
- * 不过至少我们知道它到底是个什么东西,具体做什么用嘛就不得而知了
- */
- System.out.println(proxy.getClass().getSimpleName());
- System.out.println("张三相亲前,代理人给他打扮了打扮。");
- result = m.invoke(zhangSan, args);
- } catch (Exception ex) {
- System.exit(1);
- }
- return result;
- }
- }
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
-
- /**
- * 张三来到了婚介所(相亲现场),开始相亲。
- *
- * @author zhengt
- * @time Jun 3, 2095 3:17:16 PM
- */
- public class HunJieSuo {
- public static void main(String args[]) {
- //先将张三相亲这个相亲的实现类实例化,也就是得到XiangQinInterface接口的一个实例对象
- XiangQinInterface zhangSan = new ZhangSanXiangQinInterfaceImpl();
- /**
- * 得到ZhangSanXiangQinInterfaceImpl这个类的一个代理类,同时为代理类绑定了一个处理类ReadyInvocationHandler。
- * 听着很绕口,其实就是每次调用ZhangSanXiangQinInterfaceImpl这个子类的xiangQin方法时,
- * 不是zhangSan这个ZhangSanXiangQinInterfaceImpl类的实例去调用,
- * 而是这个ZhangSanXiangQinInterfaceImpl的代理类ReadyInvocationHandler去调用它自己的invoke方法,
- * 这个invoke方法里呢可以调用zhangSan这个实例的xiangQin方法
- */
- /**
- * 在java种怎样实现动态代理呢
- * 第一步,我们要有一个接口,还要有一个接口的实现类,而这个实现类呢就是我们要代理的对象,
- * 所谓代理呢也就是在调用实现类的方法时,可以在方法执行前后做额外的工作,这个就是代理。
- * 第二步,我们要自己写一个在要代理类的方法执行时,能够做额外工作的类,而这个类必须继承InvocationHandler接口,
- * 为什么要继承它呢?因为代理类的实例在调用实现类的方法的时候,不会调真正的实现类的这个方法,
- * 而是转而调用这个类的invoke方法(继承时必须实现的方法),在这个方法中你可以调用真正的实现类的这个方法。
- * 第三步,在要用代理类的实例去调用实现类的方法的时候,写出下面两段代码。
- */
- XiangQinInterface proxy = (XiangQinInterface) Proxy.newProxyInstance(
- zhangSan.getClass().getClassLoader(),
- zhangSan.getClass().getInterfaces(),
- new ReadyInvocationHandler(zhangSan));
- proxy.xiangQin();
- /**
- * 这里要解释下中部那段长长的代码的意思,以及具体做了哪些工作?
- * 第一,根据zhangSan.getClass().getClassLoader()这个要代理类的类加载器和
- * zhangSan.getClass().getInterfaces()要代理类所实现的所有的接口
- * 作为参数调用Proxy.getProxyClass(ClassLoader loader, Class<?>... interfaces)
- * 的方法返回代理类的java.lang.Class对象,也就是得到了java动态生成的代理类$Proxy0的Class对象。
- * 同时,java还让这个动态生成的$Proxy0类实现了要代理类的实现的所有接口,并继承了Proxy接口。
- * 第二,实例化这个动态生成的$Proxy0类的一个实例,实例化代理类的构造函数为Proxy(InvocationHandler h),
- * 也就是说要实例化这个动态生成的$Proxy0类,必须给它一个InvocationHandler参数,也就是我们自己实现的用来在代理类
- * 方法执行前后做额外工作的类ReadyInvocationHandler。
- * 这 段代码 Proxy.newProxyInstance(zhangSan.getClass().getClassLoader(),zhangSan.getClass().getInterfaces(),new ReadyInvocationHandler(zhangSan))
- * 得到的其实是一个类名叫$Proxy0 extends Proxy implements XiangQinInterface的类。
- * 第三,将这个$Proxy0类强制转型成XiangQinInterface类型,调用xiangQin方法。
- */
- }
- }
在目前的Java开发包中包含了对动态代理的支持,但是其实现只支持对接口的的实现。
其实现主要通过是java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。
Proxy类主要用来获取动态代理对象,InvocationHandler接口用来约束调用者实现,如下,DiaoSi接口定义的 业务方法,DiaoSiNanImpl是DiaoSi接口的实现,
DiaoSiNanHandler是 InvocationHandler接口实现。代码如下:
业务接口:
package proxy;
public interface DiaoSi {
void luGuan();
}
public class DiaoSiNan implements DiaoSi {
@Override
public void luGuan() {
// TODO Auto-generated method stub
System.out.println("我是撸管王");
}
}
InvocationHandler实现,需要在接口方法调用前后加入一部份处理工作,这里仅仅在方法调用前后向后台输出两句字符串,其代码如下:
package proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DiaoSiNanHander implements InvocationHandler{
//要代理的原始对象
private Object diaosinan;
/**
* 构造函数。
* @param obj 要代理的原始对象。
*/
public DiaoSiNanHander(Object diaosinan) {
this.diaosinan = diaosinan;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
Object result ;
doBefore();
//调用原始对象的方法
result = method.invoke(this.diaosinan ,args);
//方法调用之后
doafter();
return result;
}
private void doafter() {
// TODO Auto-generated method stub
System.out.println("after method invoke!");
}
private void doBefore() {
// TODO Auto-generated method stub
System.out.println("before method invoke!");
}
}
测试代码:
package proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
DiaoSi ds = new DiaoSiNan();
DiaoSiNanHander handler = new DiaoSiNanHander(ds);
DiaoSi proxy = (DiaoSi) Proxy.newProxyInstance(
ds.getClass().getClassLoader(),
ds.getClass().getInterfaces(),
handler);
/*public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
if (h == null) {
throw new NullPointerException();
}*/
proxy.luGuan();
}
}
Ø 首先获取一个业务接口的实现对象;
Ø 获取一个InvocationHandler实现,此处是
DiaoSiNanHanderHandler对象;
Ø 创建动态代理对象;
Ø 通过动态代理对象调用luGuan()方法,此时会在原始对象DiaoSiNan.luGuan()方法前后输出两句字符串。
运行测试类输出如下:
before method invoke!
我试撸管王
after method invoke!
此处Test类中的方法调用代码比较多,在我们的实际应用中可以通过配置文件来来简化客户端的调用实现。另外也可以通过动态代理来实现简单的AOP。
代理模式是我们比较常用的设计模式之一。其中新思想是为了提供额外的处理或者不同的操作而在实际对象与调用者之间插入一个代理对象。这些额外的操作通常需要与实际对象进行通信,代理模式一般涉及到的角色有:
抽象角色:声明真实对象和代理对象的共同接口;
代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。
以下以发送消息为例来说明一个简单的代理模式的基本实现:
首先明确目的:有一条消息,需要把这个消息发送出去,根据这个目的定义对应接口MessageHandler。需要的附加操作:假设需要验证消息的长度不能超过指定长度并且不能为空,并且我们需要统计相关信息发送到次数,超过指定的次数我们需要输出警报。我们通过代理模式来实现这个附加的操作。下面为对应的类关系图及示例代码。

//接口定义
public interface MessageHandler {
public void sendMessage(String msg);
}
//通过Email方式发送消息的实现类
public class EmailMessage implements MessageHandler {
@Override
public void sendMessage(String msg) {
// TODO Auto-generated method stub
System.out.println(msg+" send!!");
}
}
//消息处理的代理类
public class MessageProxy implements MessageHandler {
private static int count;
private MessageHandler emailMsg;
@Override
public void sendMessage(String msg) {
// TODO Auto-generated method stub
if(checkMessage(msg))
{
if(emailMsg==null) emailMsg=new EmailMessage();
count++;
emailMsg.sendMessage(msg);
System.out.println("Message sent:"+count);
}
}
private boolean checkMessage(String msg) {
return msg != null && msg.length() > 10;
}
}
//调用类
public class MainClass {
private static void runProxy(MessageHandler handler)
{
handler.sendMessage("message for test");
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
runProxy(new EmailMessage());
System.out.println("++++++++++++++++Pjroxy++++++++++++++++++");
runProxy(new MessageProxy());
}
}
输出
message for test send!!
++++++++++++++++Pjroxy++++++++++++++++++
message for test send!!
Message sent:1
在例子中我们可以方便的在消息发送过程中添加各种需要的附加处理方式,也能方便的替换消息的处理方式,如将通过Email发送消息替换为通过短信发送消息,而调用方不会有丝毫察觉!在任何你想要将一些额外操作分离到具体对象之外,特别是希望能够很容易做出修改,或者想在具体对象的方法执行前插入一些额外操作的时候,代理就显得十分有用!
例如:假设有一组对象都实现同一个接口,实现同样的方法,但这组对象中有一部分对象需要有单独的方法,传统的笨办法是在每一个应用端都加上这个单独的方法,但是代码重用性低,耦合性高。 如果用代理的方法则很好的解决了这个问题
假设有一个Italk接口,有空的方法talk()(说话),所有的people对象都实现(implements)这个接口,实现talk()方法, 前端有很多地方都将people实例化,执行talk方法,后来发现这些前端里有一些除了要说话以外还要唱歌(sing),那么我们既不能在Italk接 口里增加sing()方法,又不能在每个前端都增加sing方法,我们只有增加一个代理类talkProxy,这个代理类里实现talk和sing方法, 然后在需要sing方法的客户端调用代理类即可,代码如下:
接口类Italk
public interface Italk {
public void talk(String msg);
}
实现类people
public class People implements Italk
{ public String username;
public String age;
public String getName()
{
return username;
}
public void setName(String name) {
this.username= name;
}
public String getAge() {
return age; }
public void setAge(String age) {
this.age = age; }
public People(String name1, String age1) {
this.username= name1;
this.age = age1;
}
public void talk(String msg) {
System.out.println(msg+"!你好,我是"+username+",我年龄是"+age);
}
}
代理类talkProxy
public class TalkProxy implements Italk
{ Italk talker;
public TalkProxy (Italk talker)
{
//super();
this.talker=talker;
}
public void talk(String msg)
{
talker.talk(msg);
}
public void talk(String msg,String singname)
{
talker.talk(msg);
sing(singname);
}
private void sing(String singname)
{
System.out.println("唱歌:"+singname);
}
}
}
应用端myProxyTest
public class MyProxyTest {
/**代理模式 * @param args */
public static void main(String[] args)
{
//不需要执行额外方法的
Italk people1=new People("
湖海散人","18");
people1.talk("No ProXY Test");
System.out.println("-----------------------------");
//需要执行额外方法的
TalkProxy talker=new TalkProxy(people1);
talker.talk("ProXY Test","七里香");
}
}
所谓代理,是指具有与代理元(被代理的对象)具有相同的接口的类,客户端必须通过代理与被代 理的目标类交互,而代理一般在交互的过程中(交互前后),进行某些特别的处理。Proxy模式是很常见的模式,在我们生活中处处可见,例如我们买火车票不 一定非要到火车站去买,可以到一些火车票的代售点去买。寄信不一定是自己去寄,可以把信委托给邮局,由邮局把信送到目的地。
代理结构如下图所示

以通过代售点买火车票为例,代码实现如下:
//提供买票的公共接口
interface Passenger {
public void buyTicket();
}
//乘客实体
public class RealPassenger implements Passenger {
@Override
public void buyTicket() {
// TODO Auto-generated method stub
System.out.print("购买了火车票");
}
}
//代售点
public class Proxy implements Passenger {
Passenger passenger;
public Proxy(Passenger p) {
this.passenger = p;
}
@Override
public void buyTicket() {
// TODO Auto-generated method stub
System.out.println("通过代售点");
passenger.buyTicket();
}
}
//测试类
public class Client {
public static void main(String[] args) {
Passenger passenger = new RealPassenger();
Passenger proxy = new Proxy(passenger);
proxy.buyTicket();
}
}
输出结果:
通过代售点
购买了火车票
以 上的也可叫做静态代理,是为了区别代理模式在Java中的另一种实现——动态代理。
接下来会为大家带来动态代理