SeleniumAndJenkins

Selenium自动化测试+Jenkins自动部署整合篇

目录

自动化测试系列

自动化测试系列(1)—— 初识selenium

初识selenium是建立在已经有一个完整的待测试的项目的基础上的


项目简介

如图,是待测试的项目,此项目是在idea上创建的Spring boot项目,当然项目用到的jar包都在Maven的管理下。

项目结构

环境搭建

接下来搭建测试环境,先基于Maven导入jar包,没有Maven的,可以直接去网上下一个selenium-java的jar包。

        <!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>2.53.0</version>
        </dependency>

点击按钮导入jar包
导入jar包

最下面这行圈圈转完就表示导入完成了

导入完成

自动化测试系列(2)—— 下载浏览器驱动

个人比较喜欢用chrome浏览器,需要使用其他浏览器测试的需下载对应的浏览器驱动


驱动版本

chromedriver的版本要与你使用的chrome的版本对应,截至今日,chromedriver.exe与chrome浏览器的对应版本如下

chromedriver.exe的版本chrome浏览器的版本
v2.41v67-69
v2.40v66-68
v2.39v66-68
v2.38v65-67
v2.37v64-66
v2.36v63-65
v2.35v62-64
v2.34v61-63
v2.33v60-62
v2.32v59-61
v2.31v58-60
v2.30v58-60
v2.29v56-58
v2.28v55-57
v2.27v54-56
v2.26v53-55
v2.25v53-55
v2.24v52-54
v2.23v51-53
v2.22v49-52
v2.21v46-50
v2.20v43-48
v2.19v43-47
v2.18v43-46
v2.17v42-43
v2.13v42-45
v2.15v40-43
v2.14v39-42
v2.13v38-41
v2.12v36-40
v2.11v36-40
v2.10v33-36
v2.9v31-34
v2.8v30-33
v2.7v30-33
v2.6v29-32
v2.5v29-32
v2.4v29-32

下载地址

点击进入chromedriver.exe的下载地址


自动化测试系列(3)—— 编写测试代码

项目已经部署到tomcat服务器上了,现在编写打开浏览器访问项目登录页面并进行登录操作。


创建浏览器

上一篇下载了chromedriver.exe,找到这个驱动的位置,在测试类里设置驱动所在的位置,代码如下

        //设置驱动所在位置
        System.setProperty("webdriver.chrome.driver", "E:\\software\\driver\\chromedriver.exe");

设置好驱动后,引用驱动就可以打开浏览器了

        //引用谷歌浏览器驱动
        WebDriver driver = new ChromeDriver();

当然也可以通过ChromeOptions设置chrome的属性(不设置的话,默认浏览器一打开只有半屏)

        //设置chrome的属性
        ChromeOptions options = new ChromeOptions();
        List<String> op = new ArrayList<String>();
        //实现窗口最大化
        op.add("--start-maximized");
        //实现全屏
        //op.add("start-fullscreen");
        //op.add("allow-running-insecure-content");
        //op.add("--test-type");
        options.addArguments(op);
        //引用谷歌浏览器驱动
        WebDriver driver = new ChromeDriver(options);

还可以通过manage().window().setSize()自定义浏览器窗口大小

        //自定义浏览器窗口大小
        driver.manage().window().setSize(new Dimension(375, 812));

按照自己的喜好或者需求弄好后就可以打开浏览器界面了

        //打开界面
        driver.get("http://localhost/huinongloan2/");

模拟登录操作

通过driver.findElement()获取页面元素,个人比较喜欢使用css格式,即cssSelector

//输入账号
driver.findElement(By.cssSelector(".login-phone input")).sendKeys(loanerLoginVo.getLoanerPhone());
//输入密码
driver.findElement(By.cssSelector(".login-password input")).sendKeys(loanerLoginVo.getLoanerPassword());

填入手机号和密码之后,获取登录按钮并模拟点击操作

//点击登录
driver.findElement(By.cssSelector(".login-submit button")).click();

这时可以运行一下代码进行测试了


自动化测试系列(4)—— 初识testng

上一篇通过chrome进行了登录操作,现在来验证一下是否登录成功。


和第一天一样,在pom.xml文件下写上引入testng的jar包的代码并导入

<!-- https://mvnrepository.com/artifact/org.testng/testng -->
<dependency>
    <groupId>org.testng</groupId>
    <artifactId>testng</artifactId>
    <version>6.14.3</version>
    <scope>test</scope>
</dependency>

用例执行前后操作

自定义测试用例执行前后的操作,以下代码在用例执行前后打印和执行后关闭浏览器进程及驱动

    @BeforeClass
    public void beforeClass() {
        System.out.println("-------用例执行前-------");
    }

    @AfterClass
    public void afterClass(){
        System.out.println("-------用例执行后-------");
        //关闭浏览器进程及驱动
        driver.close();
    }

判断元素是否存在以判断是否登录成功,参考了以下博文的第一段代码
Selenium2(WebDriver)_如何判断WebElement元素对象是否存在

    public boolean doesWebElementExist(WebDriver driver, By selector)
    {

        try
        {
            driver.findElement(selector);
            return true;
        }
        catch (NoSuchElementException e)
        {
            return false;
        }
    }

判断特定元素是否存在+断言以校验是否登录成功

    if(doesWebElementExist(driver,By.cssSelector(".title2"))){
        //断言 校验是否登录成功
        Assert.assertEquals(driver.findElement(By.cssSelector(".title2")).getText(),loanerLoginVo.getLoanerPhone());
    } else {
        throw new RuntimeException("登录失败");
    }

测试代码

测试代码如下

/**
 * @author xian
 * @date 2018/8/2 14:32
 */
public class H5LoanerLoginTest {

    public boolean doesWebElementExist(WebDriver driver, By selector)
    {

        try
        {
            driver.findElement(selector);
            return true;
        }
        catch (NoSuchElementException e)
        {
            return false;
        }
    }

    WebDriver driver = getDriver();

    // 定义数据源
    @DataProvider(name = "list")
    public Iterator<Object[]> createData() throws FileNotFoundException, ExcelException {
        LinkedHashMap<String,String> fieldMap = new LinkedHashMap<String,String>();
        fieldMap.put("手机","loanerPhone");
        fieldMap.put("密码","loanerPassword");
        LoanerLoginVo loanerLoginVo = new LoanerLoginVo();
        //从excel中获取数据
        return ExcelUtil.excelIn(fieldMap, "loanerLogin", "登录信息", loanerLoginVo.getClass());
    }

    @BeforeClass
    public void beforeClass() {
        System.out.println("用例前执行打印本句!");
        System.out.println("每条Test用例是互不相干的");
        System.out.println("用例开始执行…………");
    }

    @AfterClass
    public void afterClass(){
        System.out.println("用例结束后运行");
        //关闭浏览器进程及驱动
        driver.close();
    }

    public WebDriver getDriver(){
        //设置驱动所在位置
        System.setProperty("webdriver.chrome.driver", "E:\\software\\driver\\chromedriver.exe");
        //设置chrome的属性
        ChromeOptions options = new ChromeOptions();
        List<String> op = new ArrayList<String>();
        //实现窗口最大化
        op.add("--start-maximized");
        //实现全屏
        //op.add("start-fullscreen");
        //op.add("allow-running-insecure-content");
        //op.add("--test-type");
        options.addArguments(op);
        //引用谷歌浏览器驱动
        WebDriver driver = new ChromeDriver(options);
        return driver;
    }

    @Test(dataProvider = "list")
    public void action(LoanerLoginVo loanerLoginVo){

        //自定义浏览器窗口大小
        driver.manage().window().setSize(new Dimension(375, 812));
        //打开网页
        driver.get("http://localhost/huinongloan2/");
        //输入手机号
        driver.findElement(By.cssSelector(".login-phone input")).sendKeys(loanerLoginVo.getLoanerPhone());
        //输入密码
        driver.findElement(By.cssSelector(".login-password input")).sendKeys(loanerLoginVo.getLoanerPassword());

        //点击登录
        driver.findElement(By.cssSelector(".login-submit button")).click();

        try {
            //页面等待
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        if(doesWebElementExist(driver,By.cssSelector(".title2"))){
            //断言 校验是否登录成功
            Assert.assertEquals(driver.findElement(By.cssSelector(".title2")).getText(),loanerLoginVo.getLoanerPhone());
        } else {
            throw new RuntimeException("登录失败");
        }

    }
}

自动化测试系列(5)—— 定义数据源

上一篇通过chrome进行了登录验证操作,也放上了测试代码,分析一下testng的定义数据源操作


第一种方式:需要明确测试数据组数

    @DataProvider( name= "list")
    public Object[][] createData(){
        return new Object[][]{
            { "1", "123456" },
            { "2", "123456" },
            { "1", "111111" }
        };
   }

第二种方式:不需明确测试数据组数

    // 定义数据源
    @DataProvider(name = "list")
    public Iterator<Object[]> createData() throws FileNotFoundException, ExcelException {
        LinkedHashMap<String,String> fieldMap = new LinkedHashMap<String,String>();
        fieldMap.put("手机","loanerPhone");
        fieldMap.put("密码","loanerPassword");
        LoanerLoginVo loanerLoginVo = new LoanerLoginVo();
        //从excel中获取数据
        return ExcelUtil.excelIn(fieldMap, "loanerLogin", "登录信息", loanerLoginVo.getClass());
    }

自动化测试系列(6)—— excel表导入测试案例

感受一下从excel表导入测试案例


本项目对excel表的操作主要用了jxl,首先在pom.xml文件下写上引入jxl的jar包的代码并导入

        <!-- jxl -->
        <dependency>
            <groupId>net.sourceforge.jexcelapi</groupId>
            <artifactId>jxl</artifactId>
            <version>2.6.12</version>
        </dependency>

ExcelUtil工具类代码

public class ExcelUtil {

    /**
     * @MethodName          : excelToList
     * @Description         : 将Excel转化为List
     * @param in            :承载着Excel的输入流
     * @param entityClass   :List中对象的类型(Excel中的每一行都要转化为该类型的对象)
     * @param fieldMap      :Excel中的中文列头和类的英文属性的对应关系Map
     * @param uniqueFields  :指定业务主键组合(即复合主键),这些列的组合不能重复
     * @return              :List
     * @throws ExcelException
     */
    public static <T> List<T> excelToList(
            InputStream in,
            String sheetName,
            Class<T> entityClass,
            LinkedHashMap<String, String> fieldMap,
            String[] uniqueFields
    ) throws ExcelException {

        //定义要返回的list
        List<T> resultList=new ArrayList<T>();

        try {

            //根据Excel数据源创建WorkBook
            Workbook wb=Workbook.getWorkbook(in);
            //获取工作表
            Sheet sheet=wb.getSheet(sheetName);

            //获取工作表的有效行数
            int realRows=0;
            for(int i=0;i<sheet.getRows();i++){

                int nullCols=0;
                for(int j=0;j<sheet.getColumns();j++){
                    Cell currentCell=sheet.getCell(j,i);
                    if(currentCell==null || "".equals(currentCell.getContents().toString())){
                        nullCols++;
                    }
                }

                if(nullCols==sheet.getColumns()){
                    break;
                }else{
                    realRows++;
                }
            }


            //如果Excel中没有数据则提示错误
            if(realRows<=1){
                throw new ExcelException("Excel文件中没有任何数据");
            }


            Cell[] firstRow=sheet.getRow(0);

            String[] excelFieldNames=new String[firstRow.length];

            //获取Excel中的列名
            for(int i=0;i<firstRow.length;i++){
                excelFieldNames[i]=firstRow[i].getContents().toString().trim();
            }

            //判断需要的字段在Excel中是否都存在
            boolean isExist=true;
            List<String> excelFieldList= Arrays.asList(excelFieldNames);
            for(String cnName : fieldMap.keySet()){
                if(!excelFieldList.contains(cnName)){
                    isExist=false;
                    break;
                }
            }

            //如果有列名不存在,则抛出异常,提示错误
            if(!isExist){
                throw new ExcelException("Excel中缺少必要的字段,或字段名称有误");
            }

            //将列名和列号放入Map中,这样通过列名就可以拿到列号
            LinkedHashMap<String, Integer> colMap=new LinkedHashMap<String, Integer>();
            for(int i=0;i<excelFieldNames.length;i++){
                colMap.put(excelFieldNames[i], firstRow[i].getColumn());
            }

            //判断是否有重复行
            //1.获取uniqueFields指定的列
            Cell[][] uniqueCells=new Cell[uniqueFields.length][];
            for(int i=0;i<uniqueFields.length;i++){
                int col=colMap.get(uniqueFields[i]);
                uniqueCells[i]=sheet.getColumn(col);
            }

            //2.从指定列中寻找重复行
            for(int i=1;i<realRows;i++){
                int nullCols=0;
                for(int j=0;j<uniqueFields.length;j++){
                    String currentContent=uniqueCells[j][i].getContents();
                    Cell sameCell=sheet.findCell(currentContent,
                            uniqueCells[j][i].getColumn(),
                            uniqueCells[j][i].getRow()+1,
                            uniqueCells[j][i].getColumn(),
                            uniqueCells[j][realRows-1].getRow(),
                            true);
                    if(sameCell!=null){
                        nullCols++;
                    }
                }

                if(nullCols==uniqueFields.length){
                    throw new ExcelException("Excel中有重复行,请检查");
                }
            }

            //将sheet转换为list
            for(int i=1;i<realRows;i++){
                //新建要转换的对象
                T entity=entityClass.newInstance();

                //给对象中的字段赋值
                for(Map.Entry<String, String> entry : fieldMap.entrySet()){
                    //获取中文字段名
                    String cnNormalName=entry.getKey();
                    //获取英文字段名
                    String enNormalName=entry.getValue();
                    //根据中文字段名获取列号
                    int col=colMap.get(cnNormalName);

                    //获取当前单元格中的内容
                    String content=sheet.getCell(col, i).getContents().toString().trim();

                    //给对象赋值
                    setFieldValueByName(enNormalName, content, entity);
                }

                resultList.add(entity);
            }
        } catch(Exception e){
            e.printStackTrace();
            //如果是ExcelException,则直接抛出
            if(e instanceof ExcelException){
                throw (ExcelException)e;

                //否则将其它异常包装成ExcelException再抛出
            }else{
                e.printStackTrace();
                throw new ExcelException("导入Excel失败");
            }
        }
        return resultList;
    }

    /**
     * @MethodName  : setFieldValueByName
     * @Description : 根据字段名给对象的字段赋值
     * @param fieldName  字段名
     * @param fieldValue    字段值
     * @param o 对象
     */
    private static void setFieldValueByName(String fieldName,Object fieldValue,Object o) throws Exception{

        Field field=getFieldByName(fieldName, o.getClass());
        if(field!=null){
            field.setAccessible(true);
            //获取字段类型
            Class<?> fieldType = field.getType();

            //根据字段类型给字段赋值
            if (String.class == fieldType) {
                field.set(o, String.valueOf(fieldValue));
            } else if ((Integer.TYPE == fieldType)
                    || (Integer.class == fieldType)) {
                field.set(o, Integer.parseInt(fieldValue.toString()));
            } else if ((Long.TYPE == fieldType)
                    || (Long.class == fieldType)) {
                field.set(o, Long.valueOf(fieldValue.toString()));
            } else if ((Float.TYPE == fieldType)
                    || (Float.class == fieldType)) {
                field.set(o, Float.valueOf(fieldValue.toString()));
            } else if ((Short.TYPE == fieldType)
                    || (Short.class == fieldType)) {
                field.set(o, Short.valueOf(fieldValue.toString()));
            } else if ((Double.TYPE == fieldType)
                    || (Double.class == fieldType)) {
                field.set(o, Double.valueOf(fieldValue.toString()));
            } else if (Character.TYPE == fieldType) {
                if ((fieldValue!= null) && (fieldValue.toString().length() > 0)) {
                    field.set(o, Character
                            .valueOf(fieldValue.toString().charAt(0)));
                }
            }else if(Date.class==fieldType){
                field.set(o, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(fieldValue.toString()));
            }else{
                field.set(o, fieldValue);
            }
        }else{
            throw new ExcelException(o.getClass().getSimpleName() + "类不存在字段名 "+fieldName);
        }
    }

    /**
     * @MethodName  : getFieldByName
     * @Description : 根据字段名获取字段
     * @param fieldName 字段名
     * @param clazz 包含该字段的类
     * @return 字段
     */
    private static Field getFieldByName(String fieldName, Class<?>  clazz){
        //拿到本类的所有字段
        Field[] selfFields=clazz.getDeclaredFields();

        //如果本类中存在该字段,则返回
        for(Field field : selfFields){
            if(field.getName().equals(fieldName)){
                return field;
            }
        }

        //否则,查看父类中是否存在此字段,如果有则返回
        Class<?> superClazz=clazz.getSuperclass();
        if(superClazz!=null  &&  superClazz !=Object.class){
            return getFieldByName(fieldName, superClazz);
        }

        //如果本类和父类都没有,则返回空
        return null;
    }

    /**
     * @author xian
     * @date 2018/7/30 16:35
     * @param list 实体类列表
     * List<实体类>转为List<Object>类型
     * @return java.util.List<java.lang.Object>
     */
    static public <E> List<Object> toObject(List<E> list){
        List<Object> objlist = new ArrayList<Object>();
        for(Object e : list){
            Object obj = (Object)e;
            objlist.add(obj);
        }
        return objlist;
    }

    /**
     * @author xian
     * @date 2018/7/30 16:37
     * @param fieldMap 列名与字段名对应关系
     * @param fileName 文件名
     * @param sheetName sheet名称
     * @param entityClass 实体类(示例:user.getClass())
     * @return java.util.Iterator<java.lang.Object[]>
     */
    public static Iterator<Object[]> excelIn(LinkedHashMap<String,String> fieldMap, String fileName, String sheetName, Class entityClass) throws FileNotFoundException, ExcelException {

        File file = new File("E:\\huinongloan2\\excel\\"+fileName+".xls");
        InputStream in = new FileInputStream(file);//实例化FileInputStream
        String[] uniqueFields = {"loanerPhone"};
        List<Object> alldata = toObject(ExcelUtil.excelToList(in, sheetName, entityClass, fieldMap, uniqueFields));
        List<Object[]> result=new ArrayList<Object[]>();
        Iterator it=alldata.iterator();
        while(it.hasNext()){
            result.add(new Object[] { it.next() });
        }
        return result.iterator();

    }

}

ExcelException自定义excel异常类

public class ExcelException  extends Exception {

    public ExcelException() {
        // TODO Auto-generated constructor stub
    }

    public ExcelException(String message) {
        super(message);
        // TODO Auto-generated constructor stub
    }

    public ExcelException(Throwable cause) {
        super(cause);
        // TODO Auto-generated constructor stub
    }

    public ExcelException(String message, Throwable cause) {
        super(message, cause);
        // TODO Auto-generated constructor stub
    }

}

在testng定义的数据源中导入excel表中的数据

    // 定义数据源
    @DataProvider(name = "list")
    public Iterator<Object[]> createData() throws FileNotFoundException, ExcelException {
        LinkedHashMap<String,String> fieldMap = new LinkedHashMap<String,String>();
        fieldMap.put("手机","loanerPhone");
        fieldMap.put("密码","loanerPassword");
        LoanerLoginVo loanerLoginVo = new LoanerLoginVo();
        //从excel中获取数据
        return ExcelUtil.excelIn(fieldMap, "loanerLogin", "登录信息", loanerLoginVo.getClass());
    }

自动化测试系列(7)—— 模拟post/get请求进行接口测试

模拟post/get请求进行接口测试跟selenium没有关系,主要用到了OkHttp来对网页进行模拟post/get请求,和利用testng接收测试案例和显示测试结果


本项目对网页请求的操作主要用了okhttp3,首先在pom.xml文件下写上引入okhttp3的jar包的代码并导入

        <!--okhttp3 jar-->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>3.10.0</version>
        </dependency>

OkhttpUtil工具类代码

public class OkHttpUtil {
    private static final Logger logger = LoggerFactory.getLogger(OkHttpUtil.class);

    /**
     * get
     * @param url     请求的url
     * @param queries 请求的参数,在浏览器?后面的数据,没有可以传null
     * @return
     */
    public static  String get(String url, Map<String, String> queries) {
        String responseBody = "";
        StringBuffer sb = new StringBuffer(url);
        if (queries != null && queries.keySet().size() > 0) {
            boolean firstFlag = true;
            Iterator iterator = queries.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry entry = (Map.Entry<String, String>) iterator.next();
                if (firstFlag) {
                    sb.append("?" + entry.getKey() + "=" + entry.getValue());
                    firstFlag = false;
                } else {
                    sb.append("&" + entry.getKey() + "=" + entry.getValue());
                }
            }
        }
        Request request = new Request.Builder()
                .url(sb.toString())
                .build();
        Response response = null;
        try {
            OkHttpClient okHttpClient = new OkHttpClient();
            response = okHttpClient.newCall(request).execute();
            int status = response.code();
            if (response.isSuccessful()) {
                return response.body().string();
            }
        } catch (Exception e) {
            logger.error("okhttp3 put error >> ex = {}", ExceptionUtils.getStackTrace(e));
        } finally {
            if (response != null) {
                response.close();
            }
        }
        return responseBody;
    }

    /**
     * post
     *
     * @param url    请求的url
     * @param params post form 提交的参数
     * @return
     */
    public static String post(String url, Map<String, String> params) {
        String responseBody = "";
        FormBody.Builder builder = new FormBody.Builder();
        //添加参数
        if (params != null && params.keySet().size() > 0) {
            int count = 1;
            for (String key : params.keySet()) {
                if(params.get(key) != null)builder.add(key, params.get(key));
                count++;
            }
        }
        Request request = new Request.Builder()
                .url(url)
                .post(builder.build())
                .build();
        Response response = null;
        try {
            OkHttpClient okHttpClient = new OkHttpClient();
            response = okHttpClient.newCall(request).execute();
            int status = response.code();
            if (response.isSuccessful()) {
                System.out.println("success:"+url);
                return response.body().string();
            }
            System.out.println("fail:"+url);
        } catch (Exception e) {
            logger.error("okhttp3 post error >> ex = {}", ExceptionUtils.getStackTrace(e));
        } finally {
            if (response != null) {
                response.close();
            }
        }
        return responseBody;
    }

    /**
     * get
     * @param url     请求的url
     * @param queries 请求的参数,在浏览器?后面的数据,没有可以传null
     * @return
     */
    public static String getForHeader(String url, Map<String, String> queries) {
        String responseBody = "";
        StringBuffer sb = new StringBuffer(url);
        if (queries != null && queries.keySet().size() > 0) {
            boolean firstFlag = true;
            Iterator iterator = queries.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry entry = (Map.Entry<String, String>) iterator.next();
                if (firstFlag) {
                    sb.append("?" + entry.getKey() + "=" + entry.getValue());
                    firstFlag = false;
                } else {
                    sb.append("&" + entry.getKey() + "=" + entry.getValue());
                }
            }
        }
        Request request = new Request.Builder()
                .addHeader("key", "value")
                .url(sb.toString())
                .build();
        Response response = null;
        try {
            OkHttpClient okHttpClient = new OkHttpClient();
            response = okHttpClient.newCall(request).execute();
            int status = response.code();
            if (response.isSuccessful()) {
                return response.body().string();
            }
        } catch (Exception e) {
            logger.error("okhttp3 put error >> ex = {}", e.getMessage());
        } finally {
            if (response != null) {
                response.close();
            }
        }
        return responseBody;
    }

    /**
     * Post请求发送JSON数据....{"name":"zhangsan","pwd":"123456"}
     * 参数一:请求Url
     * 参数二:请求的JSON
     * 参数三:请求回调
     */
    public static String postJsonParams(String url, String jsonParams) {
        String responseBody = "";
        okhttp3.RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), jsonParams);
        Request request = new Request.Builder()
                .url(url)
                .post(requestBody)
                .build();
        Response response = null;
        try {
            OkHttpClient okHttpClient = new OkHttpClient();
            response = okHttpClient.newCall(request).execute();
            int status = response.code();
            if (response.isSuccessful()) {
                return response.body().string();
            }
        } catch (Exception e) {
            logger.error("okhttp3 post error >> ex = {}", ExceptionUtils.getStackTrace(e));
        } finally {
            if (response != null) {
                response.close();
            }
        }
        return responseBody;
    }

    /**
     * Post请求发送xml数据....
     * 参数一:请求Url
     * 参数二:请求的xmlString
     * 参数三:请求回调
     */
    public static String postXmlParams(String url, String xml) {
        String responseBody = "";
        RequestBody requestBody = RequestBody.create(MediaType.parse("application/xml; charset=utf-8"), xml);
        Request request = new Request.Builder()
                .url(url)
                .post(requestBody)
                .build();
        Response response = null;
        try {
            OkHttpClient okHttpClient = new OkHttpClient();
            response = okHttpClient.newCall(request).execute();
            int status = response.code();
            if (response.isSuccessful()) {
                return response.body().string();
            }
        } catch (Exception e) {
            logger.error("okhttp3 post error >> ex = {}", ExceptionUtils.getStackTrace(e));
        } finally {
            if (response != null) {
                response.close();
            }
        }
        return responseBody;
    }
}

调用OkHttpUtil的postJsonParams方法模拟post请求

@Test(dataProvider = "list")
    public void action(LoanerLoginVo loanerUpdateBasicInfoVo) {

        JSONObject jsonObject = JSONObject.fromObject(loanerUpdateBasicInfoVo);

        String url = "http://localhost/huinongloan2/web/loaner/loanerLogin";

        String res = OkHttpUtil.postJsonParams(url, String.valueOf(jsonObject));

        //System.out.println(res);

        JSONObject jsonObjectRes = JSONObject.fromObject(res);

        if (jsonObjectRes.get("code")==1) {
            throw new RuntimeException(jsonObjectRes.get("errmsg").toString());
        } else {
            Reporter.log(DateUtils.getCurrentTime() + ": 验证成功");
        }

    }

自动化测试系列(8)—— 初识reportng(自定义页面图片版)

先放两张自定义好的测试报告镇一下,大概是人生第一次改完源码还打包,大家随意看就好。


在本来的测试报告总览页面添加了饼图

添加饼图


在本来的测试报告用例页面添加了截图

添加截图

点击截图可以放大查看

点击截图放大


自动化测试系列(9)—— 初识reportng(源码页面代码版)

不解释,直接上代码。


pom.xml添加代码

        <!--https://mvnrepository.com/artifact/org.uncommons/reportng-->
        <dependency>
            <groupId>org.uncommons</groupId>
            <artifactId>reportng</artifactId>
            <version>1.1.4</version>
            <scope>test</scope>
        </dependency>

        <!-- 依赖Guice -->
        <dependency>
            <groupId>com.google.inject</groupId>
            <artifactId>guice</artifactId>
            <version>4.0</version>
            <scope>test</scope>
        </dependency>

引入surefire

        <!-- https://mvnrepository.com/artifact/org.apache.maven.surefire/surefire -->
        <dependency>
            <groupId>org.apache.maven.surefire</groupId>
            <artifactId>surefire</artifactId>
            <version>2.22.0</version>
            <type>pom</type>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.maven.surefire/surefire-testng -->
        <dependency>
            <groupId>org.apache.maven.surefire</groupId>
            <artifactId>surefire-testng</artifactId>
            <version>2.22.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.maven.surefire/surefire-providers -->
        <dependency>
            <groupId>org.apache.maven.surefire</groupId>
            <artifactId>surefire-providers</artifactId>
            <version>2.22.0</version>
            <type>pom</type>
        </dependency>

指定testng.xml的位置

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.0</version>
                <configuration>
                    <!--<skipTests>true</skipTests>-->
                    <properties>
                        <property>
                            <name>userdefaultlisteners</name>
                            <value>false</value>
                        </property>
                        <property>
                            <name>listener</name>
                            <value>org.uncommons.reportng.HTMLReporter,
                                org.uncommons.reportng.JUnitXMLReporter,
                            </value>
                        </property>
                    </properties>
                    <testFailureIgnore>true</testFailureIgnore>
                    <!--指定testng.xml的位置-->
                    <suiteXmlFiles>
                        <file>testng.xml</file>
                    </suiteXmlFiles>
                    <workingDirectory>target/</workingDirectory>
                </configuration>
            </plugin>

改完pom.xml后,import一下,开始配置testng.xml,本项目的testng.xml文件在项目的根目录下,所以上面的配置代码就是testng.xml,注意这个路径代码要根据testng.xml的位置来填

这里在项目的根目录下手动添加了testng.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<!--用例名-->
<suite name="All Test Suite">
    <!--测试-->
    <test verbose="1" preserve-order="true" name="E:\huinongloan2\src\test\java\selenium">
        <!--测试类-->
       <classes>
           <class name="selenium.H5Test"/>
           <!--<class name="selenium.apiTest.LoginTest"/>-->
           <!--<class name="selenium.apiTest.H5LoanerLoginTest"/>-->
           <!--<class name="selenium.apiTest.UpdateBasicInfoTest"/>-->
       </classes>
    </test>
</suite>

本项目的测试用例暂时这么简单,复杂用例(比如测试组什么的以后再说),配置好testng.xml就可以进行测试了

这里写图片描述

测试完可以看到reportng生成的测试报告,用浏览器打开target → surefire-reports → html → index.html

源码生成的测试报告

就可以看到如下图的测试报告啦

这里写图片描述

自动化测试系列(10)—— 初识reportng(自定义页面代码版)

准备改源码……


其实自定义页面,也就是把上次导入的reportng.1.1.4.jar包的源码,改成自己想要样子,再打包放到原来的位置,就是下面pom.xml代码中的这个包

        <!--https://mvnrepository.com/artifact/org.uncommons/reportng-->
        <dependency>
            <groupId>org.uncommons</groupId>
            <artifactId>reportng</artifactId>
            <version>1.1.4</version>
            <scope>test</scope>
        </dependency>

首先去Reportng的源码地址把源码下载下来,用idea打开,可以看到源码的项目结构如下

reportng的项目结构

先来改pom.xml,本项目用的6.14.3的testng,2.22.0的maven-surefire-plugin和1.7版本的JDK

  <dependencies>
    <dependency>
      <groupId>org.testng</groupId>
      <artifactId>testng</artifactId>
      <version>6.14.3</version>
    </dependency>
    <dependency>
      <groupId>velocity</groupId>
      <artifactId>velocity</artifactId>
      <version>1.4</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.22.0</version>
        <configuration>
          <systemPropertyVariables>
            <org.uncommons.reportng.escape-output>false</org.uncommons.reportng.escape-output>
          </systemPropertyVariables>

        </configuration>
      </plugin>
      <!--配置JDK版本-->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.7</source>
          <target>1.7</target>
        </configuration>
      </plugin>
    </plugins>
  </build>

添加饼图

import后,先来添个饼图,打开overview.html.vm,导入ichart组件(开源图形插件)

<script src='http://www.ichartjs.com/ichart.latest.min.js'></script>

接下来添加饼图的div,本项目把饼图放到了总览表格的下面,点击查看图片参考,个人觉得这样比放在表格上方更好看

</table>
#end
<div id='ichart-render'></div>
</body>
</html>

给总数,失败总数和跳过总数添加id,方便后面的js代码获取他们的值

      #if ($totalPassed > 0)
          <td id="tpn" class="passed number">$totalPassed</td>
      #else
          <td id="tpn" class="zero number">0</td>
      #end

      #if ($totalSkipped > 0)
          <td id="tsn" class="skipped number">$totalSkipped</td>
      #else
          <td id="tsn" class="zero number">0</td>
      #end

      #if ($totalFailed > 0)
          <td id="tfn" class="failed number">$totalFailed</td>
      #else
          <td id="tfn" class="zero number">0</td>
      #end

饼图显示的js代码,放在饼图的div下方即可

<script type='text/javascript'>
    pcount=document.getElementById("tpn").innerHTML;
    fcount=document.getElementById("tfn").innerHTML;
    scount=document.getElementById("tsn").innerHTML;
    $(function(){
        var chart = iChart.create({
            render:"ichart-render",
            width:800,
            height:400,
            background_color:"#fefefe",
            gradient:false,
            color_factor:0.2,
            border:{
                color:"BCBCBC",
                width:0
            },
            align:"center",
            offsetx:0,
            offsety:0,
            sub_option:{
                border:{
                    color:"#BCBCBC",
                    width:1
                },
                label:{
                    fontweight:500,
                    fontsize:11,
                    color:"#4572a7",
                    sign:"square",
                    sign_size:12,
                    border:{
                        color:"#BCBCBC",
                        width:1
                    }
                }
            },
            shadow:true,
            shadow_color:"#666666",
            shadow_blur:2,
            showpercent:false,
            column_width:"70%",
            bar_height:"70%",
            radius:"90%",
            subtitle:{
                text:"",
                color:"#111111",
                fontsize:16,
                font:"微软雅黑",
                textAlign:"center",
                height:20,
                offsetx:0,
                offsety:0
            },
            footnote:{
                text:"",
                color:"#111111",
                fontsize:12,
                font:"微软雅黑",
                textAlign:"right",
                height:20,
                offsetx:0,
                offsety:0
            },
            legend:{
                enable:false,
                background_color:"#fefefe",
                color:"#333333",
                fontsize:12,
                border:{
                    color:"#BCBCBC",
                    width:1
                },
                column:1,
                align:"right",
                valign:"center",
                offsetx:0,
                offsety:0
            },
            coordinate:{
                width:"80%",
                height:"84%",
                background_color:"#ffffff",
                axis:{
                    color:"#a5acb8",
                    width:[1,"",1,""]
                },
                grid_color:"#d9d9d9",
                label:{
                    fontweight:500,
                    color:"#666666",
                    fontsize:11
                }
            },
            label:{
                fontweight:500,
                color:"#666666",
                fontsize:11
            },
            type:"pie2d",
            data:[
                {
                    name:"通过",
                    value:pcount,
                    color:"#44aa44"
                },{
                    name:"失败",
                    value:fcount,
                    color:"#ff4444"
                },{
                    name:"跳过",
                    value:scount,
                    color:"#FFD700"
                }
            ]
        });
        chart.draw();
    });
</script>

到这里饼图就算添加完成了


添加截图

首先要明确知道,测试用例报错的时候才会截图,打开results.html.vm,找到#if ($failedTests.size() > 0)…#end,改成以下代码

  #if ($failedTests.size() > 0)

  <table class="resultsTable">
      <tr><th colspan="5" class="header failed">$messages.getString("failedTests")</th></tr>
    #foreach ($testClass in $failedTests.keySet())
        <tr>
            <td colspan="1" class="group">$testClass.name</td>
            <td colspan="1" class="group">$messages.getString("duration")</td>
            <td colspan="1" class="group">$messages.getString("log")</td>
            <td colspan="1" class="group">$messages.getString("screenshot")</td>
        </tr>
      #set ($classResults = $failedTests.get($testClass))
      #parse ("org/uncommons/reportng/templates/html/class-results.html.vm")
    #end
  </table>

  #end

对应的,在reportng.properties最底下加上这两行

log=Log Info
screenshot=Screen Shot

打开ReportNGUtils.java,新增两个方法getImageString()和removeImage()

    //提取含有img标签的字符串
    public String getImageString(String s)
    {
        String regex = "(<img(.*?)/>)";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(s);
        while (matcher.find()) {
            String group = matcher.group(1);
            return group;
        }
        return "";
    }

    //去除带有img标签的字符串
    public String removeImage(String s)
    {
        return  s.replaceAll("<img(.*?)/>","");
    }

打开class-results.html.vm,在倒数第二行加入一列来显示图片,在这里用到了ReportNGUtils.java新增方法的其一

    <td class="screenshot">
      #set ($output = $utils.getTestOutput($testResult))
      #if ($output.size() > 0)
          <div class="screenshotimage">
            #foreach( $line in $output )
              #if ($meta.shouldEscapeOutput())
                $utils.getImageString($line)<br/>
              #else
                $utils.getImageString($line)<br/>
              #end
            #end
          </div>
      #end
    </td>

打开ReportNGUtils.java……我已经忘记这里有没有改什么了,自己对照一下吧

    /**
     * Replace any angle brackets, quotes, apostrophes or ampersands with the
     * corresponding XML/HTML entities to avoid problems displaying the String in
     * an XML document.  Assumes that the String does not already contain any
     * entities (otherwise the ampersands will be escaped again).
     * @param s The String to escape.
     * @return The escaped String.
     */
    public String escapeString(String s)
    {
        if (s == null)
        {
            return null;
        }

        StringBuilder buffer = new StringBuilder();
        for(int i = 0; i < s.length(); i++)
        {
            buffer.append(escapeChar(s.charAt(i)));
        }
        return buffer.toString();
    }


    /**
     * Converts a char into a String that can be inserted into an XML document,
     * replacing special characters with XML entities as required.
     * @param character The character to convert.
     * @return An XML entity representing the character (or a String containing
     * just the character if it does not need to be escaped).
     */
    private String escapeChar(char character)
    {
        switch (character)
        {
            case '<': return "&lt;";
            case '>': return "&gt;";
            case '"': return "&quot;";
            case '\'': return "&apos;";
            case '&': return "&amp;";
            default: return String.valueOf(character);
        }
    }


    /**
     * Works like {@link #escapeString(String)} but also replaces line breaks with
     * &lt;br /&gt; tags and preserves significant whitespace. 
     * @param s The String to escape.
     * @return The escaped String.
     */
    public String escapeHTMLString(String s)
    {
        if (s == null)
        {
            return null;
        }

        StringBuilder buffer = new StringBuilder();
        for(int i = 0; i < s.length(); i++)
        {
            char ch = s.charAt(i);
            switch (ch)
            {
                case ' ':
                    // All spaces in a block of consecutive spaces are converted to
                    // non-breaking space (&nbsp;) except for the last one.  This allows
                    // significant whitespace to be retained without prohibiting wrapping.
                    char nextCh = i + 1 < s.length() ? s.charAt(i + 1) : 0;
                    buffer.append(nextCh==' ' ? "&nbsp;" : " ");
                    break;
                case '\n':
                    buffer.append("<br/>\n");
                    break;
                default:
                    buffer.append(escapeChar(ch));
            }
        }
        return buffer.toString();
    }

添加截图在这里告一段落


调整测试结果显示顺序

测试详情中的结果是按照名称排列的,现在调整为按照测试顺序排列,打开TestResultComparator.java,修改代码如下

public int compare(ITestResult result1, ITestResult result2)
    {
        //return result1.getName().compareTo(result2.getName());
        int longresult2 = 0;
        if(result1.getStartMillis() < result2.getStartMillis()){
            longresult2 = -1;
        } else {
            longresult2 = 1;
        }
        return longresult2;
    }

字符转换

在这里填一个我遇到的坑,后期用到jenkins的时候,在jenkins里显示的测试报告会显示中文乱码,需要修改AbstractReporter.java文件的generateFile方法如下,使显示字符转换成utf-8

    /**
     * Generate the specified output file by merging the specified
     * Velocity template with the supplied context.
     */
    protected void generateFile(File file,
                                String templateName,
                                VelocityContext context) throws Exception
    {
        //Writer writer = new BufferedWriter(new FileWriter(file));
        OutputStream out=new FileOutputStream(file);
        Writer writer = new BufferedWriter(new OutputStreamWriter(out,"utf-8"));
        try
        {
            Velocity.mergeTemplate(classpathPrefix + templateName,
                                   ENCODING,
                                   context,
                                   writer);
            writer.flush();
        }
        finally
        {
            writer.close();
        }
    }

ant打包
现在自定义了总览饼图,错误截图,展示顺序,差不多该打包reportng项目了,右键build.xml,点击Add as Ant Build File

添加打包文件

可以看到右侧出现ant打包步骤,双击release进行打包

点击打包

点击idea下方的messages查看打包信息,可以看到已经打包完成

打包完成

可以看到打包好的压缩包,解压后复制jar包到maven的本地仓库

压缩包

解压

复制

更新一下项目再测试一遍,就可以得到饼图和按执行顺序排序的测试结果了,截图的测试用例还要再写


自动化测试系列(11)—— 测试用例(截图版)

举一个selenium+testng+reportng测试报告显示测试失败截图的测试用例


失败后操作

    //用例结束的时候判断结果是否失败
    @AfterMethod(alwaysRun = true)
    public void afterMethod(ITestResult result) throws Exception {
        if (!result.isSuccess())
            catchExceptions(result);
    }

    //失败将设置的图片写入report
    public void catchExceptions(ITestResult result) throws IOException {
        if (!result.isSuccess()) {
            //在testng的report默认路径test-output下的html文件夹中创建一个截图文件夹以存放截图
            //截图放在这里把整个测试报告(test-output文件夹)单独打包发给别人查看也不会报错
            File file = new File("test-output/html/snapshot");
            Reporter.setCurrentTestResult(result);
            Reporter.log(file.getAbsolutePath());
            String filePath = file.getAbsolutePath();
            String dest = result.getMethod().getRealClass().getSimpleName()+"."+result.getMethod().getMethodName();
            String time = DateUtils.getCurrentDateTime2();
            String picName=filePath+File.separator+dest+time +".png";
            File srcFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
            FileUtils.copyFile(srcFile, new File(picName));
            String escapePicName="snapshot"+File.separator+dest+time+".png";
            String openPicName=escapeString("snapshot"+File.separator+dest+time+".png");
            //window.open()实现点击图片在新窗口显示大图,其中图片路径需要转义
            String html="<img src='"+escapePicName+"' onclick='window.open(\""+openPicName+"\")'' hight='100' width='100'/>";
            Reporter.log(html);

        }
    }
    /**
     * 替换字符串
     * @param s 待替换string
     * @return 替换之后的string
     */
    public String escapeString(String s)
    {
        if (s == null)
        {
            return null;
        }

        StringBuilder buffer = new StringBuilder();
        for(int i = 0; i < s.length(); i++)
        {
            buffer.append(escapeChar(s.charAt(i)));
        }
        return buffer.toString();
    }

    /**
     * 将\字符替换为\\
     * @param character 待替换char
     * @return 替换之后的char
     */
    private String escapeChar(char character)
    {
        switch (character)
        {
            case '\\': return "\\\\";
            default: return String.valueOf(character);
        }
    }

这里我把时间工具类获取时间至毫秒的方法放一下,获取至毫秒就不用担心每分钟多次截图了

    public static String getCurrentDateTime2() {
        Calendar calendar = Calendar.getInstance();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        return sdf.format(calendar.getTime());
    }

自动化测试系列(12)—— 修改testng测试报告的输出目录

从reportng的源码中找到了修改testng测试报告的输出目录的地方


修改测试报告的输出目录

把reportng的源码在idea上打开(方便打包),如图
这里写图片描述

打开HTMLReporter.java,把generateReport方法的outputDirectoryName参数赋值成你想输出的文件夹,例如

outputDirectoryName = "surefire-reports";

然后打包替换项目中的reportng.jar包

打包参考(9)


自动化测试平台搭建

自动化测试平台搭建(1)– 初识jenkins

测试代码写好后,尝试通过Jenkins搭建自动化测试平台


Jenkins安装

Jenkins下载地址

如图,选择对应的安装包下载

选择对应的安装包下载

解压后点击安装,根据需要自定义安装路径,其他默认

点击安装

安装完成后打开localhost:8080访问Jenkins首页,可以看到需要填入登录名和密码
首页

初始登录名为admin,初始登录密码在安装目录Jenkins\secrets\initialAdminPassword文件中

如果页面一直显示Please wait while Jenkins is getting ready to work,尝试重启一下jenkins服务再访问,等待数秒即可进入主页

重启

进入主页

新手指引会帮助你安装默认插件和添加用户名密码

这些设置好后,在主页点击新建任务,填写任务名称,选择自由风格,点击确定按钮进入项目配置页面

自由风格

接下来进行项目配置,首先 General 页面需要的话填上项目描述,其他的不选

项目配置

点击 源码管理 ,选择git,输入项目地址(目前展示的是http形式的url,需要用户名和密码,其他形式的url需采用ssh通信,需要git秘钥)
配置git

在项点击添加,填写git的用户名密码并确定
这里写图片描述

如果不报红字说明连接成功

点击 构建触发器 ,选择 轮询 SCM ,填入H/3 * * * *,表示每三分钟轮询一遍在源码管理里设置的代码库,只要往代码库的master分支提交代码,jenkins就会构建一次
这里写图片描述

点击 构建环境 勾上第一行,表示每次构建前删除工作空间(构建时不能打开任何文件,包括文件夹否则就会构建失败)

构建环境

点击 构建 , 点击 添加构建步骤 ,选择 调用顶层 Maven 目标

添加构建步骤

在目标处输入clean install -Dtestng.xml findbugs:findbugs,表示重新生成target,打包和运行testng.xml,最后那个,看名字就知道作用了吧
目标

在这里填一下踩过的一个坑,后期想要在jenkins页面显示测试报告,会发现显示了空白页面或者内容不全,具体原因参考以下文章
Jenkins2.2 firefox和chrome不显示测试报告解决办法

在这里,我们在构建时调用命令清除设置解决,还是在 构建 这里,点击 添加构建步骤 ,选择 Execute system Groovy script

选择

输入命令 System.setProperty(“hudson.model.DirectoryBrowserSupport.CSP”,”“) 清除设置

输入命令

经过上面的设置测试报告就会有内容了,但是可能会出现中文乱码,之前跟着自动化测试系列10修改过reportng.jar的就可以跳过这个步骤了,如果没有,参考以下文章
修改源码进行字符转换

接着点击最后一项 构建后操作 ,点击 添加构建后操作步骤 , 从上往下选好了,首先点击第一个,然后就不用管它了,默认设置就行
选择

接下来选择第三个
选择

点击新增
新增

填写测试报告的位置 target\surefire-reports\html,其他设置默认就好
填写位置

接下来选择 Publish TestNG Results ,然后也不用管它了,默认设置就行

这里写图片描述

最后是发邮件,发邮件有点复杂,留到下一篇


ps:在配置时,如果点击增加步骤发现没有这个选项,则需要去系统配置的插件管理里下载插件,比如
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述


自动化测试平台搭建(2)– Jenkins构建发送邮件配置

上一篇的配置保存好后,下一步就是邮件配置


下载插件

下载发送邮件相关插件

下载插件

系统设置

点击 系统管理

系统管理

进入 系统设置

系统设置

找到 Extended E-mail Notification 进行配置

Extended E-mail Notification

点击高级

邮件英文配置高级

上图中SMTP对应的密码需要到QQ邮箱中获取,登录QQ邮箱 → 点击设置 → 进入账户
进入邮箱账户

往下拉,找到POP3/SMTP服务,点击开启后,可以看到图片上的发送短信提示,根据提示发送短信后,点击 我已发送 ,页面会返回SMTP开通密码
这里写图片描述

同理,找到 邮件通知 进行配置,按照刚刚的步骤把红框中的信息填好即可
邮件通知

构建配置

系统配置好后,就可以配置任务test1了,找到 构建后操作 ,点击 增加构建后操作步骤, 选择倒数第五个

选择

填写需发送的邮箱列表 Recipient List,点击 Advanced settings…

配置

删掉developers,这里的Add Trigger可以添加触发发送邮件动态的构建状态

删掉developers

添加Recipient List,即把developers替换成Recipient List

添加

添加了构建成功后发送

最终形态

最后记得点击保存


自动化测试平台搭建(3)– Jenkins构建结果验收

前两篇把构建配置都设置好了,接下来尝试构建


立即构建

找到test1,点击 立即构建 ,可以看到下方的构建历史创建了一个新的构建进度条

立即构建

等待了数分钟,构建接近尾声的时候,收到了邮件,邮件内容是自定义的html就不看了

收到邮件

结果验收

现在返回jenkins的页面查看,点进最近的一次构建历史,点开 FindBugs Warnings 查看
FindBugs Warnings

点开 TestNG Results 查看

TestNG Results

查看自定义的测试报告

点击 返回到工程 ,可以看到构建的情况在右方被Jenkins自己做成了图表

工程

点开HTML Report

HTML Report1

HTML Report2

自定义的测试报告的定制过程请参考
自动化测试系列
的8-12篇


自动化测试平台搭建(4)– Jenkins构建成功后自动部署

Jenkins项目构建成功后自动部署到远程服务器上


下载插件

下载插件

系统设置

找到 Publish over SSH, 填入红框内容,点击高级

Publish over SSH

勾选用户密码登录,填写密码

填写高级

点击 Test Configuration ,如图,若左边显示Success,表示连接远程服务器成功

test

修改war包名

回到idea,把以下代码加进pom.xml文件夹,保证生成的包名是项目的根路径名称

    <build>
        <!-- 产生的构件的文件名,默认值是${artifactId}-${version}。 -->
        <finalName>yourprojectname</finalName>
    </build>

工程配置

点击进入配置,找到 构建 ,点击增加构建步骤, 选择Send files or excute commands over SSH
Send files or excute commands over SSH

填写如下图

填写

构建项目
配置好后,执行立即构建test1,构建完成后可到服务器查看,是否重新部署了一遍


最后

跳转文末专用

  • 11
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值