一、前言
當今,“自動化測試”大行其道,其中“接口自動化測試”便是同行們談得最多的話題之一。瞭解測試金字塔分層理念的童鞋都清楚,接口自動化測試有以下優點。
- 投入低,產出高。
- 比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的性能測試框架搭建》改進一有提到。
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的區別可百度。
配置構建後操作,即測試報告生成路徑。
郵件配置操作自行百度。
至此,Jenkins配置工作全部搞掂,接下來構建測試即可。
6、構建測試
六、關注點
- 整套框架使用的是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分支進行腳本編寫,腳本調試無誤後再push到遠程的dev分支,然後合併到master分支。
切換到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訪問。