51Testing软件测试论坛

标题: 自己收集的一些资料,41楼贴上自己写的基于selenium的自动化框架相关的代码 [打印本页]

作者: ryugun    时间: 2011-9-23 21:23
标题: 自己收集的一些资料,41楼贴上自己写的基于selenium的自动化框架相关的代码
本帖最后由 ryugun 于 2012-3-2 17:08 编辑

[attach]75412[/attach][attach]75413[/attach]如题


如题,这个帖子是自己为了工作方便,整理的一些资料在这里,到41楼(第3页)的地方,贴上一些自己这1年来设计的自动化测试框架

基于selenium的关键字驱动的框架,可能大家看不懂,没关系,我自己能看懂就行了~!
作者: ryugun    时间: 2011-9-23 21:28
http://www.51testing.com/?uid-14 ... space-itemid-245295
作者: ryugun    时间: 2011-9-23 21:30
http://www.51testing.com/?uid-15 ... space-itemid-237433
作者: ryugun    时间: 2011-9-23 21:30
http://www.51testing.com/?uid-37 ... space-itemid-245315
作者: ryugun    时间: 2011-9-23 21:32
http://www.51testing.com/html/9/28.html
作者: vv6515    时间: 2011-9-24 17:02
没事发那么多连接做什么的~~
作者: ryugun    时间: 2011-9-26 21:29
[attach]75458[/attach][attach]75459[/attach][attach]75460[/attach][attach]75461[/attach]
作者: ryugun    时间: 2011-9-26 21:30
[attach]75462[/attach][attach]75463[/attach][attach]75464[/attach]
作者: ryugun    时间: 2011-9-26 21:35
[attach]75465[/attach]
作者: ryugun    时间: 2011-10-7 11:46
[attach]75525[/attach]
作者: ryugun    时间: 2011-10-20 22:24
[attach]75695[/attach]
作者: love6546607    时间: 2011-10-20 23:45
这是什么?
作者: ryugun    时间: 2011-11-3 22:42
[attach]75897[/attach][attach]75898[/attach][attach]75899[/attach]
作者: ryugun    时间: 2011-11-3 22:43
http://www.51testing.com/?uid-12 ... space-itemid-233317
作者: ryugun    时间: 2011-11-3 22:44
http://www.51testing.com/html/99/n-78499.html
作者: ryugun    时间: 2011-11-9 21:49
1.@ContextConfiguration(locations = "file:WebRoot/WEB-INF/applicationContext-*.xml")
作者: ryugun    时间: 2011-11-9 22:01
Eclipse普通的Run模式没有问题,Debug模式却启动不了.换了Eclipse,MyEclipse,JDK都不解决问题

后来将机器上安装的城市热点认证软件关闭,Debug模式终于可以正常启动了.



原来是城市热点和JVM的调试模式冲突了.

使用Eclipse对Java代码进行调试,无论是远程JVM还是本地JVM都会进行Socket通讯.发生这样的错误是由于Socket通讯不上造成的.



城市热点是相当霸道的,不但会修改winsock,还会监听和占用一些端口.不单是Eclipse Debug和它冲突



很多软件都受到限制.



另外对于本地调试.JVM会在localhost上建立socket的,可以在命令行下ping一下localhost,确保hosts没有被篡改.



还可以在命令行下使用命令netsh winsock reset 重置 Winsock 目录



关闭城市热点之后,不但Java  Debug好用了,Tomcat Server 的远程调试也OK了.终于找到病根了.
作者: ryugun    时间: 2011-11-10 21:27
[attach]75983[/attach][attach]75984[/attach][attach]75985[/attach][attach]75986[/attach]
作者: ryugun    时间: 2011-11-21 21:30
[attach]76200[/attach][attach]76201[/attach]
作者: ryugun    时间: 2011-12-15 22:24
[attach]76674[/attach][attach]76675[/attach][attach]76676[/attach][attach]76677[/attach]
作者: ryugun    时间: 2011-12-15 22:39
http://www.51testing.com/html/57/1872.html
作者: ryugun    时间: 2011-12-15 22:39
http://www.51testing.com/?uid-22 ... space-itemid-240823
http://www.51testing.com/?uid-67 ... space-itemid-229136
作者: ryugun    时间: 2011-12-15 22:40
http://www.51testing.com/?uid-35 ... space-itemid-241250
http://www.51testing.com/?uid-17 ... space-itemid-241638
http://www.51testing.com/html/11/n-11611.html
作者: ryugun    时间: 2011-12-15 22:41
http://www.51testing.com/?uid-39 ... space-itemid-243315
作者: ryugun    时间: 2011-12-15 22:45
[attach]76678[/attach]
作者: ryugun    时间: 2011-12-23 22:45
[attach]76822[/attach][attach]76823[/attach][attach]76824[/attach]
作者: ryugun    时间: 2011-12-25 15:39
[attach]76830[/attach]
作者: ryugun    时间: 2011-12-28 20:52
struts2-core-2.1.8.1 不支持tree标签。
struts2.1.8,加了struts2-dojo-plugin-2.1.8.jar后,
引用<%@ taglib prefix="sd" uri="/struts-dojo-tags"%>才能使用tree标签。  

struts2里面使用 tree标签 需要导入包:struts2-dojo-plugin-2.1.8.1.jar



然后 tree.jsp 页面:

a. 引入标签库

Java代码  
<%@ taglib prefix="sx" uri="/struts-dojo-tags" %>  





b. 需要在<head></head>内加入:  

Java代码  
<sx:head parseContent="true"/>  





c. 使用tree标签:<sx:tree />
作者: ryugun    时间: 2011-12-28 21:38
如果你的Struts版本是2.1.6以上的,那就要加struts2-dojo-plugin-2.1.6.jar,Struts2升级以后把Ajax单独提出来放这个里面了。

像我用的就是struts2.1.8,加了struts2-dojo-plugin-2.1.8.jar后,
页面改成:
<%@ taglib prefix="s" uri="/struts-tags"%>
<%@ taglib prefix="sd" uri="/struts-dojo-tags"%>

<s:head />
<sd:parseContent="true"/>
作者: ryugun    时间: 2011-12-28 22:12
[attach]76898[/attach]
作者: ryugun    时间: 2012-1-4 20:43
[attach]76970[/attach]
作者: ryugun    时间: 2012-1-9 22:08
[attach]77038[/attach]
作者: ryugun    时间: 2012-1-9 22:20
[attach]77039[/attach]
作者: 580231喧    时间: 2012-2-1 13:42

作者: ryugun    时间: 2012-2-5 22:13
[attach]77236[/attach]
作者: ryugun    时间: 2012-2-5 22:27
[attach]77237[/attach]
作者: ryugun    时间: 2012-2-7 23:06
[attach]77266[/attach]
作者: ryugun    时间: 2012-2-7 23:09
[attach]77267[/attach]
作者: ryugun    时间: 2012-3-1 21:50
[attach]77828[/attach][attach]77829[/attach]
作者: ryugun    时间: 2012-3-2 15:25
本帖最后由 ryugun 于 2012-3-24 17:32 编辑

接下来:自己写的一点 通过post请求的方式 辅助selenium构建测试环境

页面请求是通过一个抓包工具抓取到的,工具名称Fiddler
作者: ryugun    时间: 2012-3-2 15:27
所需要的jar包:
commons-codec-1.5.jar
commons-httpclient-3.1.jar
commons-logging-1.1.1.jar
httpclient-4.1.1.jar
httpcore-4.1.jar
httpmime-4.1.1.jar
作者: ryugun    时间: 2012-3-2 15:30
本帖最后由 ryugun 于 2012-3-2 16:45 编辑

先是通过post请求方式登陆:
代码如下:

关键代码在里面,其他的代码请不要关注:

步骤1:先是通过post请求方式登陆:(为什么要登陆呢?主要是 取得session 以及 对付权限问题:没有登陆就不能做其他事呀~)
代码如下:

关键代码在里面,其他的代码请不要关注:

package com.XXXXX.XXXX.action.bypost.common;

import java.io.IOException;
import java.util.Map;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.methods.GetMethod;

import com.XXXX.Constants;


/**
* 通过Post请求的方式登陆
*  
* <p/>excel对应关键字:loginByPost
*
*/
public class LoginByPost extends BaseActionByPost {

    /**
     * 通过Post请求的方式登陆
     * @return context aw参数传递上下文对象
     * @param context aw参数传递上下文对象
     * @param str 包含各种参数的数组(从用例数据文件里读取出的一行数据)
     * <p/>str[0]:当前数据行状态(eg:准备/测试点/恢复)
     * <p/>str[1]:动作关键字
     * <p/>str[2]:pageURL 登陆页面的URL(eg:http://XXXX/)
     * <p/>str[3]:用户名
     * <p/>str[4]:密码
     */
    public Map<String, Object> execute(Map<String, Object> context, String[] str) {
        client = new HttpClient(); //注意,此处的client是在父类BaseActionByPost 中定义的
        
        GetMethod openURLPost = new GetMethod(str[2]); //构造请求:打开登陆页面
        //利用用户名和密码构造登陆post请求URL(str[3]/用户名, str[4]/密码)
        String loginPostURL = Constants.BASEURL + "/j_spring_security_check?j_username=" + str[3] +
                                "&j_password=" + str[4] + "&_spring_security_remember_me=null";
        GetMethod loginPost = new GetMethod(loginPostURL); //构造请求:登陆
        Header[] getLoginPageHeaders = null; //headers
        
        try {
            client.executeMethod(openURLPost); //提交Post请求并返回状态码
            getLoginPageHeaders = openURLPost.getResponseHeaders();
            
            String cook_session_id = null; // 在返回的cookie中获取session_id相关内容
            for (Header getLoginPageHeader : getLoginPageHeaders) {
                if (getLoginPageHeader.getName().equals("Set-Cookie")) {
                    cook_session_id = getLoginPageHeader.getValue();
                }
            }
            
            sessionIDByPost = cook_session_id.split(";")[0]; // 截取session
            if (null == sessionIDByPost) {
                throw new AssertionError();
            }
            
            loginPost.addRequestHeader(Constants.COOKIE, sessionIDByPost); //将sessionID设置到登陆请求里
            int respanceCode = client.executeMethod(loginPost); //提交Post请求并返回状态码
            
            if (200 == respanceCode) { //判断返回的状态码是否正确
            } else {
            }
            
        } catch (HttpException e) {

        } catch (IOException e) {

        } catch (Exception e) {

        } finally {
            openURLPost.releaseConnection(); //释放
            loginPost.releaseConnection();
        }
        context.put(Constants.SESSIONIDBYPOST, sessionIDByPost); //设置sessionID
        context.put(Constants.CLIENT, client);
        return context;

    }

}
作者: ryugun    时间: 2012-3-2 15:43
接下来,把上面提到的BaseActionByPost代码贴出来
我自己在做的时候,通过Post请求方式的类都会继承这个父类,里面写了一些公用的方法

package com.XXXX.action.bypost.common;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Map;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;

import com.XXXX.Constants;
import com.XXXX.action.common.BaseAction;

/**
* Action基类(通过Post请求)
*
*/
public abstract class BaseActionByPost implements BaseAction {
   
    /**
     * client对象
     */
    public HttpClient client = null;
   
    /**
     * sessionID
     */
    public String sessionIDByPost = "";

    /**
     * 执行动作
     * @param context aw参数传递上下文Map对象
     * @param str 包含各种参数的数组(从用例数据文件里读取出的一行数据)
     * @return aw参数传递上下文Map对象
     */
    public abstract Map<String, Object> execute(Map<String, Object> context, String[] str);
   
    /**
     * 构造Get请求并提交(GetMethod)
     * @param encodURL 待提交的Get请求
     * @return 提交请求后返回的状态码
     */
    public int doGet(String encodURL) {
        GetMethod get = new GetMethod(encodURL); //构造请求
        
        get.addRequestHeader(Constants.COOKIE, sessionIDByPost); //为请求设置cookie
        int respanceCode = 0; //状态码
        
        try {
            respanceCode = client.executeMethod(get); //提交Post请求并返回状态码
        } catch (HttpException e) {
        } catch (IOException e) {
        } catch (Exception e) {
        } finally {
            get.releaseConnection(); //释放连接
        }
        
        return respanceCode;
    }
   
    /**
     * 构造Post请求并提交(PostMethod)
     * @param encodURL 待提交的Post请求
     * @return 提交请求后返回的状态码
     */
    public int doPost(String encodURL) {
        PostMethod post = new PostMethod(encodURL); //构造请求
        
        post.addRequestHeader(Constants.COOKIE, sessionIDByPost); //为请求设置cookie
        int respanceCode = 0; //状态码
        
        try {
            respanceCode = client.executeMethod(post); //提交Post请求并返回状态码
        } catch (HttpException e) {
        } catch (IOException e) {
        } catch (Exception e) {
        } finally {
            post.releaseConnection(); //释放连接
        }
        
        return respanceCode;
    }
   


作者: ryugun    时间: 2012-3-2 15:44
接着是BaseACtionByPost的代码,上面还没有贴完呢


    /**
     * 初始化client和sessionID
     * @param context aw参数传递上下文对象
     */
    public void init(Map<String, Object> context) {
        client = (HttpClient) context.get(Constants.CLIENT);
        if (null == client || null == context.get("sessionIDByPost")) {
            logger.error("初始化client或sessionID失败:client或sessionID为空!");
            throw new AssertionError();
        }

        sessionIDByPost = context.get("sessionIDByPost").toString(); //session
    }
   
    /**
     * 上传文件(提交上传文件的请求)
     * @param encodURL 待提交的Post请求
     * @param parts post请求的参数(egart[] parts = {new FilePart("filedata", file)};)
     * @return 提交请求后返回的状态码
     */
    public int upload(String encodURL, Part[] parts) {
        PostMethod filePost = new PostMethod(encodURL); //构造请求
        filePost.addRequestHeader(Constants.COOKIE, sessionIDByPost); //为请求设置cookie

        int respanceCode = 0;
        try {
            filePost.setRequestEntity(new MultipartRequestEntity(parts, filePost.getParams()));
            respanceCode = client.executeMethod(filePost);
        } catch (HttpException e) {
        } catch (IOException e) {
        } catch (Exception e) {
        } finally {
            filePost.releaseConnection();
        }
        
        return respanceCode;
    }
   
    /**
     * 打开一个文件
     * @param filePath 文件路径
     * @return
     */
    public File getFile(String filePath) {
        File file = new File(filePath);
        if (!file.exists()) {
            logger.error("待上传文件不存在!" + filePath);
            throw new AssertionError();
        }
        
        return file;
    }
   
    /**
     * 对字符串进行转码
     * @param str 待转码的字符串
     * @return String 转码后的字符串
     */
    public String encod(String str) {
        String encodURL = str;
        try {
            encodURL = java.net.URLEncoder.encode(str, "UTF-8"); //转码
        } catch (UnsupportedEncodingException e) {
            logger.warn("转码失败:" + str, e);
        }
        return encodURL;
    }
   
    /**
     * 取工程目录下参数目录路径
     * @param path 当前类class文件所在路径
     * @return Params目录路径
     */
    public static String getParamsPath(String path) {
        path = path.replaceAll("bin/com/XXXX/action/bypost/common/", "");
        path = path.replaceFirst("/", "").trim();
        path = path.replaceAll("/", "\\\\");
        path = path + "params\\";
        try {
            path = URLDecoder.decode(path, "UTF-8"); //对URL路径中的编码进行解码
        } catch (UnsupportedEncodingException e) {
            logger.warn("文件路径解码出现异常!", e);
        }
        return path;
    }

}

作者: ryugun    时间: 2012-3-2 15:53
本帖最后由 ryugun 于 2012-3-2 15:58 编辑

步骤2:在步骤1登陆成功的基础上(主要是获取session),以上传一个东西为例(selenium上传东西是弱项)

package com.XXX.action.bypost.system.blanch;

import java.util.Map;

import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.methods.multipart.StringPart;

import com.XXX.Constants;
import com.XXX.action.bypost.common.BaseActionByPost;
import com.XXX.action.bypost.common.GetIdByQuerySQL;

/**
* 通过Post请求的方式导入单位
*  
* <p/>excel对应关键字:importBlanchByPost
*
*/
public class ImportBlanchByPost extends BaseActionByPost {
    /**
     * 导入单位的PostURL
     */
    public static final String POSTURL = Constants.BASEURL + "/department/importDepartment.do";

    /**
     * 通过Post请求的方式导入单位
     * @return context aw参数传递上下文对象
     * @param context aw参数传递上下文对象
     * @param str 包含各种参数的数组(从用例数据文件里读取出的一行数据)
     * <p/>str[0]:当前数据行状态(eg:准备/测试点/恢复)
     * <p/>str[1]:动作关键字
     * <p/>str[2]:要导入的excel文件名(包含后缀名)
     * <p/>str[3]:父单位名称(eg:单位根目录)
     */
    public Map<String, Object> execute(Map<String, Object> context, String[] str) {
        String filePath = getParamsPath(BaseActionByPost.class.getResource("").getPath()) + str[2]; //待上传的文件路径
        int respanceCode = 0; //返回的状态码
        try {
            init(context); //初始化client和sessionID

            if (Constants.AllDEPARTMENT.equals(str[3].trim())) { //父单位选择 全部单位
                Part[] parts = {new FilePart("fileData", getFile(filePath)),
                                new StringPart("parentId", "0")}; //父单位ID
                respanceCode = upload(POSTURL, parts); //提交请求并返回状态码(这里的upload函数就是在父类BaseActionBypost里面写好的,下面的doPost也是一样的)
               
            } else {
                String sql = "select id from department where name='" + str[3] + "'"; //sql语句
                GetIdByQuerySQL query = new GetIdByQuerySQL(); //查询出对应父单位在mysql中的id(这里是我自己方便得到ID,去直接查询Mysql
                Part[] parts = {new FilePart("fileData", getFile(filePath)),
                                new StringPart("parentId", query.execute(sql, "id"))}; //父单位ID
                respanceCode = upload(POSTURL, parts); //提交请求并返回状态码
            }
        } catch (Exception e) {
            throw new AssertionError();
        }

        if (Constants.RESPANCECODE == respanceCode) { //判断返回的状态码是否正确
            logger.info(str[0] + "," + str[1] + "," + str[2] + "," + str[3] + ",【success】");
        } else {
        }
        
        context.put(Constants.CLIENT, client);
        return context;
    }

}
作者: ryugun    时间: 2012-3-2 15:56
步骤2:上面是通过Post请求方式上传东西,这个是提交一个普通请求的代码,其实和login差不多,还是做个列子吧

package com.huaweisymantec.iget.omm.action.bypost.system.tooltype;

import java.util.Map;

import com.XXX.Constants;
import com.XXX.action.bypost.common.BaseActionByPost;

/**
* 系统管理-工具类型:通过Post请求的方式添加工具类型
*  
* <p/>excel对应关键字:createToolTypeByPost
*
*/
public class CreateToolTypeByPost extends BaseActionByPost {

    /**
     * 系统管理-工具类型:通过Post请求的方式添加工具类型
     * @return context aw参数传递上下文对象
     * @param context aw参数传递上下文对象
     * @param str 包含各种参数的数组(从用例数据文件里读取出的一行数据)
     * <p/>str[0]:当前数据行状态(eg:准备/测试点/恢复)
     * <p/>str[1]:动作关键字
     * <p/>str[2]:工具类型名称
     */
    public Map<String, Object> execute(Map<String, Object> context, String[] str) {
        StringBuffer encodURL = new StringBuffer(); //转码后的URL
        encodURL.append(Constants.BASEURL);
        encodURL.append("/servercfg/addToolType.do?");
        
        encodURL.append("name="); //工具类型名称
        encodURL.append(encod(str[2]));
        
        int respanceCode = 0; //返回的状态码
        try {
            init(context); //初始化client和sessionID
            respanceCode = doPost(encodURL.toString()); //提交请求并返回状态码
        } catch (Exception e) {
            throw new AssertionError();
        }

        if (Constants.RESPANCECODE == respanceCode) { //判断返回的状态码是否正确
            logger.info(str[0] + "," + str[1] + "," + str[2] + ",【success】");
        } else {
            logger.warn(str[0] + "," + str[1] + "," + str[2] + ",【未知状态码:" + respanceCode + "】");
        }
        
        context.put(Constants.CLIENT, client);
        return context;
    }

}
作者: ryugun    时间: 2012-3-2 16:03
好了~上面已经通过请求的方式完成一些selenium难以做到的事了,我主要用来为selenium创造测试环境,以及清除测试环境,不仅快速,而且稳定(selenium的方式还是不太稳定呀~!)

接下来,我还要贴上我写的基于selenium的关键字驱动的框架的核心代码(解析器)
作者: ryugun    时间: 2012-3-2 16:07
首先要说明,这个不是教程,是我自己为了工作方便,才贴在这里的,可能大家看不懂,没关系,我自己能知道就行了~!
当然如果能对大家有帮助,那么就功德无量了~!

基于关键字驱动的框架:
我是这个思路:
1、excel里存放测试数据与测试逻辑(关键字的组合构成测试逻辑)
2、用java代码写一些基于selenium的没有任何逻辑的步骤(一个步骤对应一个关键字)部品,类似于 点击、输入呀什么的
3、利用关键字解析器读取excel,并解析里面的数据(关键字),动态调用步骤2的部品,完成测试
作者: wujinjie0346    时间: 2012-3-2 16:33
xiexie
作者: bingorz    时间: 2012-3-2 16:46
这帖子成了书签了~
不错哦~
作者: ryugun    时间: 2012-3-2 16:53
好~解析器的代码如下:

package comXXX.resolver;


import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;

import java.lang.reflect.InvocationTargetException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.httpclient.HttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.XXX.Constants;
import com.thoughtworks.selenium.Selenium;

import jxl.Cell;
import jxl.Sheet;
import jxl.Workbook;


/**
* 关键字解析器
* <p />解析出用例文件里每一行的动作步骤/控件路径关键字,调用部品执行测试步骤
*
*/
public class KeyWordsResolver {
   
    /**
     * Log
     */
    private static Logger logger = LoggerFactory.getLogger(KeyWordsResolver.class);
   
    /**
     * selenium对象
     */
    private Selenium selenium = null;
   
    /**
     * client对象
     */
    private HttpClient client = null;
   
    /**
     * aw参数传递上下文对象
     */
    private Map<String, Object> context = new HashMap<String, Object>();
   
    /**
     * 数据地图(控件路径、浏览器参数)
     */
    private Map<String, String> dataMap = new HashMap<String, String>();
   
    /**
     * 关键字地图
     */
    private Map<String, String> keyWordsMap = new HashMap<String, String>();
   
   
    /**
     * 关键字解析器-构造函数
     * @param locatorMap 控件路径容器地图
     * @param keyWordsMap 关键字地图
     */
    public KeyWordsResolver(Map<String, String> dataMap, Map<String, String> keyWordsMap) {
        this.dataMap = dataMap;
        this.keyWordsMap = keyWordsMap;
        context.put("selenium", selenium);
        context.put("client", client);
    }
作者: ryugun    时间: 2012-3-2 16:54
接着上面的 解析器,该类还没贴完呢~


    /**
     * 读取用例文件并把每一行的数据进行解析
     * @param caseFile 用例文件路径
     * @param caseId 用例编号(一个sheet表示一个用例,sheet名称)
     * @param flag 指定读取带有相应标志位的数据行(prepare/test/renew/all),例如:填写prepare,则只读取标志位为prepare的数据行
     * @return Selenium
     */
    public Selenium readFile(String caseFile, String caseId, String flag) {
        logger.info("********************TestCase: " + caseId + "," + flag + " Start********************");
        //取得文件的path
        String path = formatPath(KeyWordsResolver.class.getResource("").getPath(), caseFile);

        InputStream is = null; //打开一个文件读取流
        Workbook rwb = null; //打开一个xls的workbook
        
        try {
            is = new FileInputStream(path);
            rwb = Workbook.getWorkbook(is);
            
            Sheet rs = rwb.getSheet(caseId); //获取一个工作sheet
            int rowsLength = rs.getRows(); //获取当前sheet的行数
            
            //循环读取出每一行的内容,根据关键字执行测试步骤
            for (int i = Constants.CELL_NUMBER_1; i < rowsLength; i++) { //从第2行开始取数据,第一行是注释
                Cell[] cell = rs.getRow(i); //获取一行的数据,放进一个数组里
               
                if("".equals(cell[Constants.CELL_NUMBER_2].getContents().toString())) {
                    break; //如果未填写关键字,则跳出循环
                }
               
                if(!"all".endsWith(cell[Constants.CELL_NUMBER_1].getContents().toString())) { //当状态位不是all时
                    if(!flag.equals(cell[Constants.CELL_NUMBER_1].getContents().toString())) {
                        continue; //如果当前数据行的状态位不是期待的状态位,则跳过此循环
                    }
                }

                actionResolver(cell); //解析一行的关键字,调用对应部品执行测试步骤
            }
        } catch (FileNotFoundException e) {
            dealWithException("文件不存在:" + path, e);
        }catch (AssertionError e) {
            if(null != selenium) {
                selenium.stop(); //关闭浏览器
            }
            throw new AssertionError(); //抛出新异常,退出TestNG
        } catch (Exception e) {
            dealWithException("未知异常,用例失败:" + path, e);
        }  finally {
            rwb.close();
            try {
                is.close();
            } catch (IOException e) {
                logger.warn("***关闭用例文件流出错:" + path + "***");
                logger.warn(e.getMessage());
            }
            logger.info("********************TestCase: " + caseId + "," + flag  + " End********************");
        }
        
        return selenium;
    }
   
    /**
     * 处理异常,并关闭selenium
     * @param loggerMessage 要打入log的自定义异常信息
     * @param e 异常
     */
    private void dealWithException(String loggerMessage, Exception e) {
        logger.error(loggerMessage, e);
        if(null != selenium) {
            selenium.stop();//关闭浏览器
        }
        throw new AssertionError(); //抛出断言异常,退出TestNG
    }
作者: ryugun    时间: 2012-3-2 16:56
继续接着上面的解析器,还是没有贴完呀~~~


    /**
     * 步骤关键字解析器
     * <p/>解析用例文件的步骤关键字,调用部品执行测试步骤
     * @param cell excel里的一行数据
     */
    @SuppressWarnings("unchecked")
    private void actionResolver(Cell[] cell) {
        // 根据excel表格每一行第一个单元格的内容,执行对应的步骤
        try {
            Class<?> action = Class.forName(keyWordsMap.get(cell[Constants.CELL_NUMBER_2].getContents().toString()));
            this.setContext((Map<String, Object>) action.getMethod
                    ("execute", new Class[]{Map.class, String[].class}).
                        invoke(action.newInstance(), new Object[]{context, changeCellToArray(cell)}));
        } catch (InvocationTargetException e) {
            logger.error("Selenium运行出错,请检查控件路径是否正确!关键字:"
                            + cell[Constants.CELL_NUMBER_2].getContents().toString().trim(), e);
            throw new AssertionError(); //抛出断言异常,退出TestNG
        } catch (ClassNotFoundException e) {
            dealWithException("ClassNotFoundException:" + keyWordsMap.get(cell[Constants.CELL_NUMBER_2].getContents().toString()), e);
        } catch (Exception e) {
            logger.error("解析关键字出错:" + cell[Constants.CELL_NUMBER_2].getContents().toString().trim(), e);
            throw new AssertionError(); //抛出断言异常,退出TestNG
        }

    }
   
    /**
     * 将Excel一行的数据转化为数组格式,并对控件路径关键字进行解析
     * @param cell excel一行的数据
     * @return 转化后的数组
     */
    private String[] changeCellToArray(Cell[] cell) {
        String[] str = new String[cell.length - 1]; //创建一个数组接收一行excel的数据,
                                    //由于第一列为注释,不需要写入数组,因此数组长度 = cell长度 - 1
        
        for (int i = Constants.CELL_NUMBER_1; i < cell.length; i++) { //第一列为注释,因此跳过,直接从第2列开始读数据
            if ("".equals(cell.getContents().toString())) {
                continue; //当一个表格没有内容时,跳过本次解析
            }
            
            //标识为 XML: 的数据表示在xml文件里定义的locator
            if (cell.getContents().toString().startsWith("XML:")) {
                str[i - 1] = locatorResolver(dataMap, cell.getContents().toString()); //将控件路径关键字解析后放入数组
            } else {
                if ("null".equals(cell.getContents().toString())) {
                    str[i - 1] = ""; //输入项为null表示空白输入
                } else {
                    str[i - 1] = cell.getContents().toString();
                }
            } //else
        } //for
        
        return str;
    }
作者: ryugun    时间: 2012-3-2 16:57
继续解析器,最后一点了~~



    /**
     * 控件路径解析器
     * @param map 存放对应控件的地图
     * @param locatorKeyWord 需要解析的控件路径关键字
     * <p/>          格式:XML:login_submit(login_submit为控件路径key, XML:为标识)
     * @return 控件路径
     */
    private String locatorResolver(Map<String, String> map, String locatorKeyWord) {
        String str[] = locatorKeyWord.split(":"); //将控件路径关键字按格式拆分
        return map.get(str[Constants.CELL_NUMBER_1]);
    }

    /**
     * 对参数文件的路径进行格式化
     * @param path 当前java文件的路径
     * @param fileName 文件名称
     * @return path 格式后的字符串
     */
    private String formatPath(String path, String fileName) {
        path = path.replaceAll("bin/com/huaweisymantec/iget/omm/resolver/", "");
        path = path.replaceAll("\\/", "\\\\");
        path = path.replaceFirst("\\\\", "").trim();
        path = path + "params\\casedata\\" +fileName;
        try {
            path = URLDecoder.decode(path, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            logger.warn(e.getMessage(), e);
        }
        
        return path;
    }

    /**
     * 取得Selenium对象
     * @return
     */
    public Selenium getSelenium() {
        return selenium;
    }

    /**
     * 设置Selenium对象
     * @param selenium
     */
    public void setSelenium(Selenium selenium) {
        this.selenium = selenium;
    }

    /**
     * 取得关键字地图
     * @return
     */
    public Map<String, String> getKeyWordsMap() {
        return keyWordsMap;
    }

    /**
     * 设置关键字地图
     * @param keyWordsMap
     */
    public void setKeyWordsMap(Map<String, String> keyWordsMap) {
        this.keyWordsMap = keyWordsMap;
    }

   
    public Map<String, String> getDataMap() {
        return dataMap;
    }

   
    public void setDataMap(Map<String, String> dataMap) {
        this.dataMap = dataMap;
    }

    /**
     * 取得aw参数传递上下文对象
     * @return context
     */
    public Map<String, Object> getContext() {
        return context;
    }

    /**
     * 设置aw参数传递上下文对象
     * @param context
     */
    public void setContext(Map<String, Object> context) {
        this.context = context;
        setSelenium((Selenium) context.get("selenium"));
        setClient((HttpClient) context.get("client"));
    }

   
    public HttpClient getClient() {
        return client;
    }

   
    public void setClient(HttpClient client) {
        this.client = client;
    }

}
作者: ryugun    时间: 2012-3-2 17:02
好了 解析器,已经贴完了,接下来 贴一个 关键字所对应的一个步骤部品的代码

其实就是简单的封装了下selenium的东西,因为我的业务只需要这样就能满足了,如果有复杂的步骤自己封装就是了
作者: ryugun    时间: 2012-3-2 17:15
这个代码是 一个步骤代码(关键字所对应的一个步骤):往上传文件输入框里输入文件路径(手动操作的话,点击上传按钮,就弹出windows的文件选择对话框,由于selenium不支持,所以就直接往输入框里输入对应的文件路径就可以了)

package com.XXX.action.common;

import java.util.Map;

import com.XXX.Constants;
import com.XXX.action.bypost.common.BaseActionByPost;
import com.thoughtworks.selenium.Selenium;
import com.thoughtworks.selenium.SeleniumException;

/**
* 往上传输入框里输入待上传的文件路径
*  
* <p/>部品层次:底层
* <p/>excel对应关键字:uploadInput
* @author lKF49150
*/
public class UploadInput implements BaseAction {
   
    /**
     * 往上传输入框里输入待上传的文件路径
     * @param context aw参数传递上下文对象
     * @param str 包含各种参数的数组(从用例数据文件里读取出的一行数据)
     * @return context aw参数传递上下文对象
     * <p/>str[0]:当前数据行状态(eg:准备/测试点/恢复)
     * <p/>str[1]:动作关键字
     * <p/>str[2]:locator 控件路径
     * <p/>str[3]:expected 要输入的文件路径(空白输入,请输入null)
     */
    public Map<String, Object> execute(Map<String, Object> context, String[] str) {
        Selenium selenium = (Selenium) context.get(Constants.SELENIUM);
        
        String filepath = //上传文件的路径
            BaseActionByPost.getParamsPath(BaseActionByPost.class.getResource("").getPath()) + str[3];
        
        try {
            selenium.type(str[2], filepath);
        } catch (SeleniumException e) {
            logger.info(str[0] + "," + str[1] + "," + str[2] + "," + str[3] + ",【fail:】", e);
            throw new AssertionError();
        }
        logger.info(str[0] + "," + str[1] + "," + str[2] + "," + str[3] + ",【success】");
        context.put(Constants.SELENIUM, selenium);
        return context;
    }
   
}
作者: ryugun    时间: 2012-3-2 17:17
接下来再看看测试数据保存文件是什么样子的呢?

既excel里该怎么写呢?(excel是保存测试数据和测试逻辑的文件)
作者: ryugun    时间: 2012-3-2 17:28
本帖最后由 ryugun 于 2012-3-2 17:29 编辑

数据保存文件的写法(不能上传文件,所以只能手写了,不然我贴这么多代码干嘛,直接贴java文件就可以了):
一个xls文件代表一个测试套(里面包含了同板块相关的所有用例),一个sheet代表一个用例,一行数据代表一个步骤,一列数据代表一个用例的测试逻辑
例如:登陆用例

文件名:case-login.xls(名字随便,表示该excel里全部放的都是登陆板块的用例,但在后面测试用例java文件里要对应上)
sheet名:TestCase-Login-001(可以随便命名,但在后面测试用例java文件里要对应上)

第一行,第一列(注释)                          标志位(准备环境/测试验证点/回复环境)        关键字                      参数1                                            参数2              参数n
准备环境:用post方式登陆                   prepare                                                            loginByPost             admin                                          12345678
上传一个文件作为测试数据                   prepare                                                            upLoadByPost         待上传文件.xls
测试验证点:selenium登陆                  test                                                                   openURL                  http://XXX.XXXX
登陆                                                      test                                                                   login                         //input[@id='username']           tester           密码xpath     123456   提交按钮xpath
…………………………等等
回复环境:删除文件byPost                  renew                                                               delFileByPost          待上传文件.xls
作者: ryugun    时间: 2012-3-2 17:33
接下来,就是测试用例java文件了

其实,这个文件不需要就可以直接运行了,我为什么要在这里加这个文件呢?主要是业务需要,不解释了,核心的地方都在上面的代码里。
作者: ryugun    时间: 2012-3-2 17:41
回复 59# ryugun

对于这里的标志位可能大家不太理解,这个也是和业务相关的,我才加在这里,可能大家都不需要,还是说说我的看法,为什么会加在这里,我也是思考了很久才加上的,在最开始设计的时候是没有这个状态位的。

由于我们项目目前的情况,一个用例其实应该分为3个部分(手工操作也是如此):准备测试环境-->测试验证点--->回复测试环境以免影响其他用例

其实我们真正关心的只是测试验证点,然后在跑自动化的时候,由于selenium存在不稳定性,往往在准备环境或回复环境的时候失败,验证点正常。这样就会给我们返回一个错误信息:我的验证点是正确的,用例通过的,但其他地方错了,导致自动化跑失败~!

因此,为了解决这个问题,在运行用例的时候,我就要设计一下,让准备环境和回复环境不能影响我的测试结果。

我是这么做的:利用testNG的 beforClass  和 AfterClass,以及强依赖关系depends

当准备环境失败,我就不会去执行测试验证点(必然失败),testNG会给我标识为skip
当准备环境成功,才会执行验证点,验证点通过既pass,验证点失败既fail
无论验证点通过与否,都会去回复环境;无论回复环境正确与否都不会影响测试结果
作者: ryugun    时间: 2012-3-2 17:43
回复 61# ryugun

目前是这么设计的,因此为了达到这个目的,才会增加一个java的用例文件,主要是依赖testNG的功能,以及产出报告。

当然这个java用例其实很简单,不能复杂了,不然开发成本就太高了。
作者: ryugun    时间: 2012-3-2 17:48
下面贴出用例java 文件:

package com.XXXX.testcase.login;

import org.testng.annotations.Test;

import com.CCCC.testcase.BaseTestCase;


/**
* (只检查了是否有完整用户名)
* 用例名称:用户登录(成功)
* <p />
*/
public class Test_Login_001 extends BaseTestCase {

    @Override
    public void prepare() {
        selenium = resolver.readFile("case-login.xls","Test-Login-001","prepare");
    }

    @Test(description="Test_Login_001 用户登录(成功)", groups={"用户登录"})
    @Override
    public void test() {
        selenium = resolver.readFile("case-login.xls","Test-Login-001","test");
    }
   
    @Override
    public void renew() {                   //用例文件           用例名称                状态位
        selenium = resolver.readFile("case-login.xls","Test-Login-001","renew");
        if(null != selenium) {
             selenium.stop();//关闭浏览器
        }
    }


}
作者: ryugun    时间: 2012-3-2 17:52
本帖最后由 ryugun 于 2012-3-2 17:55 编辑

上面的红字提及到了 测试用例有父类BaseTestCase
里面主要是初始化关键字等准备工作,以及prepare  test  renew 标志位函数的定义

package com.XXXX.testcase;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;


import com.XXXX.resolver.KeyWordsResolver;
import com.thoughtworks.selenium.Selenium;


/**
* 测试用例基类
*
* <p />初始化各种数据
*
*/
public abstract class BaseTestCase {
    /**
     * Log
     */
    public static Logger logger = LoggerFactory.getLogger(BaseTestCase.class);
   
    /**
     * 状态标识位:初始化 状态
     */
    public static final String FLAG_PREPARE = "prepare";
   
    /**
     * 状态标识位:测试点 状态
     */
    public static final String FLAG_TEST = "test";
   
    /**
     * 状态标识位:恢复环境 状态
     */
    public static final String FLAG_RENEW = "renew";
   
    /**
     * 状态标识位:全部 状态
     */
    public static final String FLAG_ALL = "all";
   
    /**
     * 数据地图(控件路径、浏览器参数)
     */
    protected Map<String, String> dataMap = new HashMap<String, String>();
   
    /**
     * 关键字地图
     */
    protected Map<String, String> keyWordsMap = new HashMap<String, String>();
   
    /**
     * selenium对象
     */
    protected Selenium selenium = null;
   
    /**
     * 关键字解析器
     */
    protected KeyWordsResolver resolver = null;
作者: ryugun    时间: 2012-3-2 17:53
继续BaseTestCase

   
    /**
     * 准备数据
     * <p/>当用selenium来构造环境时,对于浏览器,准备环境与验证点之间存在2种关系(若非selenium来构造环境,忽略此项)
     * <p/>     1、在准备环境的基础上不关闭浏览器直接进行验证点(验证点直接续用准备环境的浏览器)
     * <p/>         此时,prepare()中无需关闭浏览器,在test()中获取selenium,关闭浏览器
     * <p/>     2、准备环境与验证点相互独立,不存在续用关系,需要在prepare()与test()中分别关闭浏览器
     * <p/>关闭浏览器:selenium = resolver.readFile("","","");selenium.stop();
     */
    public abstract void prepare();
   
    /**
     * 测试验证点
     * <p />注意:在子类中,必须为此方法加上TestNG的测试标签Test
     * <p />建议:为避免运行测试套时启动过多的浏览器而占用系统资源,请务必在此方法的最后关闭浏览器
     * <p />关闭浏览器:selenium = resolver.readFile("","","");selenium.stop();
     */
    public abstract void test();
   
    /**
     * 恢复环境
     * <p />恢复环境的步骤应该与准备环境、测试验证点没有耦合,它是单独的完整的:登陆-恢复环境
     * <p />建议:当用selenium来清理环境时,为避免运行测试套时启动过多的浏览器而占用系统资源,请务必在此方法的最后关

闭浏览器
     * <p />关闭浏览器:selenium = resolver.readFile("","","");selenium.stop();
     */
    public abstract void renew();
作者: ryugun    时间: 2012-3-2 17:54
本帖最后由 ryugun 于 2012-9-21 21:23 编辑

接着BaseTestCase


   
    /**
     * 初始化地图
     * <p/>从xml里读取出对应的关键字,放入map里
     */
    @SuppressWarnings("unchecked")
    private void initMap(String xmlPath, Map<String, String> map) {
        try {
            SAXReader reader = new SAXReader();
            Document confXml = reader.read(BaseTestCase.class.getClassLoader().getResourceAsStream(
                xmlPath));
            // 获得根节点
            Element rootElement = confXml.getRootElement();
            Element paret;
            // 遍历一级节点
            for (Iterator<Element> parets = rootElement.elementIterator(); parets.hasNext();) {
                paret = parets.next();
                for (Iterator<Element> childrens = paret.elementIterator(); childrens.hasNext();) {
                    Element children = childrens.next();
                    map.put(children.attributeValue("name"), children.attributeValue("code"));
                }
            }
        } catch (DocumentException e) {
            logger.error("初始化【" + xmlPath + "】出错:", e);
            Assert.assertFalse(true); //强制断言
        }
    }
   
    /**
     * 类前方法(初始化数据以及准备测试环境)
     */
    @BeforeClass
    public void beforeClass(){
        initMap(); //初始化地图
        resolver = new KeyWordsResolver(dataMap, keyWordsMap); //初始化关键字解析器
        prepare(); //准备数据
    }
   
    /**
     * 类后方法(清除环境)
     */
    @AfterClass(alwaysRun=true)
    public void afterClass() {
        renew(); //恢复环境
    }
   
    /**
     * 读取xml文件,初始化地图
     */
    private void initMap() {
        File file = new File(formatPath(BaseTestCase.class.getResource("").getPath()));
        if (!file.exists()) {
            logger.error("找不到xml初始化文件,请检查文件路径!");
            return;
        }
        String[] names = file.list(); //获取当前文件夹内所有的文件名称
      
        for (int i = 0; i < names.length; i++) {
            if (names.startsWith("locator") || names.startsWith("init")) {
                initMap("data/" + names, dataMap);
                continue;
            }
           
            if (names.startsWith("keywords")) {
                initMap("data/" + names, keyWordsMap);
            }
        }
    }
   
    /**
     * 格式化参数文件的路径
     * @param path 当前java文件的路径
     * @return path 格式后的字符串
     */
    private String formatPath(String path) {
        path = path.replaceAll("com/huaweisymantec/iget/omm/testcase/", "");
        path = path.replaceAll("\\/", "\\\\");
        path = path.replaceFirst("\\\\", "").trim();
        path = path + "data\\";
        try {
            path = URLDecoder.decode(path, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
      
        return path;
    }

}


------------------------------------------
下面贴上一个简单的工程。。里面jar包都不在,需要自己去下
工程里就是利用这个思想做的一个例子。
可以通过ant 运行build.xml来看效果,当然得需要把对应的jar包下载下来放进工程的lib里(selenium一系列的包,testng的包,读取excel的包jxl.jar,读取xml的dom4j.jar)
这个工程还存在一些问题,代码的设计还有些BUG,没详细解决,但具体思路大致就是这样。
[attach]81389[/attach][attach]81502[/attach]
作者: ryugun    时间: 2012-3-24 16:16
ET测试
指南测试法(适合资料测试)通过阅读用户手册并严格按手册建议执行操作。
卖点测试法针对卖钱的特性(特别吸引客户的)进行测试。
地标测试法把重要特性作为地标,确定他们可能的先后顺序,然后执行应用程序从一个地标跳跃到另一个地标,探索应用程序。
极限测试法了解软件能承受的极限场景和不能承受的极限场景。考虑如何让软件发挥到最大程度?哪个特性会使得软件运行到其设计极限?哪些输入和数据会耗费软件最多的运算能力?等等。
遍历测试法以最快的速度遍历被测目标包含的每个对象,不追求细节,只求遍历,只检查那些明显的东西。
深夜测试法测试软件的使用高峰期,也要测试软件的空闲期.  如:日志备份等任务。发现软件被业务使用空闲时的问题。
清晨测试法测试软件的启动过程及其脚本。
恶邻测试法在产品缺陷多的地方以及邻近区域进行测试。缺陷通常扎堆出现,因此这里强调对历史上缺陷密度高的特性进行反复测试。从“触发条件”、“问题根因”、“检查点”、“外部
    环境”、“所做的操作”、“有限的资源”等探索视角进行探索测试。
博物馆测试法把很久没有更新的老功能与被测对象进行交互。
配角测试法只要场景提供了选择,就不选择所建议的而是选择和它相邻的或语义接近的;把不主要的功能与主要功能进行交互。
深巷测试法测试产品特性使用情况列表中排在最下面的几项特性,也就是最不可能被用到或最不吸引用户的特性。
通宵测试法连续不断的使用某种特性或将文件一致保持在打开状态,真正让运行时间足够长。
收藏家测试法创建一个可以产生最多输出数量的新场景。
长路径测试法选择离程序开始点尽可能远的特性开始,在尽可能多的程序中穿行,选择长路径而不是短的。

作者: ryugun    时间: 2012-3-24 16:17
ET测试(继续)
超模测试法(适合GUI测试)只关心哪些表面的东西,关注界面可用性。
测一送一测试法考虑多个应用程序操作同一个数据或对象的场景测试,或者是测试并发情况下有没有冲突或错误。
取消测试法执行耗时的操作,然后过程中取消操作,检查取消后系统还能否继续工作;
或取消操作后再做相同的操作能否操作成果。
懒汉测试法测试默认值和默认配置。
强迫测试法反复执行同样的操作或不按特定顺序做事。
反叛测试法输入最不可能的数据或已知恶意输入或错误的顺序。
破坏测试法
输入恶意数据或状态破坏软件。在运行场景测试时,在资源调用处进行破坏活动。如业务执行过程中拔插网线、掉电、复位设备、禁用必需资源等去掉本次操作所依赖的的条件。
基于场景的ET-插入操作在执行规定场景测试操作过程中插入操作:增加比规定更多的数据;使用附加输入;访问新的界面。
基于场景的ET-删除操作每次只删除一个步骤来找到最短的可执行步骤。
基于场景的ET-替换操作用可以替换的步骤代替原有场景的步骤,先删除步骤再插入步骤。例如:用键盘快捷键代替鼠标操作。
基于场景的ET-重复操作重复单独的步骤或重复一组步骤来改变顺序,创建额外的变化。
基于场景的ET-数据替换用更大的数据和更小的数据来代替先前测试所使用的数据。
基于场景的ET-环境替换硬件替换;容器替换(如:浏览器和.net平台);版本替换(更换老版本的浏览器);修改本地设置。
混票测试法在测试某个过程时,中途插入其他场景,或跳转到另一场景进行操作,达到混合目的。

作者: ryugun    时间: 2012-3-24 16:17
ET测试(继续)
出租车测试法通过不同路径(测试操作步骤路径/测试输入构造方式)达到同一个功能目的。
出租车禁区测试法通过不同路径(测试操作步骤路径/测试输入构造方式)达到同一个限制目的。
快递测试法分析并跟踪数据在被测系统内的处理过程,参与数据生命周期的每个阶段,测试和输入数据有接触的那些特性可能对数据做出的操作;如同一参数在不同的地方被引用重新计算得到另一个参数,在该参数达到边界值时,引用它的其他参数的计算是否正确;同一个变量在不同的地方显示,变量变化后,不同的显示地方有没有都同步刷新。
停车场测试法地标测试法和超模测试法结合体,第一轮测试不用看太深,重点在被测代码如何显示;第二轮再显示有错的地方运用其他ET方法,挖掘更深的bug。
上一版本测试法相对于上一版本,是否有配置丢失,是否有规格数变更导致的用户数据丢失,是否有特性丢失。
肥皂剧测试法其要点与电视中的肥皂剧如出一辙:源于真实生活、浓缩、夸张,类似于我们的故障注入测试,但是将所有的故障一次性注入进去,看看会发生什么。

作者: ryugun    时间: 2012-3-24 16:25
软件生命周期中的测试阶段不同测试阶段中的测试方法
从coding--->交付产品
测试类型:单元测试(UT)---集成测试(IT)----系统测试(ST)---验收测试
作者: ryugun    时间: 2012-3-24 16:27
本帖最后由 ryugun 于 2012-3-24 16:29 编辑

单元测试
[size=75%]u单元测试,又称模块测试,是针对软件设计的最小单元——程序模块,进行正确性检验的测试工作。
[size=75%]u单元测试主要关注每个具体单元模块内部的逻辑结构和功能是否正确,侧重于发现程序设计或实现的逻辑错误,基本属于白盒测试范畴。
[size=75%]u单元具有一些基本属性,如明确的功能、明确与其他部分的接口定义等,可清晰地与同一程序的其他单元划分开来。如:函数、模块、类或组件为基本测试单元。


由于单元本身不能独立运行,所以必须为每个单元测试开发驱动模块(Driver)和桩模块(Stub)以构成一个可运行的软件系统进行测试。

驱动模块
[size=75%]l
接收测试数据
[size=75%]l
把数据传给(被测试)模块
[size=75%]l
显示或比较相关测试结果

桩模块:替代隶属于本模块(被调用)的模块,使被测对象得以运行

单元测试过程分为计划,设计和实现,执行,评估等几个步骤。
单元测试的测试用例主要是针对功能来设计的,有时也考虑非功能属性方面的要求(如资源泄漏,性能要求,
结构测试等)。
单元测试的用例的分析和设计依据是详细设计说明书,根据详细设计说明书中的描述进行测试用例的设计,主要使用白盒测试方法。
作者: ryugun    时间: 2012-3-24 16:32
本帖最后由 ryugun 于 2012-3-24 16:33 编辑

集成测试

集成测试:把若干个经过单元测试的组件/模块/单元组装到一起的测试,主要目的是测试模块之间的接口
,以及被对象与系统其他部分的相互作用。
u集成测试依据软件概要设计说明书,通过对模块功能、接口设计进行分析,覆盖所有的功能项目,重点测试接口和边界。


根据被测对象的规模,集成测试分为:
l
模块内集成测试
l
子系统内集成测试
l
子系统间集成测试
根据测试过程中组合模块的方式,集成测试可分为:
l
非增式集成:又称一次性集成,对完成单元测试的所有模块在一起进行测试
l
增式集成:又称递增式集成,即逐次将未测试的模块与已测试的模块组合成较大的系统,边连接边测试

根据集成的过程还可分为

自顶向下集成——沿控制层次自顶向下进行集成测试

自底向上集成——从程序模块结构的最底层模块开始集成测试
l
衍变式集成:增式和非增式两者结合

集成测试主要依据是软件概要设计说明书,一般不过多的考虑集成测试对象(子模块)内部的实现,通过对模块功能、接口设计进行分析,覆盖所有的功能项目,重点的接口、重点的边界进行重点测试。

为什么进行集成测试?
一个模块可能对另一个模块产生不利的影响
将子功能合成时不一定产生所期望的主功能
独立可接受的误差在组装后可能会超过可接受的误差限度
可能会发现单元测试中未发现的接口方面的错误
在单元测试中无法发现时序问题(实时系统)
在单元测试中无法发现资源竞争问题
目的:在模块组装后查找模块间接口的错误


自顶向下的集成方法是将模块按系统的程序结构,沿控制层次自顶向下集成。首先以主模块为所测模块兼驱动模块,所有直属于该模块的下属模块全部用桩模块对主模块进行测试;然后采用深度优先或宽度优先的策略,用实际模块替换相应的桩模块,再用桩代替它们的直接下属模块,与已测模块集成为新的子系统进行测试;直到所有的模块都被集成到系统并完成所有测试用例。
自底向上的集成方法是从程序模块结构的最底层模块开始集成测试;然后用实际模块替代驱动模块,与它已测试的直属子模块集成为子系统。为新的子系统配置驱动模块,进行新的测试。直到集成到主模块完成所有测试用例。
作者: ryugun    时间: 2012-3-24 16:39
系统测试

系统测试:针对软件系统进行的整体测试,将软件系统在实际环境下运行,以验证系统是否正确实现了客户的需求
系统测试依据软件需求分析说明书,检验软件系统与需求规格说明符合的程度。通常采用黑盒测试方法。

系统测试包括功能性测试和非功能性测试,需要考虑不同的测试类型:
  功能性
  可靠性
  效率性
  可服务性

系统测试主要依据是软件需求分析说明书,检验软件系统与需求规格说明符合的程度,一般不考虑程序实现的内部逻辑,以检验输入输出信息为主,通常采用黑盒测试方法
作者: ryugun    时间: 2012-3-24 16:40
验收测试

验收测试:通常由使用系统的用户来进行,同时系统的其他利益相关者也可能参与其中。
验收测试的目的是通过对系统功能、系统的某部分或特定的系统非功能特征进行测试,来确定系统是否满足客户需求并能够进行商用。发现缺陷不是验收测试的主要目标。

验收测试有下面几种典型的类型:
  用户验收测试
  运营测试
  合同验收测试
  现场(Field)测试(alpha and beta 测试)
作者: ryugun    时间: 2012-3-24 16:47
本帖最后由 ryugun 于 2012-3-24 16:48 编辑

HLT和LLT的区别()

LLT与HLT主要区别
测试依据不同
    LLT测试依据是实现层面的需求
    HLT测试依据是客户可见的需求
测试手段不同
   LLT通常通过测试代码触发测试运行,运行环境通常为仿真环境
   HLT通常通过外界命令或是事件触发测试运行,运行环境通常为真实设备
主导人员
   LLT多为开发人员主导开展
    HLT多为测试人员主导开展
其他说明
网络现状中,部分项目开展的ST活动,虽然是开发人员执行,但是测试依据和测试手段都和SDV一样,不能称之为LLT
参考:业界对不同测试阶段的定义
作者: ryugun    时间: 2012-3-24 16:54
本帖最后由 ryugun 于 2012-3-24 16:58 编辑

HW IPD流程中LLTHLT:测试活动

------------------------------|<--------LLT-------------->|             |<------------HLT-------------|
                                              |     (low level test)              |     TR4  |           (high level test)          |
测试活动:coding/review--->UT---->IT---->ST--->BBIT--->|--->SDV----->SIT(Beta)---->SVT

  活动阶段
  
  

UT


  
  

IT


  
  

ST


  
  

BBIT


  
  活动定义
  
  Unit Test 单元测试
  
  Integration Test集成测试
  
  System Test系统测试
  
  Building Block Integration and Test构建模块集成和测试
  
  测试对象
  
  通常是函数和语句,由开发个人行动,投入产出低
  
  模块间接口,项目组级别,基本被裁剪
  
  基本上是模块测试
  项目组级别测试活动
  
  系统的功能块(多个模块组合)
  实际上BBIT基本等同SDV活动
  

作者: ryugun    时间: 2012-3-24 17:00
本帖最后由 ryugun 于 2012-3-24 17:02 编辑

Wiki流行观点:LLT

------------------------------|<-LLT---->|        |<------------HLT-------------|
                                             |                  |        |
测试活动:coding/review--->UT---->IT---->ST--->apha(Beta)---->UAT

  
  
  
UT

  
  
IT

  
  
ST

  
  
UAT

  
  活动名称
  
  Unit Test 单元测试
  
  Integration Test 集成测试
  
  System Test 系统测试
  
  User Acceptance Test

  用户验收测试
  
  活动说明
  
  UT是软件应用程序的一个最小可测试的部分。一个unit可以是一个程序(program),一个函数(function),一个过程(procedure)
  在面向对象的编程中,最小的unit是一个类的方法method)。
   
  IT测试目的是验证被测对象(组件,一群units)的功能、性能和可靠性方面的需求。
  IT是一个building block方法,将一个验证过组件添加到一个已经验证过基础组件上,以便进行下一步的组件集成活动。
  
  ST定义:对一个完整软件/硬件系统进行的测试,来确认是否满足特定需求。
  测试方法:采用黑盒测试方法,ST不需要了解系统内部设计和逻辑关系。
  区别:IT为了测试构成一个系统的组件之间不一致,或者软件和硬件之间不一致。ST是为了一方面为了发现组件间的缺陷,同时将多个组件作为一个整体来发现缺陷。ST包括负荷测试和压力测试,ST之后就是Alpha测试Beta测试。
  
  客户接收系统之前的测试活动,模仿正式使用情况,由客户或者最终用户进行测试,其目的是给客户信心。
   

作者: ryugun    时间: 2012-3-24 17:05
本帖最后由 ryugun 于 2012-3-24 17:06 编辑

TPI模型:LLT定义
------------------------------|<-LLT---->|        |<----HLT----|
                                             |                  |        |                     |
测试活动:coding/review--->UT---->IT---->ST------->AT

  
  
  

UT


  
  

IT


  
  

ST


  
  

AT


  
  活动名称
  
  Unit Test 单元测试
  
  Integration Test 集成测试
  
  System Test 系统测试
  
  Acceptance Test
验收测试

  
  活动定义
  
  demonstrate that the program meets the requirements set in the design specification.
  
  demonstrate that a logical series of programs meets the requirements set in the design specification.

  
  demonstrate that the developed system or subsystems meet the requirements set in the functional and quality specifications.
  
  demonstrate that the developed system meets the functional and quality requirements.
  
  实施
  
  开发者,实验室
  
  开发者,实验室
  
  开发者,(专门)实验室
  
  用户/经理,仿真/模型环境
  
  测试技术
  
  主要是白盒技术
  
  主要是白盒技术
  
  通常使用黑盒技术
  
  通常使用黑盒技术
  
  测试对象
  
  program
  
  a logical series of programs
  
  the developed system or subsystems
  
  the developed system
  

作者: ryugun    时间: 2012-3-24 17:14
测试退出评估/准则

1、过程目标达成情况

2、累计用例覆盖度
累计用例覆盖度实际上是一种测试进度的体现(我们缺省的认为用例的覆盖就是对规格的覆盖),用例覆盖度应该作为测试项目完成退出的一个重要指标(90%以上?)。

3、遗留缺陷分析
测试退出准则还有一个重要因素就是应该满足DI值。开发问题解决的力度不够,实际上会增加测试的成本,降低测试效率。
作者: ryugun    时间: 2012-3-24 17:23
测试计划:回答我们如何安排测试,达到什么测试目标的问题;

测试需求分析:回答我们要测试什么的问题;

测试设计:回答我们如何实现测试的问题;

测试策略:回答我们如何组织测试的问题;

测试执行:回答产品质量如何的问题。
作者: ryugun    时间: 2012-3-26 20:42
[attach]78179[/attach][attach]78180[/attach][attach]78181[/attach]
作者: ryugun    时间: 2012-4-11 14:58
本帖最后由 ryugun 于 2012-5-8 15:34 编辑

监控linux的资源使用情况
首先,suse上要安装sar包,保证sar命令可用
创建shell脚本:collector-data.sh
#!/bin/sh

# 设置测试结果源文件的路径
REPORT_HOME=/home
SAR_FILE=$REPORT_HOME/result

if [ -f "$SAR_FILE" ]
then
  echo "Collecting the test data..."
  
  {
    #collect cpu-usage info
    sar -u -f $SAR_FILE | awk '/all/{print 100-$8}' > $REPORT_HOME/cpu-usage.xls
    if [ $? -ne 0 ]
    then
      echo "Failed in collecting CPU info!"
    else
      echo "Succeed in collecting CPU info: ${?}"
    fi
  } &
  
  {
    #collect ram-usage
    sar -r -f $SAR_FILE | awk '!/kbmemfree/&&/^([0-9]|Average)/{print ($3-$5-$6)*100/($2+$3)}' > $REPORT_HOME/ram-usage.xls
    if [ $? -ne 0 ]
    then
      echo "Failed in collecting RAM info!"
    else
      echo "Succeed in collecting RAM info: ${?}"
    fi
  } &

  #collect disk info
  {
    awk 'BEGIN{OFS="\t";print "rd_MB/s","wr_MB/s","avgqu-sz","%util"}' > $REPORT_HOME/disk-info.xls
    sar -d -f $SAR_FILE | awk '$2=="dev8-16"||$2=="dev8-32"||$2=="dev8-48"||$2=="dev8-64"||$2=="dev8-80"||$2=="dev8-96"||$2=="dev8-112"||$2=="dev8-128"||$2=="dev8-144"' | awk 'BEGIN{OFS="\t"}{sum_rd+=$4;sum_wr+=$5;sum_avgqu+=$7;sum_util+=$10;if(NR%9==0){print sum_rd/(9*2*1024),sum_wr/(9*2*1024),sum_avgqu/9,sum_util/9;sum_rd=0;sum_wr=0;sum_avgqu=0;sum_util=0}}' >> $REPORT_HOME/disk-info.xls
    if [ $? -ne 0 ]
    then
      echo "Failed in collecting DISK info!"
    else
      echo "Succeed in collecting DISK info: ${?}"
    fi
  } &

  #collect net-usage
  {
    awk 'BEGIN{OFS="\t";print "rx_MB/s","tx_MB/s","%net"}' > $REPORT_HOME/net-usage.xls
    sar -n DEV -f $SAR_FILE | awk 'BEGIN{OFS="\t"}$2=="eth0"{print $5/1024,$6/1024,($5+$6)/(1024*1.25)}' >> $REPORT_HOME/net-usage.xls
    if [ $? -ne 0 ]
    then
      echo "Failed in collecting NET info!"
    else
      echo "Succeed in collecting NET info: ${?}"
    fi
  } &
  
  echo "Collecting data is done."

else
  echo "The monitor data file is not exist!"
fi
作者: ryugun    时间: 2012-4-11 15:04
接着上面:监控linux的资源使用情况

将collector-data.sh放到/home

通过SSH输入命令:
sar -u -d -r -n DEV -o /home/result 2 200
u:cpu   
d:磁盘
r:内存
n:流量
o:保存文件的路径
2:每2秒监控一次
200:监控200次

使用上面的命令后,将会在/home/下生成一个文件result

执行collector-data.sh: ./collector-data.sh 将会在/home下生成几个excel文件,即使资源使用情况,再通过excel的制图功能把数据转化为图标即可
作者: ryugun    时间: 2012-4-11 15:53
本帖最后由 ryugun 于 2012-4-11 16:10 编辑

http://www.51testing.com/?uid-60535-action-viewspace-itemid-18423
http://www.51testing.com/?uid-146821-action-viewspace-itemid-222680
测试策略
作者: ryugun    时间: 2012-4-17 17:54
测试计划与测试方案的区别
http://www.51testing.com/?uid-15 ... space-itemid-241236
作者: ryugun    时间: 2012-5-2 10:51
WATIR脚本编写指导文档


WATIR
英文缩写“Web Application Testing in Ruby”,是一个使用 Ruby 实现的开源Web 自动化测试框架,相对于那些庞大的商业工具来说,它很小巧,也很灵活,提供的功能也足够用。它对于ActiveX插件,JavaApplets, Flash, 或者其他的插件应用程序基本不支持。Ruby语言本身是一个面向对象语言,所以使用WATIR也是面向对象的思想在里面。WATIR支持对IE5.5,IE6.0,IE7.0,Firefox等浏览器的测试。

XX项目使用WATIR实现WEB自动化测试。WATIR这个自动化框架是一种编程语言,没有其他WEB自动化框架的录制功能。所以动作都需要使用语言进行控制。这样虽然在自动化中没有其他框架方便,但是纯脚本的控制可以使WEB自动化与XX项目的业务相结合,融合到原有的自动化框架中。实现了业务+WEB+CLI多种自动化模式。但是在WATIR语言编写中遇到了一些问题,例如ID根据页面信息自动分配,这样就导致了无法通过ID准确获取需要的内容。在页面复杂的情况下,HTML语言内大量的使用了<div>,CSS样式等较高级的技术。界面的操控难度上大大的提高了。本文重点介绍一下在编写过程中遇到的问题以及解决方式。

1
WATIR API使用方法

由于WATIR是RUBY的一个类库,所以也继承了RUBY面向对象的特性,所以当使用WATIR查找到某个页面时,WATIR获取到一个大对象,将这个页面内所有的组件根据HTML的结构组织成一个树形结构存放在对象中,根节点就是<html></html>,可以通过“.”的方式一层一层的向下检索,也可以使用id等组件的唯一标识直接找到这个组件,然后调用属于这个组件该有的操作。在WATIR中可以实现对按钮、链接、表格、文本框、复选按钮、单选按钮等操作。在WATIR里面找到一个组件有几个方式,可以使用组件的name、id、xpath、index等。上述几个为通用的,有些还有其他属性,例如对于超级链接还可以使用text,url等进行检索。WEB自动化测试主要依靠于WEB界面HTML语言显示出来的结果,对于name、id、text、url都是通过HTML语言对组件的定义得到的。对于xpath、index等需要根据当前页面的结构得到。Index表明这个组件在整个页面出现的序列号,例如在一个页面中出现三个文本框,如果我要检索到第三个文本框,这个文本框在没有id,name等标识情况下就可以使用index为3形式进行索引,从而找到这个文本框。。XPath 使用路径表达式来选取 XML 文档中的节点或者节点集,而每个组件的路径表达是唯一的。所以可以通过XPATH直接找到所要操作的组件。


作者: ryugun    时间: 2012-5-2 10:52
接着上面

l
text_field,对应HTML标签为<input></input >:

browser.text_field(:id, "wushan").exists ,判断文本框是否存在;

browser.text_field(:id, "wushan").value,得到文本框的值;

browser.text_field(:id, "wushan").set(“www”),向文本框输入内容。

l
button,对应HTML标签为<button></button >:

browser. button (:name, "box1").exists,判断按钮是否存在

browser .button(:id,"posixShare_input").click,点击一个按钮

l
checkbox,对应HTML标签<input type ='checkbox' ></input>:

browser. checkbox (:name, "box1").exists,判断checkbox是否存在

browser.checkbox(:name, "box4").value,返回checkbox的值

browser. checkbox(:name, "box4").set 选中checkbox

l
radio,对应HTML标签<input type =‘radio’></input>:

browser.radio(:name, "box1").exists,判断radio是否存在

browser.radio(:name, "box5", 1).set 设置radio

browser.radio(:name, "box3").isSet ,检查radio是否被设置

browser.radio(:name, "box1").getState 获取radio的状态,有两个返回值true,false

l
link,对应HTML标签为<a></a>;

browser.link(:text, "test1").click,点击一个链接

browser .link(:index, 1).href,查看一个链接的地址



l
table,对应HTML标签如下,<table>为表格定义开始,<tr></tr>定义一个行,<td></td>定义一个为列,一行里面可以包含多个列即<td></td>。

<table>


<tr>


<td>Row 1 Col1


<tr>

</table>


t=browser.table(:id, 'ext-comp-1004')
,找到一个表格


t.rows.each do|h|
,使用循环遍历表格的每一个行,每一个行有h表示,do是RUBY基本的语法,可以参考循环语句,而此时h变量是一个属组,如果想找到这一行的第一列可以h[1]来找到。如果不使用循环语句进行遍历行,也可以使用而为属组的形式直接找到相应的行和列,例如t[1][1],找到的是表格的第一行第一列。

l
div,对应HTML标签<div></div>,DIV通常与CSS使用,DIV标签可以把文档分割为独立的、不同的部分。它可以用作严格的组织工具,并且不使用任何格式与其关联。

browser .div(:id,"ext-gen21"),找到相应的DIV。

l
如果用 id 或 class 来标记<div>,那么该标签的作用会变得更加有效。在使用CSS来定义样式。然后DIV中CLASS指定包括的代码段是属于那个样式。iframe,对应HTML<iframe></iframe>:

HTML中frame将页面进行分割,每个分割后的框是一个完整的html。

browser.frame(:id,"usredit_Frame") 找到相应的frame

l
contains_text

browser. contains_text(“abc”),这个函数功能是在当前整个页面寻找是否存在abc这个字段。

l
fire_event,用这个函数可以触发事件以及SCRIPE脚本

browser.div(:id,1).fire_even(“onmousedown”),这个DIV有一个鼠标事件,但是DIV的属性与button以及click不一样,所以这个地方只能调用DIV提供的鼠标事件。

以上每个组件在页面内的使用方式以及标签属性都通过HTML页面来定义,有些属性可能不存在id,name等属性。


作者: ryugun    时间: 2012-5-2 10:54
1
XX项目WATIR使用举例1.1
查询组件

WEB界面操作,找到需要操作的组件是很重要的。找到组件之后才能根据组件的属性进行设置或者查询。由于WATIR在获取到一个界面时,会将这个界面所有的信息封装到一个对象里面,根据面向对象的思路,就可以找到组件。

(1)
因为组件的属性有id,name,value,xpath,如果要获取组件有明确的id,name,value,xpath等树形。那么根据属性的唯一标识直接找到这个组件,从而对组件进行操作。使用IE.start启动页面,这个函数以对象返回该页面HTML的树形结构,如果此时操作中变换到其他页面,browser这个对象值也会跟着改变。

browser =Watir::IE.start("http://www.baidu.com”)

browser.button(:id,”submit”),这样就可以找到IDsubmit的组件了。同样name,value,xpath只需要改变“:”后面的定义就可以了,其中XPATH前面介绍过任何一个组件都可以通过XPATH形式进行查找。

(2)
WATIR语法返回一个属性结构,在查找一个节点时可以从他的根节点一层一层的向下索引,这种方式对于组件ID自动生成有很好的效果,虽然组件存在ID,但是根据不同的情况该ID会变更,但是HTML节点位置是不会改变的,所以对于处理组件ID动态变更情况可以使用该种方法。

例如:


<div id=”test”>

<div>


aaa

</div>

<div>


<Ahref=”www.baidu.com”>baidu</A>

</div>

</div>

根据以上这个例子如果想找到baidu的超级链接并点击超级链接进入baidu页面,通过ID,name的方法是无法获取到。可以使用两种方法,一种是由于这个页面只有一个超级连接,所以只用index号为1找到这个链接:

browser.link(:index,1).click

另外一种是可以根树形结构一层一层向下查找,通过对界面观察,可以找到与元素相近的根也就是idtestdiv,这个div对象下面包含两个div,由于两个div都没有参数,所以这里使用index号找到第二个div标签,然后根据链接的index号找到链接。

browser.div(:id,”test”).div(:index,2).link(:index,1).click


作者: ryugun    时间: 2012-5-2 10:55

(1)
由于有些组件没有id,name等唯一标识这种情况下可以使用XPATH这个参数找到组件,看似复杂的语法,但是在FIREFOX中有插件可以直接找到组件对应的XPATH。使用XPATH查询组件曾经有一个问题是随着网页的变化组件的XPATH可能会更改。在XX项目自动化中很少使用该种进行查找。


作者: ryugun    时间: 2012-5-2 10:57

路径表达式:

  

表达式

  
  

描述

  
  

nodename

  
  

选取此节点的所有子节点。

  
  

/

  
  

从根节点选取。

  
  

//

  
  

从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。

  
  

.

  
  

选取当前节点。

  
  

..

  
  

选取当前节点的父节点。

  
  

@

  
  

选取属性。

  

作者: ryugun    时间: 2012-5-2 10:58

实例:

  

路径表达式

  
  

结果

  
  

/bookstore/book[1]

  
  

选取属于 bookstore 子元素的第一个 book 元素。

  
  

/bookstore/book[last()]

  
  

选取属于 bookstore 子元素的最后一个 book 元素。

  
  

/bookstore/book[last()-1]

  
  

选取属于 bookstore 子元素的倒数第二个 book 元素。

  
  

/bookstore/book[position()<3]

  
  

选取最前面的两个属于 bookstore 元素的子元素的 book 元素。

  
  

//title[@lang]

  
  

选取所有拥有名为 lang 的属性的 title 元素。

  

作者: ryugun    时间: 2012-5-2 10:58

实例:

  

路径表达式

  
  

结果

  
  

/bookstore/book[1]

  
  

选取属于 bookstore 子元素的第一个 book 元素。

  
  

/bookstore/book[last()]

  
  

选取属于 bookstore 子元素的最后一个 book 元素。

  
  

/bookstore/book[last()-1]

  
  

选取属于 bookstore 子元素的倒数第二个 book 元素。

  
  

/bookstore/book[position()<3]

  
  

选取最前面的两个属于 bookstore 元素的子元素的 book 元素。

  
  

//title[@lang]

  
  

选取所有拥有名为 lang 的属性的 title 元素。

  

作者: ryugun    时间: 2012-5-2 10:59
  

//title[@lang='eng']

  
  

选取所有 title 元素,且这些元素拥有值为 eng lang 属性。

  
  

/bookstore/book[price>35.00]

  
  

选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00

  
  

/bookstore/book[price>35.00]/title

  
  

选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00

  

作者: ryugun    时间: 2012-5-2 10:59
1.1
表格内组件查询、触发javascripe事件

有很多情况下表格不会单独只有数据,可能在表格内存在复选框,按钮,超级链接等组件。在这种情况下的组件通常没有ID或者ID会根据其他参数有所改变。这样在给自动化处理上就带来一些难度。如下图所示,mliu为创建的一个命名空间,如果想删除这个命名空间就一定要点击前面的复选框。


下图所示为插件显示该组件的信息,可以发现这个“复选框”实际上是一个div的,并不是checkbox,但是界面通过css样式的形式具有checkbox的功能。这个div并没有idname等属性。


这种情况下由于每次操作的表格项不一样,所以需要逐条循环。选择“名称”为mliu的行,并找到第一列的元素,到这里找到了一个<TD>。这个<TD>下面包含一个div(上图左面红色部分),根据index号这样就找到了这个文本框。但是此时又有一个问题出现了,这个图形表现是一个复选框,但是从HTML来看只是一个DIV,那么他就不具有checkbox的功能和动作。如果是一个简单的checkbox,可以使用set方法将这个checkbox选中。从右图红色标识可以看到这个DIV存在一个”onmousedown”的鼠标事件,那么勾选的动作是通过这个鼠标实现触发的。所以找到这个div之后需要调用fire_even(“onmousedown”)这个函数触发鼠标事件,这样这个元素就勾选上了。

t=ie.table(:id,"namespaceGrid")


t.rows.each do|h|



if h[3].text.strip ==
"mliu"


h[1].div(:index,1).fire_event("onmousedown")


break


end


end

另外,在确定查找某行或者某列时,一定要观察好这个表格的结构。有可能对外显示的是35列,但是实际上在HTML有一些隐藏的行和列,所以要看好元素真正的行和列,否则会查找失败,脚本报错。


作者: ryugun    时间: 2012-5-2 11:00
1.1
frame的使用

对于在复杂的WEB界面,其实是有很多个独立的HTML组成的,把这些独立的HTML组成在一起的是<iframe>标签。在查找一个组件时一定不要被界面的“外表”所蒙蔽,有可能这个组件在某一个frame中。所以当使用IE.start或者IE.attch找到一个页面时,首先要分析这个页面显示的结构,如果要是存在多个<iframe>标签,要找到所查找组件是否在哪个frame中,然后再按照3.1的方式进行查找组件。

例如下面这段代码中,是一个frame中嵌套另外一个frame,所以要索引到baidu的超级链接,不能通过ie.link(:index,1)的方式查找,必须要找到这个超级链接所对应的页面后,才能找到元素,根据下面代码也可以知道,每个iframe内嵌套了一个完成的HTML,所以要使用ie.frame(:id,”a1”),frame(:id,”namespace”).link(:index,1).click,这样就能找到这个超级链接了。


<html>


<iframe id=”a1”>


<html>


<iframe id=”namespace”>


<html>


<ahref=”www.baidu.com”>baidu</a>

</html>


</iframe>


<html>


</iframe>


</html>

2
经验总结、对规范的建议等

在对WATIR自动化开发过程中,还是有一些问题现在WATIR解决不了,应该是WATIR之后的最新的版本不支持这种情况。

1、
例如对于class_name这个属性如果存在两个值之间用空格隔开的情况,只能获取到class_name值的第一个值,空格后面的值就获取不到。这个应该是WATIR不支持多个class_name情况。

对于FLEXFLASE等无法使用WATIR进行RUBY自动化,所以对于新版本ISM的用户管理选择管理资源这个功能现在无法实现。
作者: ryugun    时间: 2012-5-2 20:44
[attach]78782[/attach]
作者: ryugun    时间: 2012-5-2 21:38
[attach]78783[/attach]
作者: ryugun    时间: 2012-5-3 21:52
[attach]78797[/attach]
作者: ryugun    时间: 2012-5-3 21:57
http://watij.sourceforge.net/docs/api/index.html?watij/IE.html
watij API
作者: xindexiaoxu    时间: 2012-5-3 22:11
感谢分享
作者: ryugun    时间: 2012-5-10 13:50
http://www.51testing.com/html/45/n-813345.html
浅析Java web程序之客户端和服务器端交互原理




欢迎光临 51Testing软件测试论坛 (http://bbs.51testing.com/) Powered by Discuz! X3.2