qileilove

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

Android自动化测试用例规范

目的:指导没接触过界面自动化的开发快速上手,不偏离正轨。不是自动化编码规范。
  安装/卸载测试用例
  当安装应用程序测试用例启动时,如果应用程序已被安装,首先执行卸载应用程序
  安装应用程序使用配置文件中设定的程序路径
  验证应用程序被安装,验证应用程序版本号
  验证应用程序被完全卸载,还需要验证主屏无该应用程序快捷方式
  通用测试用例规范
  测试用例名同测试用例的编号。
  每个测试用例粒度必须尽可能小,短小简单的测试用例易于调试。如果测试用例不得不长而复杂,则把它分成两个或更多的私有方法,并单独调用这些方法。尽量把重复任务放入一个方法中,这样它可以被多个测试用例调用。
  所有的测试用例必须作为一个独立的测试用例运行,每个独立的测试用例负责自己的初始化和清理任务:
  测试用例在setUp方法中构建该测试用例所需环境即前置条件,在tearDown方法中清理环境即该测试用例的后置条件,以实现测试用例间不相互依赖
  测试用例需要记录操作步骤。
  测试用例执行出错要截图,从日志查看错误能一目了然。
  测试用例要有合适的验证点,符合测试用例的期待结果。验证用是否存在的方法,如文件存在。
  测试用例只要不匹配预设的验证点,即使该测试用例还有未执行完的代码也要中断下面的执行,抛出合适的异常并提供详细的失败信息,然后设置该测试用例运行结果为失败
  测试用例要尽量处理所有的异常以健壮,对已知应用的程序的bug在处理的同时需要在代码注释中附上禅道上的bug库名和号。
  测试用例要支持横屏和竖屏。
  测试用例要能无人值守运行,即用户传递给测试程序若干必要的参数,自动化测试就会开始运行。
  测试用例要调用的文件要保存在文件夹\datafile内,跟自动化程序同目录。
  安装/启动/卸载测试用例失败后,设置其它所有测试用例执行失败并输出到日志。
  除安装/卸载测试用例,每个测试用例要判断SD卡是否存在,如果不存在则需要设置该测试用例执行失败。

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

APDPlat中数据库备份恢复的设计与实现

 APDPlat提供了web接口的数据库备份与恢复,支持手工操作和定时调度,可下载备份文件到本地,也可把备份文件发送到异地容错,极大地简化了数据库的维护工作
  设计目标:
  1、多数据库支持
  2、横切关注点隔离
  3、异地容错
  下面阐述具体的设计及实现:
  1、为了支持多数据库,统一的接口是不可避免的,如下所示:
/**
* 备份恢复数据库接口
* @author 杨尚川
*/
public interface BackupService {
/**
* 备份数据库
* @return 是否备份成功
*/
public boolean backup();
/**
* 恢复数据库
* @param date
* @return 是否恢复成功
*/
public boolean restore(String date);
/**
* 获取已经存在的备份文件名称列表
* @return  备份文件名称列表
*/
public List<String> getExistBackupFileNames();
/**
* 获取备份文件存放的本地文件系统路径
* @return 备份文件存放路径
*/
public String getBackupFilePath();
/**
* 获取最新的备份文件
* @return 最新的备份文件
*/
public File getNewestBackupFile();}


 对于各个不同的数据库来说,有一些通用的操作,如对加密的数据库用户名和密码的解密操作,还有接口定义的备份文件存放的本地文件系统路径,用一个抽象类来实现接口中的通用方法以及其他通用方法如decrypt:
/**
*备份恢复数据库抽象类,抽象出了针对各个数据库来说通用的功能
* @author 
*/
public abstract class AbstractBackupService implements BackupService{
protected final APDPlatLogger LOG = new APDPlatLogger(getClass());
protected static final StandardPBEStringEncryptor encryptor;
protected static final String username;
protected static final String password;
//从配置文件中获取数据库用户名和密码,如果用户名和密码被加密,则解密
static{
EnvironmentStringPBEConfig config=new EnvironmentStringPBEConfig();
config.setAlgorithm("PBEWithMD5AndDES");
config.setPassword("config");
encryptor=new StandardPBEStringEncryptor();
encryptor.setConfig(config);
String uname=PropertyHolder.getProperty("db.username");
String pwd=PropertyHolder.getProperty("db.password");
if(uname!=null && uname.contains("ENC(") && uname.contains(")")){
uname=uname.substring(4,uname.length()-1);
username=decrypt(uname);
}else{
username=uname;
}
if(pwd!=null && pwd.contains("ENC(") && pwd.contains(")")){
pwd=pwd.substring(4,pwd.length()-1);
password=decrypt(pwd);
}else{
password=pwd;
}
}
@Override
public String getBackupFilePath(){
String path="/WEB-INF/backup/"+PropertyHolder.getProperty("jpa.database")+"/";
path=FileUtils.getAbsolutePath(path);
File file=new File(path);
if(!file.exists()){
file.mkdirs();
}
return path;
}
@Override
public File getNewestBackupFile(){
Map<String,File> map = new HashMap<>();
List<String> list = new ArrayList<>();
String path=getBackupFilePath();
File dir=new File(path);
File[] files=dir.listFiles();
for(File file : files){
String name=file.getName();
if(!name.contains("bak")) {
continue;
}
map.put(name, file);
list.add(name);
}
if(list.isEmpty()){
return null;
}
//按备份时间排序
Collections.sort(list);
//最新备份的在最前面
Collections.reverse(list);
String name = list.get(0);
File file = map.get(name);
//加速垃圾回收
list.clear();
map.clear();
return file;
}    @Override
public List<String> getExistBackupFileNames(){
List<String> result=new ArrayList<>();
String path=getBackupFilePath();
File dir=new File(path);
File[] files=dir.listFiles();
for(File file : files){
String name=file.getName();
if(!name.contains("bak")) {
continue;
}
name=name.substring(0, name.length()-4);
String[] temp=name.split("-");
String y=temp[0];
String m=temp[1];
String d=temp[2];
String h=temp[3];
String mm=temp[4];
String s=temp[5];
name=y+"-"+m+"-"+d+" "+h+":"+mm+":"+s;
result.add(name);
}
//按备份时间排序
Collections.sort(result);
//最新备份的在最前面
Collections.reverse(result);
return result;
}
/**
* 解密用户名和密码
* @param encryptedMessage 加密后的用户名或密码
* @return 解密后的用户名或密码
*/
protected static String decrypt(String encryptedMessage){
String plain=encryptor.decrypt(encryptedMessage);
return plain;
}
}
下面来看一个MySQL数据库的实现:
/**
*MySQL备份恢复实现
* @author 杨尚川
*/
@Service("MYSQL")
public class MySQLBackupService extends AbstractBackupService{
/**
* MySQL备份数据库实现
* @return
*/
@Override
public boolean backup() {
try {
String path=getBackupFilePath()+DateTypeConverter.toFileName(new Date())+".bak";
String command=PropertyHolder.getProperty("db.backup.command");
command=command.replace("${db.username}", username);
command=command.replace("${db.password}", password);
command=command.replace("${module.short.name}", PropertyHolder.getProperty("module.short.name"));
Runtime runtime = Runtime.getRuntime();
Process child = runtime.exec(command);
InputStream in = child.getInputStream();
try(OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(path), "utf8");BufferedReader reader = new BufferedReader(new InputStreamReader(in, "utf8"))){
String line=reader.readLine();
while (line != null) {
writer.write(line+"\n");
line=reader.readLine();
}
writer.flush();
}
LOG.debug("备份到:"+path);
return true;
} catch (Exception e) {
LOG.error("备份出错",e);
}
return false;
}
/**
* MySQL恢复数据库实现
* @param date
* @return
*/
@Override
public boolean restore(String date) {
try {
String path=getBackupFilePath()+date+".bak";
String command=PropertyHolder.getProperty("db.restore.command");
command=command.replace("${db.username}", username);
command=command.replace("${db.password}", password);
command=command.replace("${module.short.name}", PropertyHolder.getProperty("module.short.name"));
Runtime runtime = Runtime.getRuntime();
Process child = runtime.exec(command);
try(OutputStreamWriter writer = new OutputStreamWriter(child.getOutputStream(), "utf8");BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(path), "utf8"))){
String line=reader.readLine();
while (line != null) {
writer.write(line+"\n");
line=reader.readLine();
}
writer.flush();
}
LOG.debug("从 "+path+" 恢复");
return true;
} catch (Exception e) {
LOG.error("恢复出错",e);
}
return false;
}
}
  这里的关键有两点,一是从配置文件db.properties或db.local.properties中获取指定的命令进行备份和恢复操作,二是为实现类指定注解@Service("MYSQL"),这里服务名称必须和配置文件db.properties或db.local.properties中jpa.database的值一致,jpa.database的值指定了当前使用哪一种数据库,如下所示:
#mysql
db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/${module.short.name}?useUnicode=true&characterEncoding=UTF-8&createDatabaseIfNotExist=true&autoReconnect=true
db.username=ENC(i/TOu44AD6Zmz0fJwC32jQ==)
db.password=ENC(i/TOu44AD6Zmz0fJwC32jQ==)
jpa.database=MYSQL
db.backup.command=mysqldump  -u${db.username} -p${db.password} ${module.short.name}
db.restore.command=mysql -u${db.username} -p${db.password} ${module.short.name}
 有了接口和多个实现,那么备份和恢复的时候究竟选择哪一种数据库实现呢?BackupServiceExecuter充当工厂类(Factory),负责从多个数据库备份恢复实现类中选择一个并执行相应的备份和恢复操作,BackupServiceExecuter也实现了BackupService接口,这也是一个典型的外观(Facade)设计模式,封装了选择特定数据库的逻辑。
  定时调度器和web前端控制器也是使用BackupServiceExecuter来执行备份恢复操作,BackupServiceExecuter通过每个实现类以@Service注解指定的名称以及配置文件
db.properties或db.local.properties中jpa.database的值来做选择的依据,如下所示:
/**
*执行备份恢复的服务,自动判断使用的是什么数据库,并找到该数据库备份恢复服务的实现并执行
* @author 杨尚川
*/
@Service
public class BackupServiceExecuter extends AbstractBackupService{
private BackupService backupService=null;
@Resource(name="backupFileSenderExecuter")
private BackupFileSenderExecuter backupFileSenderExecuter;
/**
* 查找并执行正在使用的数据的备份实现实例
* @return
*/
@Override
public boolean backup() {
if(backupService==null){
backupService=SpringContextUtils.getBean(PropertyHolder.getProperty("jpa.database"));
}
boolean result = backupService.backup();
//如果备份成功,则将备份文件发往他处
if(result){
backupFileSenderExecuter.send(getNewestBackupFile());
}
return result;
}
/**
* 查找并执行正在使用的数据的恢复实现实例
* @param date
* @return
*/
@Override
public boolean restore(String date) {
if(backupService==null){
backupService=SpringContextUtils.getBean(PropertyHolder.getProperty("jpa.database"));
}
return backupService.restore(date);
}
}
  关键是这行代码backupService=SpringContextUtils.getBean(PropertyHolder.getProperty("jpa.database"));
  2、在记录备份恢复日志的时候,如果每种数据库的实现类都要粘贴复制通用的代码到备份和恢复方法的开始和结束位置,那么四处就飘散着重复的代码,对易读性和可修改性都是极大的破坏。
  AOP是解决这个问题的不二之选,为了AOP能工作,良好设计的包结构、类层级,规范的命名都是非常重要的,尤其是这里的BackupServiceExecuter和真正执行备份恢复的实现类有共同的方法签名(都实现了BackupService接口),所以把他们放到不同的包里有利于AOP。
  使用AOP首先要引入依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
  其次是要在spring配置文件中指定启用自动代理:
  <aop:aspectj-autoproxy />
  最后就可以编写代码实现日志记录:
/**
* 备份恢复数据库日志Aspect
* org.apdplat.module.system.service.backup.impl包下面有多个数据库的备份恢复实现
* 他们实现了BackupService接口的backup方法(备份数据库)和restore(恢复数据库)方法
* @author 杨尚川
*/
@Aspect
@Service
public class BackupLogAspect {
private static final APDPlatLogger LOG = new APDPlatLogger(BackupLogAspect.class);
private static final boolean MONITOR_BACKUP = PropertyHolder.getBooleanProperty("monitor.backup");
private BackupLog backupLog = null;
static{
if(MONITOR_BACKUP){
LOG.info("启用备份恢复日志");
LOG.info("Enable backup restore log", Locale.ENGLISH);
}else{
LOG.info("禁用备份恢复日志");
LOG.info("Disable backup restore log", Locale.ENGLISH);
}
}
//拦截备份数据库操作
@Pointcut("execution( boolean org.apdplat.module.system.service.backup.impl.*.backup() )")
public void backup() {}
@Before("backup()")
public void beforeBackup(JoinPoint jp) {
if(MONITOR_BACKUP){
before(BackupLogType.BACKUP);
}
}
@AfterReturning(value="backup()", argNames="result", returning = "result")
public void afterBackup(JoinPoint jp, boolean result) {
if(MONITOR_BACKUP){
after(result);
}
}
//拦截恢复数据库操作
@Before(value="execution( boolean org.apdplat.module.system.service.backup.impl.*.restore(java.lang.String) ) && args(date)",
argNames="date")
public void beforeRestore(JoinPoint jp, String date) {
if(MONITOR_BACKUP){
before(BackupLogType.RESTORE);
}
}
@AfterReturning(pointcut="execution( boolean org.apdplat.module.system.service.backup.impl.*.restore(java.lang.String) )",
returning = "result")
public void afterRestore(JoinPoint jp, boolean result) {
if(MONITOR_BACKUP){
after(result);
}
}
private void before(String type){
LOG.info("准备记录数据库"+type+"日志");
User user=UserHolder.getCurrentLoginUser();
String ip=UserHolder.getCurrentUserLoginIp();
backupLog=new BackupLog();
if(user != null){
backupLog.setUsername(user.getUsername());
}
backupLog.setLoginIP(ip);
try {
backupLog.setServerIP(InetAddress.getLocalHost().getHostAddress());
} catch (UnknownHostException e) {
LOG.error("无法获取服务器IP地址", e);
LOG.error("Can't get server's ip address", e, Locale.ENGLISH);
}
backupLog.setAppName(SystemListener.getContextPath());
backupLog.setStartTime(new Date());
backupLog.setOperatingType(type);
}
private void after(boolean result){
if(result){
backupLog.setOperatingResult(BackupLogResult.SUCCESS);
}else{
backupLog.setOperatingResult(BackupLogResult.FAIL);
}
backupLog.setEndTime(new Date());
backupLog.setProcessTime(backupLog.getEndTime().getTime()-backupLog.getStartTime().getTime());
//将日志加入内存缓冲区
BufferLogCollector.collect(backupLog);
LOG.info("记录完毕");
}
}
select
    Vcs_Accd_Info.Date_Rcv AS "Date Rcv",
    Vcs_Accd_Info.Date_Send AS "Date Send",
    Vcs_Accd_Info.Rgn_Accd_Code AS "Rgn Accd Code",
    Count (*) AS "Case Cnt",
    Vcd_Sys_Cd.Desc AS "Region",
    'THIS' AS "Period",
    Vmi_Report_Date.Date_Rpt_Start AS "Date Rpt Start",
    Vmi_Report_Date.Date_Rpt_End AS "Date Rpt End",
    to_char(Vmi_Report_Date.Date_Rpt_Start ,'DD-MM-YYYY') ||' - '||to_char(Vmi_Report_Date.Date_Rpt_End,'DD-MM-YYYY') AS "Rep",
    case when Count (*)= 0 then 0 else days(Vcs_Accd_Info.Date_Rcv) - days(Vcs_Accd_Info.Date_Send)+1 end "Duration",
      case when Vcs_Accd_Info.Rgn_Accd_Code  = '1' then Count (*)  else 0 end "THKI_Case_Cnt",
      case when Vcs_Accd_Info.Rgn_Accd_Code  = '2' then Count (*)  else 0 end "TKE_Case_Cnt",
      case when Vcs_Accd_Info.Rgn_Accd_Code  = '3' then Count (*)  else 0 end "TKW_Case_Cnt",
      case when Vcs_Accd_Info.Rgn_Accd_Code  = '5' then Count (*)  else 0 end "TNTN_Case_Cnt",
      case when Vcs_Accd_Info.Rgn_Accd_Code  = '4' then Count (*)  else 0 end "TNTS_Case_Cnt",
      case when Vcs_Accd_Info.Rgn_Accd_Code  = '6' then Count (*)  else 0 end "OTH_Case_Cnt",
      case when Vcs_Accd_Info.Rgn_Accd_Code  = '' or Vcs_Accd_Info.Rgn_Accd_Code is null then 1 else 0 end "Unknown_Case_Cnt"
    FROM
    Vcs_Accd_Info, 
    Vcd_Sys_Cd,Vmi_Report_Date 
    WHERE
    (Vcs_Accd_Info.Last_Upd_Ts<>Vmi_Report_Date.Join_Ts
    AND Vcd_Sys_Cd.Last_Upd_Ts<>Vcs_Accd_Info.Last_Upd_Ts)
    AND ((Vcd_Sys_Cd.Sys_Code_Type='RG1'
    AND Vcs_Accd_Info.Date_Rcv >= Vmi_Report_Date.Date_Rpt_Start
    and Vcs_Accd_Info.Date_Rcv <= Vmi_Report_Date.Date_Rpt_End 
    AND Vmi_Report_Date.Rpt_Group=6
    AND Vcd_Sys_Cd.sys_code_key = Vcs_Accd_Info.Rgn_Accd_Code
    AND Vcs_Accd_Info.Date_Send<>'1111-11-11'))
      
    GROUP BY
    Vcs_Accd_Info.DATE_RCV, 
    Vcs_Accd_Info.DATE_SEND, 
    Vcs_Accd_Info.Rgn_Accd_Code, 
    Vcd_Sys_Cd.DESC, 
    'THIS', 
    Vmi_Report_Date.DATE_RPT_START, 
    Vmi_Report_Date.DATE_RPT_END,
    to_char(Vmi_Report_Date.Date_Rpt_Start ,'DD-MM-YYYY') ||' - '||to_char(Vmi_Report_Date.Date_Rpt_End,'DD-MM-YYYY')
  3、怎么样才能异地容错呢?将备份文件保存到与服务器处于不同地理位置的机器上,最好能多保存几份。除了能自动把备份文件传输到异地服务器上面,用户也可以从web界面下载。
  APDPlat使用推模型来发送备份文件,接口如下:
/**
* 备份文件发送器
* 将最新的备份文件发送到其他机器,防止服务器故障丢失数据
* @author 杨尚川
*/
public interface BackupFileSender {
public void send(File file);
}

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

Java实现webservice实例

http://yangjizhong.iteye.com/blog/579511
 

今天下午突然想研究下WEBSERVICE,从网上找了好多实例,但很多写的让人实在难以恭维,花了三个小时的圈圈转后总算搞通了,其实也挺简单的,这里特别感谢http://www.liuzm.com/article/java/992.htm这篇文章,总算让我弄明白了,现在贴出我的实例步骤,感谢那个博主的同时,与大家共同分享。
  一:首先创建个WEB工程,然后: http://ws.Apache.org/axis/网站下载Axis安装包.当然还依赖其他包的,我这里在附件里上传了所有应用到得包,方便大家。
  二:然后就写wsdd 文件(注:其实这里真的不用,可以跳过第二步)为了生成这个文件:server-config.wsdd
deploy.wsdd:
<deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="" target="_blank">http://xml.apache.org/axis/wsdd/providers/java">
<service name="HelloWorld" provider="java:RPC">
<parameter name="className" value="HelloWorld"/>
<parameter name="allowedMethods" value="sayHello"/>
</service>
</deployment>
  在DOS下转换目录到%TOMCAT_HOME%\webapps\axis\WEB-INF,命令:
  java -cp %AXISCLASSPATH% org.apache.axis.client.AdminClient deploy.wsdd
  三:可以自己写第二步中的文件,代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
<globalConfiguration>
<parameter name="sendMultiRefs" value="true"/>
<parameter name="disablePrettyXML" value="true"/>
<parameter name="dotNetSoapEncFix" value="true"/>
<parameter name="enableNamespacePrefixOptimization" value="false"/>
<parameter name="sendXMLDeclaration" value="true"/>
<parameter name="sendXsiTypes" value="true"/>
<parameter name="attachments.implementation" value="org.apache.axis.attachments.AttachmentsImpl"/>
</globalConfiguration>
<handler type="java:org.apache.axis.handlers.http.URLMapper" name="URLMapper"/>
<service name="Login.jws" provider="java:RPC">
<parameter name="className" value="server.SayHello"/>
<parameter name="scope" value="request"/>
<parameter name="allowedMethods" value="*"/>
<messageReceiver class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/>
</service>
<transport name="http">
<requestFlow>
<handler type="URLMapper"/>
</requestFlow>
</transport>
</deployment>


 放到 WEB-INF 目录下 和web.xml一起,其中,WEB.XML可以直接从你下载的axis项目中拿来到自己工程就好,或者自己添加:
<servlet>
<servlet-name>AxisServlet</servlet-name>
<servlet-class>org.apache.axis.transport.http.AxisServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AxisServlet</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>
  四:服务端提供的方法:
package server;
public class SayHello {
public String getName(String name) {
return "hello====>" + name;
}
}
  五:客户端访问服务端接口获得数据的方法:
package client;
import org.apache.axis.client.Call;
import org.apache.axis.client.Service;
public class TestClient {
public static void main(String[] args) throws Exception {
// 指出service所在URL
String endpoint = "http://127.0.0.1:8082/webservice/services/Login.jws";
// 创建一个服务(service)调用(call)
Service service = new Service();
Call call = (Call) service.createCall();// 通过service创建call对象
// 设置service所在URL
call.setTargetEndpointAddress(new java.net.URL(endpoint));
// 方法名(processService)与MyService.java方法名保持一致
call.setOperationName("getName");
// Object 数组封装了参数,参数为"This is Test!",调用processService(String arg)
String ret = (String) call.invoke(new Object[] { "继中" });
System.out.println(ret);
}
}
  六:启动tomcat,先访问http://127.0.0.1:8082/webservice/services,看是否有方法显示,有显示,则代表服务端提供的接口可以成功访问了。
  我的效果是这样:
And now... Some Services
Login.jws (wsdl)
getName
  七:然后在本地跑客户端TestClient,就应该有结果啦,大家试试吧。

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

性能测试之LoadRunner检查点

 概述
  1.检查点概念
  2.实例
  以下是详细介绍
  检查点:首先来看一下VuGen确定脚本运行成功的判断条件。在录制编写脚本后,通常就会进行回放,如果回放通过没有错误,就认为脚本是正确的。究竟VuGen怎么区分脚本是否回放正确呢?VuGen判断脚本是否执行成功是根据服务器返回的状态来确定的,如果服务器返回的http状态为200 OK,那么VuGen就认为脚本正确地运行了,并且是运行通过的。在绝大多数系统出错时会返回错误页面吗?不会,一般系统都会返回一个消息提示框,来提升用户感受。例如 “网站忙,请稍后”。其实这个时候网站已经无法正确响应用户请求了,要检查点函数帮助验证请求发送出去后,服务器的返回是不是期望的内容,如果不是,那么就说明服务器无法提供正常的服务了
  例子<以login为例>
  Step1.录制登陆login脚本
  Step2.将脚本切换到tree模式 <在需要添加检查点的地方,选中右击选择添加检查点>
  Step3.添加事物判断
  Step4,运行脚本,查看结果

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

自动化测试之读写64位操作系统的注册表

 非Web程序(桌面程序)的设置一般都存在注册表中。 给这些程序做自动化测试时, 需要经常要跟注册表打交道。 通过修改注册表来修改程序的设置。
  本章介绍如何利用C#程序如何操作注册表, 特别是如何操作64位操作系统的注册表。
  阅读目录
  自动化测试经常需要修改注册表
  Windows注册表简介
  C#修改注册表
  32位机器和64位机器注册表的区别
  C#程序访问64位操作系统的注册表
  自动化测试经常需要修改注册表
  很多系统的设置(比如:IE的设置)都是存在注册表中。 桌面应用程序的设置也是存在注册表中。 所以做自动化测试的时候,经常需要去修改注册表
  Windows注册表简介
  注册表编辑器在 C:\Windows\regedit.exe。   或者在运行中,运行”regedit”. 就可以启动注册表编辑器。
  注册表由主键,键,子键,值项构成。  如下图
  主键中的:HKEY_CURRENT_USER和HKEY_LOCAL_MACHINE中的内容差不多,一个是当前用户的设置,一个是机器的设置。
  C#修改注册表
  C#修改注册表实在是太简单了,先添加 using Microsoft.Win32; 几行代码就搞定了,  看下面的实例, 读,增,删,改,操作都有了。
static void Main(string[] args)
{
// 实例, 修改IE主页
RegistryKey localMachine = Registry.CurrentUser;
RegistryKey sougou = localMachine.OpenSubKey(@"SOFTWARE\Microsoft\Internet Explorer\MAIN", true);
// 获取IE 的主页
string version = sougou.GetValue("Start Page").ToString();
//  修改IE的主页
sougou.SetValue("Start Page", "http://www.cnblogs.com/", RegistryValueKind.String);
// 修改Tanktest这个值项, 如不存在,则新建TankTest值项。
sougou.SetValue("TankTest2", "1", RegistryValueKind.DWord);
// 删除值项
sougou.DeleteValue("TankTest2");
// 新建子键
sougou.CreateSubKey("This is subkey1");
sougou.CreateSubKey("This is subkey2");
// 删除子键
sougou.DeleteSubKey("This is subkey1");
}

 32位操作系统和64位操作系统注册表的区别
  上面的代码在32位的操作系统上运行没问题, 但是在64位操作系统中就不行了。
  应用软件也有32位和64位之分。  在64位的操作系统中, 可以运行32位的应用程序和64位的应用程序。
  如果在64位操作系统中安装32位的应用程序, 会安装到C:\Program Files(x86)\下。 启动任务管理器,你会看到32位的程序的进程名字后会带一个 ”*32“, 如下图:
  注意: 64位操作系统中:
  64位的程序的注册表还在: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer
  32位的程序的注册表而是在: HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Internet Explorer
  C#程序访问64位操作系统的注册表
  C#的程序都是32位的,访问注册表的时候,会访问HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\, 而访问不到HKEY_LOCAL_MACHINE\SOFTWARE\
  .NET 3.5以前 C#程序需要通过Win32API函数(要写几百行代码)才能访问64位操作系统的注册表。
  .NET 4.0 后访问64位操作系统的注册表很简单了。
static void Main(string[] args)
{
// 修改64位操作系统的注册表
// 修改IE的首页
// 通过RegistryView 来指定是64位操作系统还是32位
RegistryKey localKey = RegistryKey.OpenBaseKey(Microsoft.Win32.RegistryHive.CurrentUser, RegistryView.Registry64);
localKey = localKey.OpenSubKey(@"SOFTWARE\Microsoft\Internet Explorer\Main", true);
if (localKey != null)
{
localKey.SetValue("Start Page", "http://www.cnblogs.com");
}
}

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

Java复用、多态&接口

  可以为每一个类都创建main()方法,便于单元测试
  当创建一个导出类的对象时,该对象包含一个包装在导出类对象内部的基类对象,所以java会自动的在导出类的构造器中插入对基类构造器的调用(super),在构造过程中是从基类“向外”扩散。
  带参数的构造器:
  如果基类的构造器带有参数,如Super(int i){},则导出类的构造器既可以是无参,也可以有参,如Extend() or Extend(int i),但在导出类构造器中必须用用super(int i)调用基类的构造器,否则将调用基类默认的构造器,找不到则出错!
  代理:介于继承与组合,我们将一个成员对象置于所要构造的类中,但与此同时我们在新类中暴露了该成员对象的所有方法(就像继承),通过代理可以选择提供成员对象部分方法,java不直接支持代理!
  向上转型:导出类转换成基类,实际上仅仅是堆栈中的类引用的变化,增加了一个基类引用,指向堆中的对象(导出类引用和基类引用指向同一个对象),与导出类引用相比,基类引用仅仅是少了一些借口而已,如果基类引用指向的是一个导出类对象,则向下转型不会发生任何问题。
  final 数据:常量,参数:参数引用所指向的对象无法更改,方法:锁定,不被覆盖,类:不被继承
  private方法被自动认为是final方法,而对导出类是屏蔽的,不可重载
  “封装”通过合并特征和行为来创建新的数据类型。“实现隐藏”则通过将细节“私有化”把接口与实现分离,多态消除类型之间的偶合关系。
  方法绑定(将一个方法调用同一个方法主题关联起来):前期绑定(通过编译器和连接程序实现)用于面向过程的语言;后期绑定(动态、运行时绑定)在运行时根据对象的类型进行绑定,在运行是判断对象的类型,从而调用恰当的方法。(此处说法与自己的理解稍有出入,主要是还不明白对象类方法调用机制)
  构造器内部的多态方法的行为:如果在一个构造器内部调用正在构造的对象的某个动态绑定方法,例子如下:
public class Test {
public static void main(String[] args) {
new B();
}
}
class A {
void func() { System.out.println(&quot;fA&quot;); }
A() {
System.out.println(&quot;begin A&quot;);
func();
System.out.println(&quot;end A&quot;);
}
}
class B extends A {
void func() { System.out.println(&quot;fB &quot; + i); }
int i = 1;
B() {
super();
System.out.println(&quot;B &quot; + i);
}
}
/*output
begin A
fB 0
end A
B 1
*/
  (对象的初始化顺序见《初始化与清理》)首先执行基类A的构造方法,再调用func()方法,由于func()方法已被导出类B覆盖,实际上执行的是B.func(),最后再执行类B的构造方法。问题的关键是在A中执行B.func()时,输出的i是0,而不是1,说明此时导出类B的成员还未初始化,这也验证了《初始化与清理》中初始化的顺序问题。所以在构造器中唯一能安全调用的方法是基类的final方法。
  接口被用来建立类与类之间的协议

posted @ 2014-02-11 10:50 顺其自然EVO 阅读(196) | 评论 (0)编辑 收藏

一个强大的工具来模拟数百万​​并发用户负载测试:Gryphon

  Gryphon是由网易自主研发的能够模拟千万级别并发用户的一个软件,目的是能够用较少的资源来模拟出大量并发用户,并且能够更加真实地进行压力测试, 以解决网络消息推送服务方面的压力测试的问题和传统压力测试的问题。
  Gryphon分为两个程序,一个运行gryphon,用来模拟用户,一个是 intercept,用来截获响应包信息给gryphon。Gryphon模拟用户的本质是用一个连接来模拟一个用户,所以有多少个连接,就有多少个用户,而用户的素材则取自于pcap抓包文件。
  值得注意的是,Gryphon架构类似于tcpcopy,也可以采用传统使用方式和高级使用方式。
  特性
  1)无需绑定多个ip地址
  2)客户端可使用的ip地址个数不受限制
  3)并发用户数量不受os的限制
  4)只要是可回放的协议,一般都支持
  5)可支持的最大并发用户数,取决于该机器的带宽,cpu和内存
  6)所使用的会话数据均从pcap文件中抽取,可以保持在线的多种特性
  下载地址
  intercept程序
git clone git://github.com/wangbin579/tcpcopy.git
  gryhpon程序
git clone git://github.com/wangbin579/gryphon.git
Gryphon configure Options
–enable-debug compile Gryphon with debug support (saved in a log file)
–enable-advanced run Gryphon at advanced mode (advanced archecture)
–enable-dlinject send packets at the data link layer instead of the IP layer
–enable-single 单一实例运行方式(跟intercept一一对应),适合于高效使用
–enable-comet 消息推送模式

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

IOS、Android自动化测试框架Appium概述

  Appium
  Appium是一个开源、跨平台的测试框架,可以用来测试原生及混合的移动端应用。Appium支持IOS、Android及FirefoxOS平台。Appium使用WebDriver的json wire协议,来驱动Apple系统的UIAutomation库、Android系统的UIAutomator框架。Appium对IOS系统的支持得益于Dan Cuellar’s对于IOS自动化的研究。Appium也集成了Selendroid,来支持老android版本。
  使用Appium进行自动化测试有两个好处:
  1. Appium在不同平台中使用了标准的自动化APIs,所以在跨平台时,不需要重新编译或者修改自己的应用。
  2. Appium支持Selenium WebDriver支持的所有语言,如java、Object-C、JavaScript、Php、PythonRuby、C#、Clojure,或者Perl语言,更可以使用Selenium WebDriver的Api。Appium支持任何一种测试框架。如果只使用Apple的UIAutomation,我们只能用javascript来编写测试用例,而且只能用Instruction来运行测试用例。同样,如果只使用Google的UIAutomation,我们就只能用java来编写测试用例。Appium实现了真正的跨平台自动化测试。
  Requirements
  总体:
  IOS自动化测试需要Mac os操作系统
  Mac OS X 10.7或者更高版本,推荐10.8.4版本
  Android自动化测试可以在Mac、Linux上进行。对于Windows平台的支持还在beta阶段
  需要安装node和npm(node版本高于0.8)
  IOS自动化:
  Mac Xcode
  Apple开发者工具(iphone模拟器sdk,及命令行工具)
  Android自动化:
  Android SDK API版本 >= 17,即android版本高于4.2
  快速入门
  方案1: 使用Appium.app
  下载appium.app dmg
  在Apple系统上安装appium.app,就可以直接运行自己的case
  方案2: 使用node从命令行运行appium
  安装node及npm
  下面命令是在linux系统中安装appium
mkdir appium-test && cd appium-test
npm install -g appium  # might have to do this with sudo
sudo authorize_ios # enable developer use of iOS sim
npm install wd
curl -O https://raw.github.com/appium/appium/master/sample-code/examples/node/simplest.js
appium &
node simplest.js
  下面是一些不同语言编写的appium的测试用例
  Example Tests: Node.js | Python | PHP | Ruby | Java
  问题解决
  如果使用过程遇到问题,可以参考这里,这里包含了一些常见错误的解决方法,也包含appium组织的联系方法。

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

敏捷开发(Agile)中的性能测试

与传统开发过程相比,敏捷开发能够更好、更快的提供潜在可发布版本,同时需求的变化对产品带来的冲击也降到了最小。那么如何更好,更有效的在这种快速迭代,快速集成的开发思想下做性能测试也成了大家研究的方向,综合了很多大牛的思想和我对Agile开发的理解,做一个个人总结:
  性能测试的阶段:每个Sprint
  在Sprint Planning之初,首先需要明确需要性能测试的Story,定义可量化的性能测试目标,然后添加性能测试的任务,性能测试是否需要单独创建user story要依产品而定:
  1. 对于即刻发布的版本(以移动应用为例):最好在将性能测试与user story放到一块,这样才能更好地track user story的是否可交付(是否做完性能测试)。
  2. 对于周期长,潜在可发布版本不会立即到用户手中的项目:建议单独为性能测试创建user story。原因之一是这种项目比较庞大,各个user story之间的集成比较复杂,同一个性能测试关联的user story非常多,单独创建能够更清晰,也不会影响user story的commit。
  测试对象:由于一个个的story相对独立,所以测试的对象可以是小到一个个函数或接口,达到一一个端到端的场景(这种情况下需要考虑其他模块或第三方软件对性能的影响)。
  测试的执行:对与单个的性能测试任务,流程基本和传统性能测试相同,但整个流程需要对该story的每一个改动后的可测版本执行:
  1. 定义性能场景
  2. 选取监控指标(可参考Acceptance Criteria)
  3. 模拟负载(可以通过自动化脚本和工具产生)
  4. 收集数据和生成报表。
  验收:主要是参照sprint planning中定义的性能acceptance criteria来评估潜在可交付版本的性能。
版权声明:本文出自 AlvinXu 的51Testing软件测试博客:http://www.51testing.com/?554494

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

性能测试之LoardRunner自动关联

 概述:
  1.什么是自动关联?
  2.实例介绍
  以下是详细介绍:
  自动化关联:它是VuGen提供的自动化扫描关联处理策略,它的原理是对同一个脚本运行和录制时的服务器返回进行比较,来自动查找变化的部分,并且提示是否生成关联
  实例介绍<以登陆为例进行演示>
  Step1.录制login
  Step2.打开菜单 Vuser-->Scan Script for Correlations,进行扫描需要关联的地方,
  Step3.当LoardRunner自动扫描后,需要关联的地方会出现在Correlation Results下面,然后选中,点击右边的Correlate进行关联
  注意:并不是每次都能扫描的到,如本次笔者就没有扫描到,所以还是建议用手动关联

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

仅列出标题
共394页: First 上一页 150 151 152 153 154 155 156 157 158 下一页 Last 
<2024年11月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

导航

统计

常用链接

留言簿(55)

随笔分类

随笔档案

文章分类

文章档案

搜索

最新评论

阅读排行榜

评论排行榜