基于TestNG+Rest Assured+Allure的接口自動化測試框架

一评架、前言

當今,“自動化測試”大行其道炕泳,其中“接口自動化測試”便是同行們談得最多的話題之一纵诞。了解測試金字塔分層理念的童鞋都清楚,接口自動化測試有以下優(yōu)點培遵。

  • 投入低浙芙,產(chǎn)出高。
  • 比UI自動化更穩(wěn)定籽腕。
  • 比單元測試更接近真實業(yè)務(wù)嗡呼。

正因為以上優(yōu)點,接口自動化測試逐漸成為了業(yè)界主流皇耗,各種工具/框架層出不窮桩皿,比如Postman,Jmeter,Htttpclient,Rest assured,HttpRunnerManager等榜揖。

二颊咬、背景

此前筆者曾基于Jenkins+Ant+Git+Jmeter搭建過一套接口自動化框架肪笋,期間亦針對Jmeter做了許多功能的擴展,比如:生成excle結(jié)果文件呜袁、數(shù)據(jù)庫斷言敌买、自動提交缺陷、自動更新案例執(zhí)行結(jié)果至Testlink等傅寡。雖說Jmeter簡單易上手放妈,但在筆者看來,其并不是接口自動化測試的首選荐操,其中的原因暫不祥談芜抒,畢竟仁者見仁。
近段時間托启,筆者一直在思索宅倒,學(xué)習前輩們優(yōu)秀的經(jīng)驗,并從公司項目架構(gòu)出發(fā)屯耸,搭建了一套基于Jenkins+Maven+Git+TestNG+RestAssured+Allure的持續(xù)集成測試框架拐迁,相比原先Jmeter的那套蹭劈,其易用性更好、效率更高线召、擴展性更強铺韧。

三、框架理念

接口自動化測試框架
  • 根據(jù)用例模板編寫測試用例(包含響應(yīng)報文斷言及接口入?yún)⒌龋?/li>
  • 編寫數(shù)據(jù)庫斷言文件缓淹。
  • 編寫測試類哈打。
  • 配置環(huán)境及測試用例集。
  • Jenkins選擇運行環(huán)境及用例集讯壶,觸發(fā)構(gòu)建料仗。
  • 生成測試報告,郵件通知相關(guān)人員伏蚊。

四立轧、操作步驟

1、編寫用例
測試案例模板

用例文件名稱與測試類名一致躏吊,比如開戶的測試類名為OpenAcc氛改,則用例文件名為OpenAcc.xls,用例模板由以下幾部分組成颜阐。

  • caseNo平窘、testPoint分別與案例管理系統(tǒng)的案例編號、案例標題對應(yīng)凳怨,方便后續(xù)同步執(zhí)行結(jié)果。
  • preResult為響應(yīng)報文斷言是鬼,目前支持jsonpath肤舞、包含斷言、不包含斷言均蜜。
  • YorN為案例執(zhí)行標志李剖,Y表示執(zhí)行。
  • tableCheck為數(shù)據(jù)庫斷言標志囤耳,Y表示進行數(shù)據(jù)庫斷言篙顺。
  • 其他字段為接口入?yún)ⅲ壳爸С忠韵挛宸N供數(shù)方式充择。
    (1)自定義函數(shù)供數(shù)德玫。引用格式為:__phone()表示生成11位手機號。__idno()表示生成18位身份證號椎麦。
    (2)查詢數(shù)據(jù)池供數(shù)宰僧。引用格式為:${dp.sql(select accountNo from M_account where status = 1)}
    (3)查詢數(shù)據(jù)庫供數(shù)。引用格式為:${db.sql(select accountNo from M_account_card where status = 1)}
    (4)先接口請求观挎,然后提取響應(yīng)報文供數(shù)琴儿。引用格式為:${SendmsgYg.case023.post($.data.code)}段化,表示先以post方式發(fā)送SendmsgYg接口請求,然后再提取響應(yīng)報文的code字段造成。支持接口之間的多重依賴显熏。
    (5)先接口請求,然后查詢數(shù)據(jù)庫/池供數(shù)晒屎。引用格式為:${SendmsgYg.case023.post.db.sql(select accountNo from M_account_card where status = 1)}佃延,表示先以post方式發(fā)送SendmsgYg接口請求,然后再查詢數(shù)據(jù)庫(db)/數(shù)據(jù)池(dp)獲取數(shù)據(jù)夷磕。
2履肃、編寫數(shù)據(jù)庫斷言

數(shù)據(jù)庫斷言文件名稱與測試類名一致,比如開戶的測試類名為OpenAcc坐桩,則斷言文件為OpenAcc.xml尺棋。一個接口對應(yīng)一個數(shù)據(jù)庫斷言文件,一個斷言文件里可包含多條案例绵跷,每條案例可以斷言多張表膘螟,模板如下。
ps:為了提升效率碾局,后續(xù)亦會提供一個生成數(shù)據(jù)庫斷言文件小工具荆残。

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

<dbCheck dbCheck_name="開戶綁卡數(shù)據(jù)庫檢查點">
    <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>         <!--其他字段的預(yù)期結(jié)果-->
            <column column_name="MERCHANT_ID">MERCHANT_ID</column> <!--其他字段的預(yù)期結(jié)果-->
            <column column_name="ACCOUNT_STATUS">1</column>        <!--其他字段的預(yù)期結(jié)果-->
            <column column_name="ORGAN_NO">0019901</column>        <!--其他字段的預(yù)期結(jié)果-->
        </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>

對于未確定的預(yù)期結(jié)果,使用變量代替净当,后續(xù)編寫測試類時再做映射内斯。

3、編寫測試類

測試類分為兩大類像啼,一是只需響應(yīng)報文斷言俘闯,二是需要響應(yīng)報文及數(shù)據(jù)庫斷言。測試人員按照以下模板編寫腳本即可忽冻。

/*
 *短信發(fā)送接口
 * 環(huán)境參數(shù)在SetUpTearDown 父類定義
 */
@Feature("分類賬戶改造")
public class SendmsgYg extends SetUpTearDown {

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

        //發(fā)送請求
        Response response = RunCaseJson.runCase(bodyString, "post");

        //只進行響應(yīng)報文斷言
        asserts(caseMess, bodyString, response.asString(),"",null);
    }
}

數(shù)據(jù)庫斷言文件中的變量真朗,可通過調(diào)用封裝的方法取值,比如查數(shù)據(jù)庫僧诚、提取響應(yīng)報文遮婶、調(diào)用接口等方式。

/*
 *開立分類賬戶
 * 環(huán)境參數(shù)在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 {

        //發(fā)送請求
        Response response = RunCaseJson.runCase(bodyString, "post");

        //如果需要數(shù)據(jù)庫斷言湖笨,此處添加斷言文件變量的map映射
        //可通過調(diào)用封裝的方法取值旗扑,比如查數(shù)據(jù)庫、提取響應(yīng)報文赶么、調(diào)用接口等方式肩豁。
        Map<String, String> map = new HashMap<>();
        //查詢數(shù)據(jù)庫獲取,取不到值返回""
        String account = DataBaseCRUD.selectData("select accountNo from M_ACCOUNT where status =1");
        //提取響應(yīng)報文,取不到值返回""
        String custId = RespondAssertForJson.getBuildValue(response.asString(),"$.data.custid");
        //執(zhí)行SendmsgYg接口的case023案例清钥,然后提取響應(yīng)報文的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);

        //斷言(包含響應(yīng)報文斷言和數(shù)據(jù)庫斷言)
        String xmlFileName = this.getClass().getSimpleName(); //數(shù)據(jù)庫斷言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構(gòu)建

選擇環(huán)境及測試用例集谜叹,開始構(gòu)建,構(gòu)建完成后生成測試報告及日志搬葬。也可根據(jù)需要設(shè)置定時構(gòu)建荷腊,持續(xù)進行質(zhì)量監(jiān)控,具體的設(shè)置方法在筆者的另一篇文章《基于Jmeter的性能測試框架搭建》改進一有提到急凰。

Jenkins構(gòu)建

Jenkins構(gòu)建后

Jenkins構(gòu)建

6女仰、報告分析

在這個注重顏值的世界,allure框架出來的測試報告絕對稱得上“報告界的小鮮肉”抡锈,筆者在文章《高大上的測試報告-Allure開源框架探索》亦有詳細介紹疾忍。
測試報告總覽包含用例通過率、測試套件床三、環(huán)境一罩、feature、類別撇簿、趨勢等信息聂渊。以下示例截圖的案例全部執(zhí)行失敗,所以總覽的通過率是0%补疑。

測試報告

類別主要展現(xiàn)失敗的用例信息歧沪,可根據(jù)項目情況自定制報告內(nèi)容,比如請求報文莲组、響應(yīng)報文、斷言結(jié)果等暖夭。
測試報告

測試報告

時間刻度展現(xiàn)了每條案例的執(zhí)行時間锹杈。
測試報告

報告更詳細的功能可閱覽《高大上的測試報告-Allure開源框架探索》一文。

五迈着、框架實現(xiàn)方案

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

工程目錄

工程目錄
3裕菠、pom依賴

支持多環(huán)境(sit,uat)切換咬清,結(jié)合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 插件會對文本資源文件進行轉(zhuǎn)碼,但是它無法區(qū)分文件是否是純文本文件還是二進制文件.于是二進制文件在部署過程中也就被轉(zhuǎn)碼了.-->
            <!--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進行轉(zhuǎn)碼 -->
                        <nonFilteredFileExtension>xls</nonFilteredFileExtension>
                    </nonFilteredFileExtensions>
                </configuration>
            </plugin>

        </plugins>
    </build>

    <!--通過“-P”引用變量-->
    <profiles>
        <!-- uat測試環(huán)境 -->
        <profile>
            <id>uat</id>
            <properties>
                <env>uat</env>
            </properties>

        </profile>

        <!-- sit測試環(huán)境 -->
        <profile>
            <id>sit</id>
            <properties>
                <env>sit</env>
            </properties>
            <activation>
                <activeByDefault>true</activeByDefault><!--默認啟用的是sit環(huán)境配置-->
            </activation>
        </profile>

    </profiles>

</project>
4、實現(xiàn)思路
  • 將每個接口的案例讀取到Map保存掘剪,其中key值為每條案例的caseNo+testPoint+preResult+YorN+tableCheck拼裝(也是一個map)平委,value值為剩余字段(接口入?yún)ⅲ┑钠囱b,即接口請求的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屬于公共入?yún)?公共入?yún)⒆侄卧赑ublicArgs.properties文件進行配置
                // getBuildValue(value1,value2)方法用于轉(zhuǎn)換${}或者函數(shù)為對應(yīng)的值
                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));
    }
  • 對于案例中的自定義函數(shù)或供數(shù)變量廉赔,封裝了方法RespondAssertForJson.getBuildValue(var1,var2)進行轉(zhuǎn)換,目前暫支持前文提到的五種供數(shù)方式匾鸥,后續(xù)可根據(jù)需要進行擴展蜡塌。
    /**
     * 支持json串轉(zhuǎn)換
     * 支持自定義函數(shù)的轉(zhuǎn)換
     * 支持${}變量轉(zhuǎn)換
     *
     * @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讀取對應(yīng)的值
                log.info("key start with $.,value is:" + key);
            } else if (funMatch.find()) {//函數(shù)

                String args = funMatch.group(2);  //函數(shù)入?yún)?                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("$.")) {  //函數(shù)入?yún)⒁嘀С謏son格式
                        argArr[index] = JSONPath.read(sourchJson, arg).toString();
                    }
                }
                log.info("argArr:"+argArr.length);
                String value = FunctionUtil.getValue(funMatch.group(1), argArr);  //函數(shù)名不區(qū)分大小寫,返回函數(shù)值
                log.info("函數(shù)名 funMatch.group(1):" + funMatch.group(1));
                key = StringUtil.replaceFirst(key, funMatch.group(), value);  //把函數(shù)替換為生成的值
                log.info("函數(shù) 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為接口入?yún)?     */
    @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等注解來進行參數(shù)準備或數(shù)據(jù)清理。
    父類部分方法:
//環(huán)境配置
    @BeforeClass
    public void envSetUp() {
        try {
            String system = "env.properties";    //環(huán)境由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();
        }
    }
  • 對每條案例進行斷言账忘,目前暫支持響應(yīng)報文斷言和數(shù)據(jù)庫斷言志膀,后續(xù)可根據(jù)需要擴展。
    響應(yīng)報文斷言:將案例文件的preResult字段(預(yù)期結(jié)果)與接口響應(yīng)報文進行比對鳖擒,預(yù)期結(jié)果暫支持jsonpath溉浙,包含,不包含斷言蒋荚,引用格式為:$.status=200;__contain(lrr)戳稽;__notcontain(tomandy),$.cdoe=1000,多個斷言使用英文分號隔開期升。
    數(shù)據(jù)庫斷言:將測試類名.xml斷言文件中的預(yù)期結(jié)果與數(shù)據(jù)庫實際結(jié)果進行比對惊奇,各個字段的比對結(jié)果在測試報告中展現(xiàn)。
  • 對于拋異常的案例播赁,失敗重試颂郎。
/*
 *實現(xiàn)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);
            }
        }
    }
}
  • 測試報告展現(xiàn)請求報文容为,響應(yīng)報文乓序,斷言結(jié)果等信息寺酪。
/*
 *測試報告展現(xiàn)
 */
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);

        //報告展現(xiàn)請求報文
        return URL+"\n"+str;
    }

    @Attachment("響應(yīng)報文")
    public static String respondBody(String respond) {
        //報告展現(xiàn)響應(yīng)報文
        return respond;
    }

    @Attachment("數(shù)據(jù)庫斷言結(jié)果")
    public static StringBuffer databaseAssertResult(StringBuffer assertResult){
        //報告展現(xiàn)數(shù)據(jù)庫斷言結(jié)果
        return assertResult;
    }

    @Attachment("響應(yīng)報文斷言結(jié)果")
    public static StringBuffer assertRespond(StringBuffer assertResult){
        //報告展現(xiàn)數(shù)據(jù)庫斷言結(jié)果
        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

# 文件達到指定大小的時候產(chǎ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-系統(tǒng)管理-全局工具配置)坛猪。


全局工具配置

全局工具配置

參數(shù)化構(gòu)建過程配置脖阵,選擇【choice Parameter】,配置的Name需與pom.xml文件的變量名一致,字典根據(jù)源碼中實際用途定義墅茉。此處需注意命黔,profiles定義的屬性通過“-P”引用,下文會提及就斤。
參數(shù)配置

參數(shù)配置

源碼管理配置悍募,此處根據(jù)源碼管理工具配置。


源碼管理配置

build配置洋机,此處通過clean test -P${env} -DxmlFileName=${xmlFileName}來把參數(shù)傳給pom坠宴,-P和-D的區(qū)別可百度。


build配置

配置構(gòu)建后操作绷旗,即測試報告生成路徑喜鼓。


構(gòu)建后操作

郵件配置操作自行百度。
至此衔肢,Jenkins配置工作全部搞掂庄岖,接下來構(gòu)建測試即可。
6角骤、構(gòu)建測試
Jenkins構(gòu)建

六隅忿、關(guān)注點

  • 整套框架使用的是ojdbc,Maven配置ojdbc依賴需做特殊處理邦尊,詳情戳此鏈接硼控。
  • 編譯后,測試案例.xls文件亂碼胳赌。原因是Maven resources 插件會對文本資源文件進行轉(zhuǎn)碼,但是它無法區(qū)分文件是純文本文件還是二進制文件,二進制文件在部署過程中也就被轉(zhuǎn)碼了匙隔。需要在pom文件增加如下配置疑苫。
            <!--增加此配置,防止編譯后xls文件亂碼-->
            <!--Maven resources 插件會對文本資源文件進行轉(zhuǎn)碼,但是它無法區(qū)分文件是否是純文本文件還是二進制文件.于是二進制文件在部署過程中也就被轉(zhuǎn)碼了.-->
            <!--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進行轉(zhuǎn)碼 -->
                        <nonFilteredFileExtension>xls</nonFilteredFileExtension>
                    </nonFilteredFileExtensions>
                </configuration>
            </plugin>
  • mvn編譯后出現(xiàn)亂碼。需要在pom文件增加如下配置捍掺。
<properties>
        <aspectj.version>1.8.10</aspectj.version>
        <!-- 解決mvn編譯亂碼問題-->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
  • log4j.properties配置的日志路徑為項目路徑下撼短,不便于長期管理,部署時應(yīng)在另指定存儲路徑挺勿。
  • Jenkins控制臺輸出亂碼曲横,解決方法如下。
    (1)不瓶、設(shè)置jenkins所在服務(wù)器環(huán)境變量禾嫉,添加系統(tǒng)變量。
    變量名: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凛篙,進入系統(tǒng)管理→系統(tǒng)設(shè)置黍匾,在全局屬性處勾選Environment variables,添加編碼環(huán)境變量LANG=zh_CN.UTF-8
  • 腳本統(tǒng)一放到Git管理呛梆,暫定代碼分支管理規(guī)則如下锐涯。
    master作為穩(wěn)定的分支,測試人員在dev分支進行腳本編寫削彬,執(zhí)行無誤再合并master分支全庸,然后觸發(fā)Jenkins自動構(gòu)建。
    新建dev分支

    新建dev分支并Checkout

    后續(xù)在dev分支進行腳本編寫融痛,腳本調(diào)試無誤后再push到遠程的dev分支壶笼,然后合并到master分支。
    git Commit

    git push

    切換到master分支雁刷,然后合并dev分支內(nèi)容覆劈,然后再push到遠程倉庫。
    分支合并

    更詳細的操作可參考idea中g(shù)it分支的使用沛励。
  • 如果jenkins的服務(wù)器部署在內(nèi)網(wǎng)责语,而自己公司又沒有專門的maven遠程倉庫,第一次構(gòu)建可能無法鏈接外網(wǎng)下載依賴包導(dǎo)致報錯目派,可通過以下方式解決坤候。
    (1)先在本機鏈接外網(wǎng)下載所有的依賴包,然后再拷貝本機用戶目錄的maven倉庫(C:\Users\lenovo.m2)到j(luò)enkins服務(wù)器對應(yīng)的用戶目錄下企蹭。
    (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>

七、框架擴展

上述框架目前僅局限于測試端徒河,嚴格意義上來說并不算真正的持續(xù)集成系馆,后續(xù)再完善以下幾點。

  • 增加缺陷確認顽照,提交缺陷由蘑,然后同步案例管理系統(tǒng)。
  • 與單元測試打通代兵。
  • 增加代碼覆蓋率檢查尼酿。
  • 搭建統(tǒng)一供數(shù)平臺,通過restful api訪問奢人。

八谓媒、github

https://github.com/Tomandy08/ApiAutoTest

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末何乎,一起剝皮案震驚了整個濱河市句惯,隨后出現(xiàn)的幾起案子抢野,更是在濱河造成了極大的恐慌,老刑警劉巖恃轩,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異宏所,居然都是意外死亡酥艳,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門爬骤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來充石,“玉大人,你說我怎么就攤上這事霞玄『斩” “怎么了浓镜?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長劲厌。 經(jīng)常有香客問我,道長听隐,這世上最難降的妖魔是什么补鼻? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮雅任,結(jié)果婚禮上风范,老公的妹妹穿的比我還像新娘。我一直安慰自己沪么,他們只是感情好硼婿,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著禽车,像睡著了一般寇漫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上殉摔,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天州胳,我揣著相機與錄音,去河邊找鬼逸月。 笑死栓撞,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的碗硬。 我是一名探鬼主播瓤湘,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼恩尾!你這毒婦竟也來了弛说?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤特笋,失蹤者是張志新(化名)和其女友劉穎剃浇,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體猎物,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡虎囚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蔫磨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片淘讥。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖堤如,靈堂內(nèi)的尸體忽然破棺而出蒲列,到底是詐尸還是另有隱情窒朋,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布蝗岖,位于F島的核電站侥猩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏抵赢。R本人自食惡果不足惜欺劳,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望铅鲤。 院中可真熱鬧划提,春花似錦、人聲如沸邢享。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽骇塘。三九已至伊履,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間绪爸,已是汗流浹背湾碎。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留奠货,地道東北人介褥。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像递惋,于是被迫代替她去往敵國和親柔滔。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容

  • what-接口是什么萍虽? 在計算機中睛廊,接口是計算機系統(tǒng)中兩個獨立的部件進行信息交換的共享邊界。舉個例子杉编,我提供加法的...
    我為峰2014閱讀 12,142評論 3 27
  • 非常認可這句話:自動化測試是為了提高效率超全,測試腳本要易維護,不能讓測試腳本變成另一種技術(shù)債務(wù)邓馒,不能為了自動化測試而...
    Kewings閱讀 8,016評論 0 10
  • 前言 如果有測試大佬發(fā)現(xiàn)內(nèi)容不對嘶朱,歡迎指正,我會及時修改光酣。 大多數(shù)的iOS App(沒有持續(xù)集成)迭代流程是這樣的...
    默默_David閱讀 1,669評論 0 4
  • 接口自動化測試一體式解決方案 前戲叨逼叨:測試多年工作經(jīng)驗疏遏,很少有寫文章、博客之類的東西。其實我這人不愛去寫博客之...
    J先生有點兒屁閱讀 15,037評論 20 104
  • 五一聚會的時候聽朋友講起他老板的故事财异。 88年的創(chuàng)一代倘零,從機修出身到客戶接待到銷售顧問到開始創(chuàng)業(yè)然后創(chuàng)業(yè)成功。每天...
    霽萱閱讀 210評論 0 1