一评架、前言
當今,“自動化測試”大行其道炕泳,其中“接口自動化測試”便是同行們談得最多的話題之一纵诞。了解測試金字塔分層理念的童鞋都清楚,接口自動化測試有以下優(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的性能測試框架搭建》改進一有提到急凰。
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”引用,下文會提及就斤。
源碼管理配置悍募,此處根據(jù)源碼管理工具配置。
build配置洋机,此處通過clean test -P${env} -DxmlFileName=${xmlFileName}來把參數(shù)傳給pom坠宴,-P和-D的區(qū)別可百度。
配置構(gòu)建后操作绷旗,即測試報告生成路徑喜鼓。
郵件配置操作自行百度。
至此衔肢,Jenkins配置工作全部搞掂庄岖,接下來構(gòu)建測試即可。
6角骤、構(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)建。
后續(xù)在dev分支進行腳本編寫融痛,腳本調(diào)試無誤后再push到遠程的dev分支壶笼,然后合并到master分支。
切換到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訪問奢人。