接口自动化测试 – Java+TestNG 测试 Restful Web Service

关键词:基于Rest的Web服务,接口自动化测试,数据驱动测试,测试Restful Web Service, 数据分离,Java+Maven+TestNG

本文主要介绍如何用Java针对Restful web service 做接口自动化测试(数据驱动),相比UI自动化,接口自动化稳定性可靠性高,实施难易程度低,做自动化性价比高。所用到的工具或类库有 TestNG, Apache POI, Jayway rest-assured,Skyscreamer - JSONassert

简介:

思想是数据驱动测试,用Excel来管理数据,‘Input’ Sheet中存放输入数据,读取数据后拼成request 调用service, 拿到response后写入 ‘Output’ Sheet 即实际结果, ‘Baseline’为基线(期望结果)用来和实际结果对比的,‘Comparison’ Sheet里存放的是对比结果不一致的记录,‘Result’ Sheet 是一个简单的结果报告。

Maven工程目录结构:

详细介绍

核心就一个测试类HTTPReqGenTest.java 由四部分组成

@BeforeTest  读取Excel (WorkBook) 的 ‘Input’ 和 ‘Baseline’ sheet

并且新建‘Output’, ‘Comparison’, ‘Result’ 三个空sheet

读取http_request_template.txt 内容转成string

@DataProvider (name = "WorkBookData")

TestNG的DataProvider, 首先用DataReader构造函数,读取Excel中Input的数据,放入HashMap<String, RecordHandler>, Map的key值就是test case的ID,value是RecordHandler对象,此对象中一个重要的成员属性就是input sheet里面 column和value 的键值对,遍历Map将test case ID 与 test case的value 即input sheet前两列的值放入List<Object[]> ,最后返回List的迭代器iterator (为了循环调用@Test方法)

@Test (dataProvider = "WorkBookData", description = "ReqGenTest")

测试方法,由DataProvider提供数据,首先根据ID去取myInputData里的RecordHandler, 由它和template 去生成request, 然后执行request 返回response,这些工作都是由HTTPReqGen.java这个类完成的,借助com.jayway.restassured, 返回的response是一个JSON体,通过org.skyscreamer.jsonassert.JSONCompare 与Baseline中事先填好的期望结果(同样也是JSON格式)进行比较, 根据结果是Pass还是Fail, 都会相应的往Excel里的相应Sheet写结果。

@AfterTest

写入统计的一些数据

关闭文件流

实现代码:

HTTPReqGenTest.java

package com.demo.qa.rest_api_test;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map; import org.apache.commons.io.IOUtils;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.json.JSONException;
import org.skyscreamer.jsonassert.JSONCompare;
import org.skyscreamer.jsonassert.JSONCompareMode;
import org.skyscreamer.jsonassert.JSONCompareResult;
import org.testng.Assert;
import org.testng.ITest;
import org.testng.ITestContext;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test; import com.demo.qa.utils.DataReader;
import com.demo.qa.utils.DataWriter;
import com.demo.qa.utils.HTTPReqGen;
import com.demo.qa.utils.RecordHandler;
import com.demo.qa.utils.SheetUtils;
import com.demo.qa.utils.Utils;
import com.jayway.restassured.response.Response; public class HTTPReqGenTest implements ITest { private Response response;
private DataReader myInputData;
private DataReader myBaselineData;
private String template; public String getTestName() {
return "API Test";
} String filePath = ""; XSSFWorkbook wb = null;
XSSFSheet inputSheet = null;
XSSFSheet baselineSheet = null;
XSSFSheet outputSheet = null;
XSSFSheet comparsionSheet = null;
XSSFSheet resultSheet = null; private double totalcase = 0;
private double failedcase = 0;
private String startTime = "";
private String endTime = ""; @BeforeTest
@Parameters("workBook")
public void setup(String path) {
filePath = path;
try {
wb = new XSSFWorkbook(new FileInputStream(filePath));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
inputSheet = wb.getSheet("Input");
baselineSheet = wb.getSheet("Baseline"); SheetUtils.removeSheetByName(wb, "Output");
SheetUtils.removeSheetByName(wb, "Comparison");
SheetUtils.removeSheetByName(wb, "Result");
outputSheet = wb.createSheet("Output");
comparsionSheet = wb.createSheet("Comparison");
resultSheet = wb.createSheet("Result"); try {
InputStream is = HTTPReqGenTest.class.getClassLoader().getResourceAsStream("http_request_template.txt");
template = IOUtils.toString(is, Charset.defaultCharset());
} catch (Exception e) {
Assert.fail("Problem fetching data from input file:" + e.getMessage());
} SimpleDateFormat sf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
startTime = sf.format(new Date());
} @DataProvider(name = "WorkBookData")
protected Iterator<Object[]> testProvider(ITestContext context) { List<Object[]> test_IDs = new ArrayList<Object[]>(); myInputData = new DataReader(inputSheet, true, true, 0);
Map<String, RecordHandler> myInput = myInputData.get_map(); // sort map in order so that test cases ran in a fixed order
Map<String, RecordHandler> sortmap = Utils.sortmap(myInput); for (Map.Entry<String, RecordHandler> entry : sortmap.entrySet()) {
String test_ID = entry.getKey();
String test_case = entry.getValue().get("TestCase");
if (!test_ID.equals("") && !test_case.equals("")) {
test_IDs.add(new Object[] { test_ID, test_case });
}
totalcase++;
} myBaselineData = new DataReader(baselineSheet, true, true, 0); return test_IDs.iterator();
} @Test(dataProvider = "WorkBookData", description = "ReqGenTest")
public void api_test(String ID, String test_case) { HTTPReqGen myReqGen = new HTTPReqGen(); try {
myReqGen.generate_request(template, myInputData.get_record(ID));
response = myReqGen.perform_request();
} catch (Exception e) {
Assert.fail("Problem using HTTPRequestGenerator to generate response: " + e.getMessage());
} String baseline_message = myBaselineData.get_record(ID).get("Response"); if (response.statusCode() == 200)
try {
DataWriter.writeData(outputSheet, response.asString(), ID, test_case); JSONCompareResult result = JSONCompare.compareJSON(baseline_message, response.asString(), JSONCompareMode.NON_EXTENSIBLE);
if (!result.passed()) {
DataWriter.writeData(comparsionSheet, result, ID, test_case);
DataWriter.writeData(resultSheet, "false", ID, test_case, 0);
DataWriter.writeData(outputSheet);
failedcase++;
} else {
DataWriter.writeData(resultSheet, "true", ID, test_case, 0);
}
} catch (JSONException e) {
DataWriter.writeData(comparsionSheet, "", "Problem to assert Response and baseline messages: "+e.getMessage(), ID, test_case);
DataWriter.writeData(resultSheet, "error", ID, test_case, 0);
failedcase++;
Assert.fail("Problem to assert Response and baseline messages: " + e.getMessage());
}
else {
DataWriter.writeData(outputSheet, response.statusLine(), ID, test_case); if (baseline_message.equals(response.statusLine())) {
DataWriter.writeData(resultSheet, "true", ID, test_case, 0);
} else {
DataWriter.writeData(comparsionSheet, baseline_message, response.statusLine(), ID, test_case);
DataWriter.writeData(resultSheet, "false", ID, test_case, 0);
DataWriter.writeData(outputSheet);
failedcase++;
}
}
} @AfterTest
public void teardown() {
SimpleDateFormat sf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
endTime = sf.format(new Date());
DataWriter.writeData(resultSheet, totalcase, failedcase, startTime, endTime); try {
FileOutputStream fileOutputStream = new FileOutputStream(filePath);
wb.write(fileOutputStream);
fileOutputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

DataReader

package com.demo.qa.utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* Class that read data from XSSF sheet
*
*/
public class DataReader { protected static final Logger logger = LoggerFactory.getLogger(DataReader.class); private HashMap<String, RecordHandler> map = new HashMap<String, RecordHandler>(); private Boolean byColumnName = false;
private Boolean byRowKey = false;
private List<String> headers = new ArrayList<String>(); private Integer size = 0; public DataReader() {
} /**
* Primary constructor. Uses Apache POI XSSF to pull data from given excel workbook sheet. Data is stored in a
* structure depending on the options from other parameters.
*
* @param sheet Given excel sheet.
* @param has_headers Boolean used to specify if the data has a header or not. The headers will be used as field keys.
* @param has_key_column Boolean used to specify if the data has a column that should be used for record keys.
* @param key_column Integer used to specify the key column for record keys.
*/
public DataReader(XSSFSheet sheet, Boolean has_headers, Boolean has_key_column, Integer key_column) { XSSFRow myRow = null;
HashMap<String, String> myList;
size = 0; this.byColumnName = has_headers;
this.byRowKey = has_key_column; try { if(byColumnName) {
myRow = sheet.getRow(0);
for(Cell cell: myRow) {
headers.add(cell.getStringCellValue());
}
size = 1;
} for(; (myRow = sheet.getRow(size)) != null; size++ ) { myList = new HashMap<String, String>(); if(byColumnName) {
for(int col = 0; col < headers.size(); col++ ) {
if(col < myRow.getLastCellNum()) {
myList.put(headers.get(col), getSheetCellValue(myRow.getCell(col))); // myRow.getCell(col).getStringCellValue());
} else {
myList.put(headers.get(col), "");
}
}
} else {
for(int col = 0; col < myRow.getLastCellNum(); col++ ) {
myList.put(Integer.toString(col), getSheetCellValue(myRow.getCell(col)));
}
} if(byRowKey) {
if(myList.size() == 2 && key_column == 0) {
map.put(getSheetCellValue(myRow.getCell(key_column)), new RecordHandler(myList.get(1)));
} else if(myList.size() == 2 && key_column == 1) {
map.put(getSheetCellValue(myRow.getCell(key_column)), new RecordHandler(myList.get(0)));
} else {
map.put(getSheetCellValue(myRow.getCell(key_column)), new RecordHandler(myList));
}
} else {
map.put(Integer.toString(size), new RecordHandler(myList));
}
} } catch (Exception e) {
logger.error("Exception while loading data from Excel sheet:"+e.getMessage());
}
} /**
* Utility method used for getting an excel cell value. Cell's type is switched to String before accessing.
*
* @param cell Given excel cell.
*/
private String getSheetCellValue(XSSFCell cell) { String value = ""; try {
cell.setCellType(Cell.CELL_TYPE_STRING);
value = cell.getStringCellValue();
} catch(NullPointerException npe) {
return "";
} return value;
} /**
* Returns entire HashMap containing this class's data.
*
* @return HashMap<String, RecordHandler>, map of ID-Record data.
*/
public HashMap<String, RecordHandler> get_map() { return map;
} /**
* Gets an entire record.
*
* @param record String key value for record to be returned.
* @return HashMap of key-value pairs representing the specified record.
*/
public RecordHandler get_record(String record) { RecordHandler result = new RecordHandler(); if(map.containsKey(record)) {
result = map.get(record);
} return result;
} }

HTTPReqGen

package com.demo.qa.utils;

import static com.jayway.restassured.RestAssured.given;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map; import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import com.jayway.restassured.response.Response;
import com.jayway.restassured.specification.RequestSpecification; /**
* Wrapper for RestAssured. Uses an HTTP request template and a single record housed in a RecordHandler object to
* generate and perform an HTTP requests.
*
*/
public class HTTPReqGen { protected static final Logger logger = LoggerFactory.getLogger(HTTPReqGen.class); private RequestSpecification reqSpec; private String call_host = "";
private String call_suffix = "";
private String call_string = "";
private String call_type = "";
private String body = "";
private Map<String, String> headers = new HashMap<String, String>();
private HashMap<String, String> cookie_list = new HashMap<String, String>(); public Map<String, String> getHeaders() {
return headers;
} public String getCallString() {
return call_string;
} /**
* Constructor. Initializes the RequestSpecification (relaxedHTTPSValidation avoids certificate errors).
*
*/
public HTTPReqGen() {
reqSpec = given().relaxedHTTPSValidation();
} public HTTPReqGen(String proxy) {
reqSpec = given().relaxedHTTPSValidation().proxy(proxy);
} /**
* Pulls HashMap from given RecordHandler and calls primary generate_request method with it.
*
* @param template String, should contain the full template.
* @param record RecordHandler, the input data used to fill in replacement tags that exist in the template.
* @return this Reference to this class, primarily to allow request generation and performance in one line.
* @throws Exception
*/
public HTTPReqGen generate_request(String template, RecordHandler record) throws Exception { return generate_request(template, (HashMap<String, String>) record.get_map());
} /**
* Generates request data, using input record to fill in the template and then parse out relevant data. To fill in the
* template, identifies tags surrounded by << and >> and uses the text from the corresponding fields in the
* RecordHandler to replace them. The replacement is recursive, so tags may also exist in the fields of the
* RecordHandler so long as they are also represented by the RecordHandler and do not form an endless loop.
* After filling in the template, parses the resulting string in preparation for performing the HTTP request. Expects the
* the string to be in the following format:
*
* <<call_type>> <<call_suffix>>
* Host: <<root_host_name>>
* <<header1_name>>:<<header1_value>>
* ...
* <<headerN_name>>: <<headerN_value>>
*
* <<body_text>>
*
* <<call_type>> must be GET, PUT, POST, or DELETE. <<call_suffix>> must be a string with no spaces. It is appended to
* <<root_host_name>> to form the complete call string. After a single blank line is encountered, the rest of the file
* is used as the body of text for PUT and POST calls. This function also expects the Record Handler to include a field
* named "VPID" containing a unique record identifier for debugging purposes.
*
* @param template String, should contain the full template.
* @param record RecordHandler, the input data used to fill in replacement tags that exist in the template.
* @return this Reference to this class, primarily to allow request generation and performance in one line.
* @throws Exception
*/
public HTTPReqGen generate_request(String template, HashMap<String, String> record) throws Exception { String filled_template = "";
Boolean found_replacement = true;
headers.clear(); try { // Splits template into tokens, separating out the replacement strings
// like <<id>>
String[] tokens = tokenize_template(template); // Repeatedly perform replacements with data from record until no
// replacements are found
// If a replacement's result is an empty string, it will not throw an
// error (but will throw one if there is no column for that result)
while(found_replacement) {
found_replacement = false;
filled_template = ""; for(String item: tokens) { if(item.startsWith("<<") && item.endsWith(">>")) {
found_replacement = true;
item = item.substring(2, item.length() - 2); if( !record.containsKey(item)) {
logger.error("Template contained replacement string whose value did not exist in input record:[" + item + "]");
} item = record.get(item);
} filled_template += item;
} tokens = tokenize_template(filled_template);
} } catch (Exception e) {
logger.error("Problem performing replacements from template: ", e);
} try { // Feed filled template into BufferedReader so that we can read it line
// by line.
InputStream stream = IOUtils.toInputStream(filled_template, "UTF-8");
BufferedReader in = new BufferedReader(new InputStreamReader(stream));
String line = "";
String[] line_tokens; // First line should always be call type followed by call suffix
line = in.readLine();
line_tokens = line.split(" ");
call_type = line_tokens[0];
call_suffix = line_tokens[1]; // Second line should contain the host as it's second token
line = in.readLine();
line_tokens = line.split(" ");
call_host = line_tokens[1]; // Full call string for RestAssured will be concatenation of call
// host and call suffix
call_string = call_host + call_suffix; // Remaining lines will contain headers, until the read line is
// empty
line = in.readLine();
while(line != null && !line.equals("")) { String lineP1 = line.substring(0, line.indexOf(":")).trim();
String lineP2 = line.substring(line.indexOf(" "), line.length()).trim(); headers.put(lineP1, lineP2); line = in.readLine();
} // If read line is empty, but next line(s) have data, create body
// from them
if(line != null && line.equals("")) {
body = "";
while( (line = in.readLine()) != null && !line.equals("")) {
body += line;
}
} } catch(Exception e) {
logger.error("Problem setting request values from template: ", e);
} return this;
} /**
* Performs the request using the stored request data and then returns the response.
*
* @return response Response, will contain entire response (response string and status code).
*/
public Response perform_request() throws Exception { Response response = null; try { for(Map.Entry<String, String> entry: headers.entrySet()) {
reqSpec.header(entry.getKey(), entry.getValue());
} for(Map.Entry<String, String> entry: cookie_list.entrySet()) {
reqSpec.cookie(entry.getKey(), entry.getValue());
} switch(call_type) { case "GET": {
response = reqSpec.get(call_string);
break;
}
case "POST": {
response = reqSpec.body(body).post(call_string);
break;
}
case "PUT": {
response = reqSpec.body(body).put(call_string);
break;
}
case "DELETE": {
response = reqSpec.delete(call_string);
break;
} default: {
logger.error("Unknown call type: [" + call_type + "]");
}
} } catch (Exception e) {
logger.error("Problem performing request: ", e);
} return response;
} /**
* Splits a template string into tokens, separating out tokens that look like "<<key>>"
*
* @param template String, the template to be tokenized.
* @return list String[], contains the tokens from the template.
*/
private String[] tokenize_template(String template) {
return template.split("(?=[<]{2})|(?<=[>]{2})");
} }

RecordHandler

package com.demo.qa.utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; public class RecordHandler { private enum RecordType {
VALUE, NAMED_MAP, INDEXED_LIST
} private String single_value = "";
private HashMap<String, String> named_value_map = new HashMap<String, String>();
private List<String> indexed_value_list = new ArrayList<String>();
private RecordType myType; public RecordHandler() {
this("");
} public RecordHandler(String value) { this.myType = RecordType.VALUE;
this.single_value = value; } public RecordHandler(HashMap<String, String> map) { this.myType = RecordType.NAMED_MAP;
this.named_value_map = map; } public RecordHandler(List<String> list) { this.myType = RecordType.INDEXED_LIST;
this.indexed_value_list = list; } public HashMap<String, String> get_map() {
return named_value_map;
} public int size() {
int result = 0; if(myType.equals(RecordType.VALUE)) {
result = 1;
} else if(myType.equals(RecordType.NAMED_MAP)) {
result = named_value_map.size();
} else if(myType.equals(RecordType.INDEXED_LIST)) {
result = indexed_value_list.size();
} return result;
} public String get() {
String result = ""; if(myType.equals(RecordType.VALUE)) result = single_value;
else {
System.out.println("Called get() on wrong type:" + myType.toString());
} return result;
} public String get(String key) {
String result = ""; if(myType.equals(RecordType.NAMED_MAP)) result = named_value_map.get(key); return result;
} public String get(Integer index) {
String result = ""; if(myType.equals(RecordType.INDEXED_LIST)) result = indexed_value_list.get(index); return result;
} public Boolean set(String value) {
Boolean result = false; if(myType.equals(RecordType.VALUE)) {
this.single_value = value;
result = true;
} else if(myType.equals(RecordType.INDEXED_LIST)) {
this.indexed_value_list.add(value);
result = true;
} return result;
} public Boolean set(String key, String value) {
Boolean result = false; if(myType.equals(RecordType.NAMED_MAP)) {
this.named_value_map.put(key, value);
result = true;
} return result;
} public Boolean set(Integer index, String value) {
Boolean result = false; if(myType.equals(RecordType.INDEXED_LIST)) {
if(this.indexed_value_list.size() > index) this.indexed_value_list.set(index, value); result = true;
} return result;
} public Boolean has(String value) {
Boolean result = false; if(myType.equals(RecordType.VALUE) && this.single_value.equals(value)) {
result = true;
} else if(myType.equals(RecordType.NAMED_MAP) && this.named_value_map.containsKey(value)) {
result = true;
} else if(myType.equals(RecordType.INDEXED_LIST) && this.indexed_value_list.contains(value)) {
result = true;
} return result;
} public Boolean remove(String value) {
Boolean result = false; if(myType.equals(RecordType.VALUE) && this.single_value.equals(value)) {
this.single_value = "";
result = true;
}
if(myType.equals(RecordType.NAMED_MAP) && this.named_value_map.containsKey(value)) {
this.named_value_map.remove(value);
result = true;
} else if(myType.equals(RecordType.INDEXED_LIST) && this.indexed_value_list.contains(value)) {
this.indexed_value_list.remove(value);
result = true;
} return result;
} public Boolean remove(Integer index) {
Boolean result = false; if(myType.equals(RecordType.INDEXED_LIST) && this.indexed_value_list.contains(index)) {
this.indexed_value_list.remove(index);
result = true;
} return result;
} }

其它不重要的类不一一列出来了。

pom.xml

<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>com.demo</groupId>
<artifactId>qa</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Automation</name>
<description>Test project for Demo</description> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.5</version>
<executions>
<execution>
<id>default-jar</id>
<goals>
<goal>test-jar</goal>
</goals>
<configuration>
<archive>
<manifest>
<mainClass>com.demo.qa.utils.TestStartup</mainClass>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<useUniqueVersions>false</useUniqueVersions>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.17</version>
<configuration>
<skip>true</skip>
<suiteXmlFiles>
<suiteXmlFile>src\test\resources\HTTPReqGenTest.xml</suiteXmlFile>
</suiteXmlFiles>
</configuration>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.8</version>
<executions>
<execution>
<id>default-cli</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<versionRange>[1.0.0,)</versionRange>
<goals>
<goal>copy-dependencies</goal>
</goals>
</pluginExecutionFilter>
<action>
<execute />
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.3.2</version>
</dependency> <dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>com.jayway.restassured</groupId>
<artifactId>rest-assured</artifactId>
<version>2.3.3</version>
</dependency>
<dependency>
<groupId>com.jayway.restassured</groupId>
<artifactId>json-path</artifactId>
<version>2.3.3</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.10.1</version>
<exclusions>
<exclusion>
<artifactId>commons-codec</artifactId>
<groupId>commons-codec</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.8</version>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.10.1</version>
<exclusions>
<exclusion>
<artifactId>xml-apis</artifactId>
<groupId>xml-apis</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.skyscreamer</groupId>
<artifactId>jsonassert</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.6</version>
</dependency>
</dependencies>
</project>

运行是通过TestNG的xml文件来执行的, 里面配置了Parameter “workBook” 的路径

TestNG的运行结果都是Pass, 但事实上里面有case是Fail的,我只是借助TestNG来运行,我并没有在@Test方法里加断言Assert, 所以这里不会Fail, 我的目的是完全用Excel来管理维护测试数据以及测试结果,做到数据脚本完全分离。

Output sheet

Comparison sheet

Result sheet

当然 你也可以把maven工程打成一个可执行jar来运行,不过需要增加一个main函数作为入口,xml测试文件通过参数传递进去,另外还需要在pom里配置一些插件,像maven-jar-plugin。

如果你还需要做back-end DB check,你可以在Input里再增加几列,你要查询的表,字段,Baseline里也相应的加上期望结果,这里就不再赘述了。

http://www.cnblogs.com/wade-xu/p/4229805.html

注:转载需注明出处及作者名。

接口自动化测试框架搭建 – Java+TestNG 测试Restful service的更多相关文章

  1. 零成本实现接口自动化测试 – Java+TestNG 测试Restful service

    接口自动化测试 – Java+TestNG 测试 Restful Web Service 关键词:基于Rest的Web服务,接口自动化测试,数据驱动测试,测试Restful Web Service, ...

  2. 【接口自动化】Python+Requests接口自动化测试框架搭建【一】

    公司项目启用新框架,前后端分离,所以接口测试成为测试工作中不可缺失的一个环节,现在将从0开始搭建接口自动化测试框架的路程,一步步记录下来. 开发语言我们采用Python+第三方库Requests,测试 ...

  3. (转)接口自动化测试 – Java+TestNG 测试 Restful Web Service

    本文主要介绍如何用Java针对Restful web service 做接口自动化测试(数据驱动),相比UI自动化,接口自动化稳定性可靠性高,实施难易程度低,做自动化性价比高.所用到的工具或类库有 T ...

  4. Jmeter+Ant+Jenkins接口自动化测试框架搭建

    前言 软件开发的V模型大家都不陌生,其中测试阶段分为单元测试→功能测试→系统测试→验收测试.其中单元测试一般由开发同学们自己完成,大部分测试具体实施(这里不包括用例设计)是从单体功能测试开始着手的. ...

  5. 自动化测试框架selenium+java+TestNG——配置篇

    最近来总结下自动化测试 selenium的一些常用框架测试搭配,由简入繁,最简单的就是selenium+java+TestNG了,因为我用的是java,就只是总结下java了. TestNG在线安装: ...

  6. 【接口自动化】Python+Requests接口自动化测试框架搭建【三】

    经过上两篇文章的讲解,我们已经完成接口自动化的基础框架,现在开始根据实际项目丰满起来. 在PyCharm中新建项目,项目工程结构如下: config:配置文件夹,可以将一些全局变量放于配置文件中,方便 ...

  7. 自动化测试框架selenium+java+TestNG——TestNG注解、执行、测试结果和测试报告

    TestNG是java的一个测试框架,相比较于junit,功能更强大和完善,我是直接学习和使用的TestNG就来谈下TestNG的一些特点吧. TestNG的特点 注解 TestNG使用Java和面向 ...

  8. 自动化测试框架selenium+java+TestNG——读取csv文件

    读取csv文件可以直接读取,也可以使用javacsv.jar,后者比较简单,这个也可以变相认为是对表格的处理,我们可以在表格中做好数据,存储成csv格式的文件,后续对xlsx表格的操作抽个时间再记录下 ...

  9. 接口自动化测试框架(Java 实现)

    目录 需求分析 开发设计 分层与抽象 技术选型 主要类设计 测试文件设计 工程目录设计 工程实现 github 地址 运行示例 需求分析 需求点 需求分析 通过 yaml 配置接口操作和用例 后续新增 ...

随机推荐

  1. MFC控件CTabCtrl关联变量

    1.先建立一个对话框MFC应用程序,然后在工具箱里面把Tab Control控件放到对话框中的合适位置上. 再在对话框类中,将该控件绑定一个变量 用两种方法: 1 ) 自己定义成员变量 CTabCtr ...

  2. 怎样用SQL语句查看查询的性能指标

    一.SET STATISTICS IO  (有关TSQL语句查询所产生的磁盘活动量) 扫描计数:在查询中涉及到的表被访问的次数: 逻辑读取:从数据缓冲中读取的数据页数: 物理读取:从物理磁盘中往缓冲读 ...

  3. ios-toolchain-based-on-clang-for-linux

    https://github.com/tpoechtrager/cctools-port.git https://www.embtoolkit.org

  4. openstack nova 用户管理

    用户管理      创建管理员用户      用法:      nova-manage user admin name [access] [secret]      其中access 和secret可 ...

  5. Cracking the Coding Interview(linked list)

    第二章的内容主要是关于链表的一些问题. 基础代码: class LinkNode { public: int linknum; LinkNode *next; int isvisit; protect ...

  6. CentOS 配置Rails开发环境

    1 安装mysql yum install -y mysql mysql-server 启动mysql $ /etc/init.d/mysqld start 设置root密码,删除test数据库等 / ...

  7. JIRA - 使用指南(项目跟踪管理工具)

    第一章.前言    JIRA 是澳大利亚 Atlassian 公司开发的一款优秀的问题跟踪管理软件工具,可以对各种类型的问题进行跟踪管理,包括缺陷.任务.需求.改进等.JIRA采用J2EE技术,能够跨 ...

  8. SDWebImage第三方库使用注意的一些问题

    1.利用"UIImageView+WebCache.h"加载图片数据 例如: UIImage *placeHolderImg = [UIImage imageNamed:@&quo ...

  9. yii---对数组进行分页

    很多时候,我们会对多个数据进行分页处理,例如我最近开发的一个功能,系统消息,系统消息的来源是多个表,而且多个表之间的数据没有任何关联,这个时候,需要对多个表进行查询,查询返回的数据进行分页,而且采用的 ...

  10. spring面试大全

    一.spring如何实现资源管理? 使用 applicationContext.getResource(“classpath:文件名”):在src根目录下,在类路径下 applicationConte ...