基于TestNG+Rest Assured+Allure的接口自动化测试框架

一、前言

当今,“自动化测试”大行其道,其中“接口自动化测试”便是同行们谈得最多的话题之一。了解测试金字塔分层理念的童鞋都清楚,接口自动化测试有以下优点。

  • 投入低,产出高。
  • 比UI自动化更稳定。
  • 比单元测试更接近真实业务。

正因为以上优点,接口自动化测试逐渐成为了业界主流,各种工具/框架层出不穷,比如Postman,Jmeter,Htttpclient,Rest assured,HttpRunnerManager等。

二、背景

此前笔者曾基于Jenkins+Ant+Git+Jmeter搭建过一套接口自动化框架,期间亦针对Jmeter做了许多功能的扩展,比如:生成excle结果文件、数据库断言、自动提交缺陷、自动更新案例执行结果至Testlink等。虽说Jmeter简单易上手,但在笔者看来,其并不是接口自动化测试的首选,其中的原因暂不祥谈,毕竟仁者见仁。
近段时间,笔者一直在思索,学习前辈们优秀的经验,并从公司项目架构出发,搭建了一套基于Jenkins+Maven+Git+TestNG+RestAssured+Allure的持续集成测试框架,相比原先Jmeter的那套,其易用性更好、效率更高、扩展性更强。

三、框架理念

接口自动化测试框架
  • 根据用例模板编写测试用例(包含响应报文断言及接口入参等)。
  • 编写数据库断言文件。
  • 编写测试类。
  • 配置环境及测试用例集。
  • Jenkins选择运行环境及用例集,触发构建。
  • 生成测试报告,邮件通知相关人员。

四、操作步骤

1、编写用例
测试案例模板

用例文件名称与测试类名一致,比如开户的测试类名为OpenAcc,则用例文件名为OpenAcc.xls,用例模板由以下几部分组成。

  • caseNo、testPoint分别与案例管理系统的案例编号、案例标题对应,方便后续同步执行结果。
  • preResult为响应报文断言,目前支持jsonpath、包含断言、不包含断言。
  • YorN为案例执行标志,Y表示执行。
  • tableCheck为数据库断言标志,Y表示进行数据库断言。
  • 其他字段为接口入参,目前支持以下五种供数方式。
    (1)自定义函数供数。引用格式为:__phone()表示生成11位手机号。__idno()表示生成18位身份证号。
    (2)查询数据池供数。引用格式为:${dp.sql(select accountNo from M_account where status = 1)}
    (3)查询数据库供数。引用格式为:${db.sql(select accountNo from M_account_card where status = 1)}
    (4)先接口请求,然后提取响应报文供数。引用格式为:${SendmsgYg.case023.post($.data.code)},表示先以post方式发送SendmsgYg接口请求,然后再提取响应报文的code字段。支持接口之间的多重依赖。
    (5)先接口请求,然后查询数据库/池供数。引用格式为:${SendmsgYg.case023.post.db.sql(select accountNo from M_account_card where status = 1)},表示先以post方式发送SendmsgYg接口请求,然后再查询数据库(db)/数据池(dp)获取数据。
2、编写数据库断言

数据库断言文件名称与测试类名一致,比如开户的测试类名为OpenAcc,则断言文件为OpenAcc.xml。一个接口对应一个数据库断言文件,一个断言文件里可包含多条案例,每条案例可以断言多张表,模板如下。
ps:为了提升效率,后续亦会提供一个生成数据库断言文件小工具。

<?xml version="1.0" encoding="utf-8"?>

<dbCheck dbCheck_name="开户绑卡数据库检查点">
    <caseNo case_no="case085"> <!--案例编号-->
        <table table_name="M_ACCOUNT"> <!--表名-->
            <priKey key_name="ACCOUNT_NO">ACCOUNT_NO</priKey>      <!--主键-->
            <column column_name="CUST_ID">CUST_ID</column>         <!--其他字段的预期结果-->
            <column column_name="MERCHANT_ID">MERCHANT_ID</column> <!--其他字段的预期结果-->
            <column column_name="ACCOUNT_STATUS">1</column>        <!--其他字段的预期结果-->
            <column column_name="ORGAN_NO">0019901</column>        <!--其他字段的预期结果-->
        </table>
    </caseNo>

    <caseNo case_no="case086">
        <table table_name="M_ACCOUNT_CARD">
            <priKey key_name="ACCOUNT_NO">ACCOUNT_NO</priKey>
            <priKey key_name="CARD_NO">CARD_NO</priKey>
            <column column_name="CARD_TYPE">2</column>
            <column column_name="MERCHANT_ID">MERCHANT_ID</column>
            <column column_name="CUST_ID">CUST_ID</column>
            <column column_name="CARD_IMG">CARD_IMG</column>
            <column column_name="OPEN_BANKNAME">NOTNULL</column>
        </table>
    </caseNo>
</dbCheck>

对于未确定的预期结果,使用变量代替,后续编写测试类时再做映射。

3、编写测试类

测试类分为两大类,一是只需响应报文断言,二是需要响应报文及数据库断言。测试人员按照以下模板编写脚本即可。

/*
 *短信发送接口
 * 环境参数在SetUpTearDown 父类定义
 */
@Feature("分类账户改造")
public class SendmsgYg extends SetUpTearDown {

    @Story("发送短信")
    @Test(dataProvider = "dataprovider",
            dataProviderClass = DataProviders.class,
            description = "发送短信")
    public void runCase(String caseMess, String bodyString) throws IOException, SQLException, ClassNotFoundException {

        //发送请求
        Response response = RunCaseJson.runCase(bodyString, "post");

        //只进行响应报文断言
        asserts(caseMess, bodyString, response.asString(),"",null);
    }
}

数据库断言文件中的变量,可通过调用封装的方法取值,比如查数据库、提取响应报文、调用接口等方式。

/*
 *开立分类账户
 * 环境参数在SetUpTearDown 父类定义
 */
@Feature("分类账户改造")
public class OpenYg extends SetUpTearDown {

    @Story("分类账户开户")
    @Test(dataProvider = "dataprovider",
            dataProviderClass = DataProviders.class,
            description = "开户")
    public void runCase(String caseMess, String bodyString) throws IOException, SQLException, ClassNotFoundException {

        //发送请求
        Response response = RunCaseJson.runCase(bodyString, "post");

        //如果需要数据库断言,此处添加断言文件变量的map映射
        //可通过调用封装的方法取值,比如查数据库、提取响应报文、调用接口等方式。
        Map<String, String> map = new HashMap<>();
        //查询数据库获取,取不到值返回""
        String account = DataBaseCRUD.selectData("select accountNo from M_ACCOUNT where status =1");
        //提取响应报文,取不到值返回""
        String custId = RespondAssertForJson.getBuildValue(response.asString(),"$.data.custid");
        //执行SendmsgYg接口的case023案例,然后提取响应报文的merchanId ,取不到值返回""
        String merchanId = RespondAssertForJson.getBuildValue("","${SendmsgYg.case023.post($.data.merchanId)}");

        map.put("ACCOUNT_NO",account);
        map.put("CUST_ID",custId);
        map.put("MERCHANT_ID",merchanId);

        //断言(包含响应报文断言和数据库断言)
        String xmlFileName = this.getClass().getSimpleName(); //数据库断言xml文件名(与类名保持一致)
        asserts(caseMess, bodyString, response.asString(),xmlFileName,map);
    }
}
4、用例集

对于多个suite,可通过suite-files配置。testng.xml文件配置如下。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="IIACCOUNT自动化测试" parallel="classes">

    <listeners>
        <!--失败重跑-->
        <listener class-name="com.iiaccount.listener.FailedRetryListener"/>
    </listeners>

    <test verbose="2" name="IIACCOUNT_YG">

        <classes>
            <class name="com.iiaccout.yiguan.OpenYg"/>
            <class name="com.iiaccout.yiguan.SendmsgYg"/>
        </classes>
    </test>
</suite>
5、Jenkins构建

选择环境及测试用例集,开始构建,构建完成后生成测试报告及日志。也可根据需要设置定时构建,持续进行质量监控,具体的设置方法在笔者的另一篇文章《基于Jmeter的性能测试框架搭建》改进一有提到。

Jenkins构建

Jenkins构建后

Jenkins构建

6、报告分析

在这个注重颜值的世界,allure框架出来的测试报告绝对称得上“报告界的小鲜肉”,笔者在文章《高大上的测试报告-Allure开源框架探索》亦有详细介绍。
测试报告总览包含用例通过率、测试套件、环境、feature、类别、趋势等信息。以下示例截图的案例全部执行失败,所以总览的通过率是0%。

测试报告

类别主要展现失败的用例信息,可根据项目情况自定制报告内容,比如请求报文、响应报文、断言结果等。
测试报告

测试报告

时间刻度展现了每条案例的执行时间。
测试报告

报告更详细的功能可阅览《高大上的测试报告-Allure开源框架探索》一文。

五、框架实现方案

1、工具/框架
  • Jenkins
  • Maven
  • Gitlab
  • TestNG
  • Rest Assured
  • Allure
2、工程目录
工程目录

工程目录

工程目录
3、pom依赖

支持多环境(sit,uat)切换,结合Jenkins使用。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>HFIIACCOUNT</groupId>
    <artifactId>ApiAutoTest</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--通过“-D”引用变量-->
    <properties>
        <aspectj.version>1.8.10</aspectj.version>
        <!-- 解决mvn编译乱码问题-->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <!--外部传参-->
        <xmlFileName></xmlFileName>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>6.11</version>
        </dependency>

        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>rest-assured</artifactId>
            <version>3.1.0</version>
        </dependency>

        <dependency>
            <groupId>ru.yandex.qatools.allure</groupId>
            <artifactId>allure-testng-adaptor</artifactId>
            <version>1.3.6</version>
            <exclusions>
                <exclusion>
                    <groupId>org.testng</groupId>
                    <artifactId>testng</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>io.qameta.allure</groupId>
            <artifactId>allure-testng</artifactId>
            <version>2.0-BETA14</version>
        </dependency>

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

        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.2</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.13</version>
        </dependency>

        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ojdbc14</artifactId>
            <version>10.2.0.4.0</version>
        </dependency>

    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
            </resource>
        </resources>

        <filters>
            <filter>src/main/filters/filter_${env}.properties</filter>
        </filters>

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.20</version>
                <configuration>
                    <argLine>
                        -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
                    </argLine>
                    <!--生成allure-result的目录-->
                    <systemProperties>
                        <property>
                            <name>allure.results.directory</name>
                            <value>./target/allure-results</value>
                        </property>
                    </systemProperties>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.aspectj</groupId>
                        <artifactId>aspectjweaver</artifactId>
                        <version>${aspectj.version}</version>
                    </dependency>
                </dependencies>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.19</version>
                <configuration>
                    <suiteXmlFiles>
                        <!--该文件位于工程根目录时,直接填写名字,其它位置要加上路径-->
                        <!--suiteXmlFile>src/main/resources/testngXml/${xmlFileName}</suiteXmlFile-->
                        <suiteXmlFile>${project.basedir}/target/classes/testngXml/${xmlFileName}</suiteXmlFile>
                    </suiteXmlFiles>

                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>

            <!--增加此配置,防止编译后xls文件乱码-->
            <!--Maven resources 插件会对文本资源文件进行转码,但是它无法区分文件是否是纯文本文件还是二进制文件.于是二进制文件在部署过程中也就被转码了.-->
            <!--https://blog.csdn.net/xdxieshaa/article/details/54906476-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <nonFilteredFileExtensions>
                        <!-- 不对xls进行转码 -->
                        <nonFilteredFileExtension>xls</nonFilteredFileExtension>
                    </nonFilteredFileExtensions>
                </configuration>
            </plugin>

        </plugins>
    </build>

    <!--通过“-P”引用变量-->
    <profiles>
        <!-- uat测试环境 -->
        <profile>
            <id>uat</id>
            <properties>
                <env>uat</env>
            </properties>

        </profile>

        <!-- sit测试环境 -->
        <profile>
            <id>sit</id>
            <properties>
                <env>sit</env>
            </properties>
            <activation>
                <activeByDefault>true</activeByDefault><!--默认启用的是sit环境配置-->
            </activation>
        </profile>

    </profiles>

</project>
4、实现思路
  • 将每个接口的案例读取到Map保存,其中key值为每条案例的caseNo+testPoint+preResult+YorN+tableCheck拼装(也是一个map),value值为剩余字段(接口入参)的拼装,即接口请求的bodyString。


    案例模板
public static void getMap(Sheet sheet, int cols, int row, String pubArgs){

        for (int col = 0; col < cols; col++) {

            String cellKey = sheet.getCell(col, 0).getContents();//表头
            String cellValue = sheet.getCell(col, row).getContents();//值
            if (col >= 5) {
                //appid,api,version属于公共入参,公共入参字段在PublicArgs.properties文件进行配置
                // getBuildValue(value1,value2)方法用于转换${}或者函数为对应的值
                if (pubArgs.toLowerCase().contains(cellKey.toLowerCase().trim())) {
                    bodyMap.put(cellKey, RespondAssertForJson.getBuildValue("", sheet.getCell(col, row).getContents()));
                } else {
                    dataMap.put(cellKey, RespondAssertForJson.getBuildValue("", sheet.getCell(col, row).getContents()));
                }
            } else {
                caseMessMap.put(cellKey, cellValue);
            }
        }
        bodyMap.put("data", dataMap);
        map.put(new Gson().toJson(caseMessMap), new Gson().toJson(bodyMap));
    }
  • 对于案例中的自定义函数或供数变量,封装了方法RespondAssertForJson.getBuildValue(var1,var2)进行转换,目前暂支持前文提到的五种供数方式,后续可根据需要进行扩展。
    /**
     * 支持json串转换
     * 支持自定义函数的转换
     * 支持${}变量转换
     *
     * @param sourchJson
     * @param key
     * @return
     */
    public static String getBuildValue(String sourchJson, String key) {
        key = key.trim();
        Matcher funMatch = funPattern.matcher(key);
        Matcher replacePattern = replaceParamPattern.matcher(key);

        log.info("key is:" + key);
        try{
            if (key.startsWith("$.")) {// jsonpath
                key = JSONPath.read(sourchJson, key).toString();  //jsonpath读取对应的值
                log.info("key start with $.,value is:" + key);
            } else if (funMatch.find()) {//函数

                String args = funMatch.group(2);  //函数入参
                log.info("key is a function,args is:" + args);
                String[] argArr = args.split(",");
                for (int index = 0; index < argArr.length; index++) {
                    String arg = argArr[index];
                    if (arg.startsWith("$.")) {  //函数入参亦支持json格式
                        argArr[index] = JSONPath.read(sourchJson, arg).toString();
                    }
                }
                log.info("argArr:"+argArr.length);
                String value = FunctionUtil.getValue(funMatch.group(1), argArr);  //函数名不区分大小写,返回函数值
                log.info("函数名 funMatch.group(1):" + funMatch.group(1));
                key = StringUtil.replaceFirst(key, funMatch.group(), value);  //把函数替换为生成的值
                log.info("函数 funMatch.group():" + funMatch.group());
                log.info("key is a function,value is:" + key);
            } else if (replacePattern.find()) {//${}变量
                log.info("${}变量体:"+replacePattern.group(1));
                String var = replacePattern.group(1).trim();

                String value1 = DataBuilders.dataprovide(var);
                key = StringUtil.replaceFirst(key, replacePattern.group(), value1);  //把变量替换为生成的值
                log.info("key is a ${} pattern,value is:" + key);
            }
            return key;

        }catch(Exception e){

            log.info(e.getMessage());
            return  null;
        }
    }
  • 通过TestNG的@DataProvider获取Map的案例信息,进行接口请求。约定测试案例名称为:测试类名.xls
    /*
     *map包含两部分json,key为caseNo等信息,value为接口入参
     */
    @DataProvider(name = "dataprovider")
    public static Object[][] dataP(Method method) throws IOException, BiffException, URISyntaxException {
        String className = method.getDeclaringClass().getSimpleName(); //获取类名
        String caseFileName = className+".xls"; //测试案例名称为:类名.xls

        Object[][] objects = null;
        Map<String,String> map = new HashMap<String, String>();
        map = AssembledMessForJson.assembleMess(caseFileName,""); //""表示读取所有的为Y的case
        objects = new Object[map.size()][2];
        int i=0;
        for(Map.Entry<String, String> entry : map.entrySet()){
            objects[i][0] = entry.getKey();
            objects[i][1] = entry.getValue();
            i++;
        }
        map.clear();  //需清空map,否则案例会不断叠加 2018-10-19 add by lrb
        return objects;
    }
  • 所有的测试类都继承SetUpTearDown父类(详见前文例子),父类中使用@BeforeSuite,@BeforeClass,@AfterSuite等注解来进行参数准备或数据清理。
    父类部分方法:
//环境配置
    @BeforeClass
    public void envSetUp() {
        try {
            String system = "env.properties";    //环境由filter配置
            RestAssured.baseURI = new GetFileMess().getValue("baseURI", system);
            RestAssured.basePath = new GetFileMess().getValue("basePath", system);
            RestAssured.port = Integer.parseInt(new GetFileMess().getValue("port", system));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
  • 对每条案例进行断言,目前暂支持响应报文断言和数据库断言,后续可根据需要扩展。
    响应报文断言:将案例文件的preResult字段(预期结果)与接口响应报文进行比对,预期结果暂支持jsonpath,包含,不包含断言,引用格式为:$.status=200;__contain(lrr);__notcontain(tomandy),$.cdoe=1000,多个断言使用英文分号隔开。
    数据库断言:将测试类名.xml断言文件中的预期结果与数据库实际结果进行比对,各个字段的比对结果在测试报告中展现。
  • 对于抛异常的案例,失败重试。
/*
 *实现IAnnotationTransformer接口,修改@Test的retryAnalyzer属性
 */
public class FailedRetryListener implements IAnnotationTransformer {
    public void transform(ITestAnnotation iTestAnnotation, Class aClass, Constructor constructor, Method method) {
        {
            IRetryAnalyzer retry = iTestAnnotation.getRetryAnalyzer();
            if (retry == null) {
                iTestAnnotation.setRetryAnalyzer(FailedRetry.class);
            }
        }
    }
}
  • 测试报告展现请求报文,响应报文,断言结果等信息。
/*
 *测试报告展现
 */
public class TestStep {

    public static void requestAndRespondBody(String URL, String Body,String Respond){
        requestBody(URL,Body);
        respondBody(Respond);
    }

    @Attachment("请求报文")
    public static String requestBody(String URL, String body) {

        //格式化json串
        boolean prettyFormat = true; //格式化输出
        JSONObject jsonObject = JSONObject.parseObject(body);
        String str = JSONObject.toJSONString(jsonObject,prettyFormat);

        //报告展现请求报文
        return URL+"\n"+str;
    }

    @Attachment("响应报文")
    public static String respondBody(String respond) {
        //报告展现响应报文
        return respond;
    }

    @Attachment("数据库断言结果")
    public static StringBuffer databaseAssertResult(StringBuffer assertResult){
        //报告展现数据库断言结果
        return assertResult;
    }

    @Attachment("响应报文断言结果")
    public static StringBuffer assertRespond(StringBuffer assertResult){
        //报告展现数据库断言结果
        return assertResult;
    }
}
  • 日志采集,log4j.properties配置如下。
log4j.rootLogger=INFO, stdout, D , E

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss}  [ %C.%M(%L) ] - [ %p ]  %m%n

# 文件达到指定大小的时候产生一个新的文件
log4j.appender.D=org.apache.log4j.DailyRollingFileAppender
# TODO 部署时,修改为指定路径
log4j.appender.D.File=logs/apiAutoTest_debug.log  
log4j.appender.D.Append = true
# 输出DEBUG级别以上的日志
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout=org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss}  [ %C.%M(%L) ] - [ %p ]  %m%n

### 保存异常信息到单独文件 ###
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
## 异常日志文件名
# TODO 部署时,修改为指定路径
log4j.appender.E.File = logs/apiAutoTest_error.log
log4j.appender.E.Append = true
## 只输出ERROR级别以上的日志!!!
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %C.%M(%L) ] - [ %p ]  %m%n
5、Jenkins配置
5.1、插件安装

可在线安装插件或下载到本地安装,下载地址

  • Maven Integration
  • Allure
  • TestNG Results
  • Parameterized Trigger
  • Email Extension
5.2、Jenkins配置

新建一个maven项目。


新建项目

全局工具配置(Jenkins-系统管理-全局工具配置)。


全局工具配置

全局工具配置

参数化构建过程配置,选择【choice Parameter】,配置的Name需与pom.xml文件的变量名一致,字典根据源码中实际用途定义。此处需注意,profiles定义的属性通过“-P”引用,下文会提及。
参数配置

参数配置

源码管理配置,此处根据源码管理工具配置。


源码管理配置

build配置,此处通过clean test -P${env} -DxmlFileName=${xmlFileName}来把参数传给pom,-P和-D的区别可百度。


build配置

配置构建后操作,即测试报告生成路径。


构建后操作

邮件配置操作自行百度。
至此,Jenkins配置工作全部搞掂,接下来构建测试即可。
6、构建测试
Jenkins构建

六、关注点

  • 整套框架使用的是ojdbc,Maven配置ojdbc依赖需做特殊处理,详情戳此链接
  • 编译后,测试案例.xls文件乱码。原因是Maven resources 插件会对文本资源文件进行转码,但是它无法区分文件是纯文本文件还是二进制文件,二进制文件在部署过程中也就被转码了。需要在pom文件增加如下配置。
            <!--增加此配置,防止编译后xls文件乱码-->
            <!--Maven resources 插件会对文本资源文件进行转码,但是它无法区分文件是否是纯文本文件还是二进制文件.于是二进制文件在部署过程中也就被转码了.-->
            <!--https://blog.csdn.net/xdxieshaa/article/details/54906476-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <nonFilteredFileExtensions>
                        <!-- 不对xls进行转码 -->
                        <nonFilteredFileExtension>xls</nonFilteredFileExtension>
                    </nonFilteredFileExtensions>
                </configuration>
            </plugin>
  • mvn编译后出现乱码。需要在pom文件增加如下配置。
<properties>
        <aspectj.version>1.8.10</aspectj.version>
        <!-- 解决mvn编译乱码问题-->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
  • log4j.properties配置的日志路径为项目路径下,不便于长期管理,部署时应在另指定存储路径。
  • Jenkins控制台输出乱码,解决方法如下。
    (1)、设置jenkins所在服务器环境变量,添加系统变量。
    变量名:JAVA_TOOL_OPTIONS
    变量值:-Dfile.encoding=UTF8
    (2)、修改Tomcat配置,进入apache_tomcat/conf文件夹下,编辑server.xml,在Connector port="8080"后面加入useBodyEncodingForURI="true"
    <Connector port="8080" useBodyEncodingForURI="true" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
    (3)、启动tomcat,运行jenkins,进入系统管理→系统设置,在全局属性处勾选Environment variables,添加编码环境变量LANG=zh_CN.UTF-8
  • 脚本统一放到Git管理,暂定代码分支管理规则如下。
    master作为稳定的分支,测试人员在dev分支进行脚本编写,执行无误再合并master分支,然后触发Jenkins自动构建。
    新建dev分支

    新建dev分支并Checkout

    后续在dev分支进行脚本编写,脚本调试无误后再push到远程的dev分支,然后合并到master分支。
    git Commit

    git push

    切换到master分支,然后合并dev分支内容,然后再push到远程仓库。
    分支合并

    更详细的操作可参考idea中git分支的使用
  • 如果jenkins的服务器部署在内网,而自己公司又没有专门的maven远程仓库,第一次构建可能无法链接外网下载依赖包导致报错,可通过以下方式解决。
    (1)先在本机链接外网下载所有的依赖包,然后再拷贝本机用户目录的maven仓库(C:\Users\lenovo.m2)到jenkins服务器对应的用户目录下。
    (2)修改maven_home conf目录下的settings.xml文件,增加localRepository。
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
  <!-- localRepository
   | The path to the local repository maven will use to store artifacts.
   |
   | Default: ${user.home}/.m2/repository
  <localRepository>/path/to/local/repo</localRepository>
  -->
<localRepository>C:/Users/wtapp01/.m2/repository</localRepository>

七、框架扩展

上述框架目前仅局限于测试端,严格意义上来说并不算真正的持续集成,后续再完善以下几点。

  • 增加缺陷确认,提交缺陷,然后同步案例管理系统。
  • 与单元测试打通。
  • 增加代码覆盖率检查。
  • 搭建统一供数平台,通过restful api访问。

八、github

https://github.com/Tomandy08/ApiAutoTest

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容