基于cucumber接口测试框架的扩展——测试框架总结之cucumber

主要功能:

1、通过fiddler抓取请求,导出xml文件。

2、解析xml文件至excel,或者手工填写excel数据。

3、根据excel中的URL中地址生成的接口集合和feature内容模板生成各个接口的feature文件。

4、修改feature文件,指定excel中的执行数据,添加预期值。

5、执行feature文件,使用cucumber report在jenkins中执行和统计结果。

详细如下:

1、通过fiddler抓取请求,导出xml文件。

a.Filters设置Show only the following Hosts  输入10.253.11.41:41630
b.点击Actions-Run Filterset now 开始操作系统抓取请求
c.File-Export Sessions-All Sessons菜单,选择Visual Studio WebTest格式导出文件。
d.将上述文件的内容拷贝到自己的xml文件中,删除<Items> 节点,保存。
e.将文件拷贝至项目的resource目录下.

基于cucumber接口测试框架的扩展——测试框架总结之cucumber

文件内容:

<?xml version="1.0" encoding="utf-8"?>
<TestCase Name="FiddlerGeneratedWebTest" Id="" Owner="" Description="" Priority="0" Enabled="True" CssProjectStructure="" CssIteration="" DeploymentItemsEditable="" CredentialUserName="" CredentialPassword="" PreAuthenticate="True" Proxy="" RequestCallbackClass="" TestCaseCallbackClass="">
<Request Method="POST" Version="1.1" Url="http://10.253.11.41:41630/login" ThinkTime="0" Timeout="60" ParseDependentRequests="True" FollowRedirects="True" RecordResult="True" Cache="False" ResponseTimeGoal="0" Encoding="utf-8">
<Headers>
<Header Name="Origin" Value="http://10.253.11.41:41630" />
<Header Name="X-Requested-With" Value="XMLHttpRequest" />
<Header Name="Content-Type" Value="application/x-www-form-urlencoded; charset=UTF-8" />
</Headers>
<FormPostHttpBody ContentType="application/x-www-form-urlencoded; charset=UTF-8">
<FormPostParameter Name="name" Value="wuxichao" UrlEncode="True" />
<FormPostParameter Name="password" Value="Abcd1234" UrlEncode="True" />
<FormPostParameter Name="params" Value="mid=guihe" UrlEncode="True" />
</FormPostHttpBody>
</Request>
<Request Method="POST" Version="1.1" Url="http://10.253.11.41:41630/menu" ThinkTime="12" Timeout="60" ParseDependentRequests="True" FollowRedirects="True" RecordResult="True" Cache="False" ResponseTimeGoal="0" Encoding="utf-8">
<Headers>
<Header Name="Origin" Value="http://10.253.11.41:41630" />
<Header Name="X-Requested-With" Value="XMLHttpRequest" />
</Headers>
</Request>
<Request Method="POST" Version="1.1" Url="http://10.253.11.41:41630/dept/tree" ThinkTime="-3" Timeout="60" ParseDependentRequests="True" FollowRedirects="True" RecordResult="True" Cache="False" ResponseTimeGoal="0" Encoding="utf-8">
<Headers>
<Header Name="Origin" Value="http://10.253.11.41:41630" />
<Header Name="X-Requested-With" Value="XMLHttpRequest" />
</Headers>
</Request> <Request Method="POST" Version="1.1" Url="http://10.253.11.41:41630/dict/select" ThinkTime="0" Timeout="60" ParseDependentRequests="True" FollowRedirects="True" RecordResult="True" Cache="False" ResponseTimeGoal="0" Encoding="utf-8">
<Headers>
<Header Name="Origin" Value="http://10.253.11.41:41630" />
<Header Name="X-Requested-With" Value="XMLHttpRequest" />
<Header Name="Content-Type" Value="application/x-www-form-urlencoded; charset=UTF-8" />
</Headers>
<FormPostHttpBody ContentType="application/x-www-form-urlencoded; charset=UTF-8">
<FormPostParameter Name="dictionaryCode" Value="staff.staff_type" UrlEncode="True" />
</FormPostHttpBody>
</Request>
</TestCase>

2、解析xml文件至excel,或者手工填写excel数据。

1)ProductExcelDataTool.java类,将xml解析写入excel
public static boolean productExcelData(String xmlFileName, String excelpath) throws UnsupportedEncodingException, DocumentException {
List<ReqestEntity> requestlist = XmlTool.getRequestList(xmlFileName);
ExcelManage em = new ExcelManage();
//判断文件是否存在
System.out.println(em.fileExist(excelpath));
//创建文件
String title[] = {"caseNo", "URL", "Method", "Params", "ContentType", "status", "success", "message", "msgCode"};
//判断sheet是否存在
System.out.println(em.sheetExist(excelpath, "sheet1"));
em.createExcel(excelpath, "sheet1", title); //写入到excel
em.writeToExcel(excelpath, "sheet1", requestlist); //读取excel
ReqestEntity object = new ReqestEntity();
List<ReqestEntity> list = em.readFromExcel(excelpath, "sheet1");
for (int i = 0; i < list.size(); i++) {
ReqestEntity reqestEntity = (ReqestEntity) list.get(i);
System.out.println(reqestEntity.getCaseNo() + "||" + reqestEntity.getUrl() + "||" + reqestEntity.getMethod() + "||"
+ reqestEntity.getParams() + "||" + reqestEntity.getContentType());
}
return true;
}
public static void main(String[] args) throws UnsupportedEncodingException, DocumentException { boolean b = productExcelData("case.xml","fiddlerData/test2.xls"); }
}  
2)解析 Fiddler xml文件生成list
package fiddler.module;

import org.apache.commons.codec.binary.Base64;
import other.TestDom4j;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader; import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List; /**
* Created by linyanghua on 2017/9/26.
* 解析 Fiddler xml文件
*
*/
public class XmlTool {
/**
* 将fiddler 录制xml文件解析,并返回 Request请求数据存入List<ReqestEntity>中
* @param filename resource 目录下的fiddler xml文件名
* @return List<ReqestEntity> Request请求数据
* @throws DocumentException
* @throws UnsupportedEncodingException
*/
public static List<ReqestEntity> getRequestList(String filename) throws DocumentException, UnsupportedEncodingException {
SAXReader reader = new SAXReader();
InputStream in = TestDom4j.class.getClassLoader().getResourceAsStream(filename);
org.dom4j.Document doc = reader.read(in);
Element root = doc.getRootElement();
//Request Node
List<Element> reqNodes = root.elements();
List<ReqestEntity> listreq = new ArrayList<>();
//解析xml,存入ReqestEntity 的list
int i=0;
for (Element reqNode : reqNodes) {
i++;
String method = reqNode.attribute("Method").getValue();
List<Element> reqChild = reqNode.elements();
ReqestEntity reqestEntity = new ReqestEntity();
if (method.equals("GET")) {
reqestEntity = getRequest(reqNode);
}
if (method.equals("POST") && reqChild.size() == 1) {
reqestEntity = postFormParameter(reqNode);
}
if (method.equals("POST") && (reqChild.size() == 2)) {
for (Element e : reqChild) {
if (e.getName() == "StringHttpBody") {
reqestEntity = postStringHttpBody(reqNode);
} else if (e.getName() == "FormPostHttpBody") {
reqestEntity = postFormParameter(reqNode);
}
}
}
reqestEntity.setCaseNo("case-"+i);
listreq.add(reqestEntity);
}
return listreq;
} /**
* get 节点数据对象
*
* @param request
* @return xml中get节点数据
*/
public static ReqestEntity getRequest(Element request) {
ReqestEntity getReq = new ReqestEntity();
String url = request.attribute("Url").getValue();
List<Element> StringParameters = request
.selectNodes("//QueryStringParameter");
List<Paramter> listparam = new ArrayList<>();
for (Element e : StringParameters) {
String name = e.attribute("Name").getValue();
String value = e.attribute("Value").getValue();
System.out.print("Name:" + name);
System.out.print("value:" + value);
Paramter paramter = new Paramter();
paramter.setName(name);
paramter.setValue(value);
listparam.add(paramter);
}
String paraStr = listAsStr(listparam);
getReq.setMethod("GET");
getReq.setUrl(url);
if (listparam.size() >= 1) {
getReq.setParams(paraStr);
}
getReq.setContentType("application/x-www-form-urlencoded");
return getReq;
} /**
* post json 字符串时,需要对xml 中json串解密处理,返回节点数据对象
*
* @param requestNode Request节点
* @return xml中post json节点数据
* @throws UnsupportedEncodingException
*/
public static ReqestEntity postStringHttpBody(Element requestNode) throws UnsupportedEncodingException {
ReqestEntity postJsonReq = new ReqestEntity();
String url = requestNode.attribute("Url").getValue();
String stringHttpBody = requestNode
.selectSingleNode("//StringHttpBody").getText();
String paraStr = decodePostBodyJson(stringHttpBody);
postJsonReq.setMethod("POST");
postJsonReq.setUrl(url);
postJsonReq.setParams(paraStr);
postJsonReq.setContentType("application/json");
return postJsonReq;
} /**
* post FormParameter 返回节点数据对象
*
* @param requestNode
* @return xml中postForm节点数据
* @throws UnsupportedEncodingException
*/
public static ReqestEntity postFormParameter(Element requestNode) throws UnsupportedEncodingException {
ReqestEntity postFormReq = new ReqestEntity();
String url = requestNode.attribute("Url").getValue();
List<Element> FormParameters = requestNode
.selectNodes("child::*/child::FormPostParameter");
List<Paramter> listparam = new ArrayList<>();
for (Element e : FormParameters) {
String name = e.attribute("Name").getValue();
String value = e.attribute("Value").getValue();
System.out.print("Name:" + name);
System.out.print("value:" + value);
Paramter paramter = new Paramter();
paramter.setName(name);
paramter.setValue(value);
listparam.add(paramter);
}
String paraStr = listAsStr(listparam);
postFormReq.setMethod("POST");
postFormReq.setUrl(url);
postFormReq.setParams(paraStr);
postFormReq.setContentType("application/x-www-form-urlencoded");
return postFormReq;
} /**
*
* @param enStr
* @return fiddler xml文件中解密json数据
* @throws UnsupportedEncodingException
*/
public static String decodePostBodyJson(String enStr) throws UnsupportedEncodingException {
String postBodyJson;
String deStr=null;
try {
deStr = new String(decodeBase64(enStr));
} catch (Exception e) {
e.printStackTrace();
}
postBodyJson = URLDecoder.decode(URLEncoder.encode(deStr, "utf-8").replace("%00", ""),"utf-8");
return postBodyJson;
} /***
* encode by Base64
*/
public static String encodeBase64(byte[]input) throws Exception{
Class clazz=Class.forName("com.sun.org.apache.xerces.internal.impl.dv.util.Base64");
Method mainMethod= clazz.getMethod("encode", byte[].class);
mainMethod.setAccessible(true);
Object retObj=mainMethod.invoke(null, new Object[]{input});
return (String)retObj;
}
/***
* decode by Base64
*/
public static byte[] decodeBase64(String input) throws Exception{
Class clazz=Class.forName("com.sun.org.apache.xerces.internal.impl.dv.util.Base64");
Method mainMethod= clazz.getMethod("decode", String.class);
mainMethod.setAccessible(true);
Object retObj=mainMethod.invoke(null, input);
return (byte[])retObj;
}
/**
* @param bytes
* @return
*/
public static String decode(final byte[] bytes) {
String str = new String(Base64.decodeBase64(bytes));
return str;
} /**
* 二进制数据编码为BASE64字符串
*
* @param bytes
* @return
* @throws Exception
*/
public static String encode(final byte[] bytes) {
return new String(Base64.encodeBase64(bytes));
} /**
* 将xml中的参数list转换为字符串
* @param list
* @return request 的参数字符串
*/
public static String listAsStr(List<Paramter> list){
String paraStr = "";
if (list.size()!= 0){
for (int i=0; i<list.size();i++){
if (i!=list.size()-1){
paraStr =paraStr + list.get(i).getName()+"="+ list.get(i).getValue()+"&";
}else {
paraStr =paraStr + list.get(i).getName()+"="+ list.get(i).getValue();
}
}
}
return paraStr;
} public static void main(String[] args) throws Exception {
List<ReqestEntity> reqlist = getRequestList("case.xml");
} }
  3)excel类公共方法
package fiddler.module;
import java.io.*;
import java.util.ArrayList;
import java.util.List; import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet; /**
* 从excel读取数据/往excel中写入 excel有表头,表头每列的内容对应实体类的属性
*
* @author nagsh
*
*/
public class ExcelManage {
private HSSFWorkbook workbook = null; /**
* 判断文件是否存在.
* @param fileDir 文件路径
* @return
*/
public boolean fileExist(String fileDir){
boolean flag = false;
File file = new File(fileDir);
flag = file.exists();
return flag;
}
/**
* 判断文件的sheet是否存在.
* @param fileDir 文件路径
* @param sheetName 表格索引名
* @return
*/
public boolean sheetExist(String fileDir,String sheetName){
boolean flag = false;
File file = new File(fileDir);
if(file.exists()){ //文件存在
//创建workbook
try {
workbook = new HSSFWorkbook(new FileInputStream(file));
//添加Worksheet(不添加sheet时生成的xls文件打开时会报错)
HSSFSheet sheet = workbook.getSheet(sheetName);
if(sheet!=null)
flag = true;
} catch (Exception e) {
e.printStackTrace();
} }else{ //文件不存在
flag = false;
} return flag;
}
/**
* 创建新excel.
* @param fileDir excel的路径
* @param sheetName 要创建的表格索引
* @param titleRow excel的第一行即表格头
*/
public void createExcel(String fileDir,String sheetName,String titleRow[]){
//创建workbook
workbook = new HSSFWorkbook();
//添加Worksheet(不添加sheet时生成的xls文件打开时会报错)
Sheet sheet1 = workbook.createSheet(sheetName);
//新建文件
FileOutputStream out = null;
try {
//添加表头
Row row = workbook.getSheet(sheetName).createRow(0); //创建第一行
for(int i = 0;i < titleRow.length;i++){
Cell cell = row.createCell(i);
cell.setCellValue(titleRow[i]);
}
out = new FileOutputStream(fileDir);
workbook.write(out);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 删除文件.
* @param fileDir 文件路径
*/
public boolean deleteExcel(String fileDir){
boolean flag = false;
File file = new File(fileDir);
// 判断目录或文件是否存在
if (!file.exists()) { // 不存在返回 false
return flag;
} else {
// 判断是否为文件
if (file.isFile()) { // 为文件时调用删除文件方法
file.delete();
flag = true;
}
}
return flag;
}
/**
* 往excel中写入.
* @param fileDir 文件路径
* @param sheetName 表格索引
* @param requestlist ReqestEntity 有9个属性,所以只需用到前5列 下标为0,1,2,3,4,5,6,7,8
*/
public void writeToExcel(String fileDir,String sheetName, List<ReqestEntity> requestlist) {
//创建workbook
File file = new File(fileDir);
try {
workbook = new HSSFWorkbook(new FileInputStream(file));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//流
FileOutputStream out = null;
HSSFSheet sheet = workbook.getSheet(sheetName);
int rowCount;
for (ReqestEntity req : requestlist) {
// 获取表格的总行数
rowCount = sheet.getLastRowNum() + 1; // 需要加一
try {
Row row = sheet.createRow(rowCount); //最新要添加的一行
// 获得表头行对象
HSSFRow titleRow = sheet.getRow(0);
if (titleRow != null) {
row.createCell(0).setCellValue(req.getCaseNo());
row.createCell(1).setCellValue(req.getUrl());
row.createCell(2).setCellValue(req.getMethod());
row.createCell(3).setCellValue(req.getParams());
row.createCell(4).setCellValue(req.getContentType());
row.createCell(5).setCellValue(req.getStatus());
row.createCell(6).setCellValue(req.getSuccess());
row.createCell(7).setCellValue(req.getMessage());
row.createCell(8).setCellValue(req.getMsgCode());
}
out = new FileOutputStream(fileDir);
workbook.write(out);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 读取excel表中的数据.
*
* @param fileDir 文件路径
* @param sheetName 表格索引(EXCEL 是多表文档,所以需要输入表索引号,如sheet1) */
public List<ReqestEntity> readFromExcel(String fileDir,String sheetName) {
//创建workbook
File file = new File(fileDir);
try {
workbook = new HSSFWorkbook(new FileInputStream(file));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} List<ReqestEntity> result = new ArrayList();
// 读取excel数据
// 获得指定的excel表
HSSFSheet sheet = workbook.getSheet(sheetName);
// 获取表格的总行数
int rowCount = sheet.getLastRowNum() + 1; // 需要加一
if (rowCount < 1) {
return result;
}
// 获取表头的列数
int columnCount = sheet.getRow(0).getLastCellNum();
// 获得表头行对象
for (int row =1;row<rowCount;row++) { HSSFRow titleRow = sheet.getRow(row);
// 遍历
ReqestEntity reqestEntity = new ReqestEntity();
reqestEntity.setCaseNo(titleRow.getCell(0).toString());
reqestEntity.setUrl(titleRow.getCell(1).toString());
reqestEntity.setMethod(titleRow.getCell(2).toString());
reqestEntity.setParams(titleRow.getCell(3).toString());
reqestEntity.setContentType(titleRow.getCell(4).toString());
reqestEntity.setStatus(titleRow.getCell(5).toString());
reqestEntity.setSuccess(titleRow.getCell(6).toString());
reqestEntity.setMessage(titleRow.getCell(7).toString());
reqestEntity.setMsgCode(titleRow.getCell(8).toString());
result.add(reqestEntity);
}
rowCount =rowCount-1;
System.out.println("rowCount:"+ rowCount); return result;
} /**
* 获取指定文件中指定行的数据
* @param fileDir 文件路径
* @param sheetName sheet名称
* @param index 行号
* @return ReqestEntity 对应数据
* @throws Exception
*/
public static ReqestEntity getRequestData(String fileDir,String sheetName,int index) throws Exception {
ExcelManage em = new ExcelManage();
List<ReqestEntity> list = em.readFromExcel(fileDir,sheetName);
ReqestEntity reqestEntity = null;
for (ReqestEntity req : list) {
if (Integer.parseInt(req.getCaseNo().split("-")[1].trim()) == index) {
reqestEntity = req;
break;
} }
if (reqestEntity==null ) throw new Exception("不存在对应行数据");
System.out.println(reqestEntity.getCaseNo()+"||"+ reqestEntity.getUrl() + "||" + reqestEntity.getMethod() + "||"
+ reqestEntity.getParams()+ "||" + reqestEntity.getContentType());
return reqestEntity;
} public static void main(String[] args) throws Exception {
ReqestEntity req= getRequestData("fiddlerData/test2.xls","sheet1",1);
//删除文件
//System.out.println(em.deleteExcel("E:/test2.xls")); }
}
4)cookie和常量
package fiddler.module;

import com.github.restdriver.serverdriver.RestServerDriver;
import com.github.restdriver.serverdriver.http.AnyRequestModifier;
import com.github.restdriver.serverdriver.http.response.Response; import static com.github.restdriver.serverdriver.RestServerDriver.body; /**
* Created by linyanghua on 2017/8/15.
*/
public class Common {
//登陆
static String hostUrl="http://10.253.11.41:41630";
static String loginurl=hostUrl+"/login?params=mid=guihe";
static String loginparam="name=wuxichao&password=Abcd1234";
static String applicationContext = "application/x-www-form-urlencoded"; static String applicationJson= "application/json";
//案件地址添加
static String addCreateUrl =hostUrl+ "/address/create";
//案件操作记录详情
static String caseOperatorRecUrl = hostUrl+"/caseOperationRecords/get"; /*
function 获取cookie
param
return
*/
static String getcookie(String url,String loginparam) throws InterruptedException {
Response response = RestServerDriver.post(url, body(loginparam,applicationContext));
Thread.sleep(3000);
String cookie= response.getHeaders().get(1).toString();
cookie=cookie.split(";")[0].split(":")[1].trim();
System.out.println( "cookie>>>"+cookie);
return cookie;
}
/**
* 案件地址添加测试
* @param jsonStr
* @param header_cookie
*/
static String addressCreateTestUrl(String jsonStr,AnyRequestModifier header_cookie) throws Exception {
Response response = RestServerDriver.post(addCreateUrl, body(jsonStr, applicationJson), header_cookie);
Thread.sleep(300);
int status = response.getStatusCode();
String content = response.getContent().toString();
if (status >= 500) throw new Exception("http报500错误,请求失败");
return content;
}
/**
* 案件操作记录详情测试
* @param id
* @param header_cookie
*/
static String caseOperationRecTestUrl(String id,AnyRequestModifier header_cookie) throws Exception {
String url = caseOperatorRecUrl+"?id="+id;
Response response = RestServerDriver.get(url,header_cookie);
int status = response.getStatusCode();
if(status >= 500) try {
throw new Exception("http报500错误,请求失败");
} catch (Exception e) {
e.printStackTrace();
}
String jsonNode = response.asText();
if(!jsonNode.contains("操作成功") || !jsonNode.contains("true")) throw new Exception("调用案件操作记录详情接口,执行失败");
return jsonNode;
} public static void main(String[] args) throws InterruptedException {
String cookie =getcookie(loginurl,loginparam);
System.out.println( "cookie>>>"+cookie);
}
}
5)实体
package fiddler.module;

/**
* Created by linyanghua on 2017/9/21.
*/
public class Paramter {
String name;
String value; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public String getValue() {
return value;
} public void setValue(String value) {
this.value = value;
}
}
package fiddler.module;

import java.util.ArrayList;
import java.util.List; /**
* Created by linyanghua on 2017/9/21.
*/
public class ReqestEntity {
String caseNo=null;
String url= null;
String method = null;
String params;
String contentType = null;
String status = null;
String success = null;
String message = null;
String msgCode = null;
String result=null;
String reponse=null; public String getStatus() {
return status;
} public void setStatus(String status) {
this.status = status;
} public String getSuccess() {
return success;
} public void setSuccess(String success) {
this.success = success;
} public String getMessage() {
return message;
} public void setMessage(String message) {
this.message = message;
} public String getMsgCode() {
return msgCode;
} public void setMsgCode(String msgCode) {
this.msgCode = msgCode;
} public String getCaseNo() {
return caseNo;
} public void setCaseNo(String caseNo) {
this.caseNo = caseNo;
} public String getResult() {
return result;
} public void setResult(String result) {
this.result = result;
} public String getReponse() {
return reponse;
} public void setReponse(String reponse) {
this.reponse = reponse;
} public ReqestEntity() {
} public String getUrl() {
return url;
} public void setUrl(String url) {
this.url = url;
} public String getMethod() {
return method;
} public void setMethod(String method) {
this.method = method;
} public String getParams() {
return params;
} public void setParams(String params) {
this.params = params;
} public String getContentType() {
return contentType;
} public void setContentType(String contentType) {
this.contentType = contentType;
}
} 6)生成的excel如下:

基于cucumber接口测试框架的扩展——测试框架总结之cucumber

3、根据excel中的URL中地址生成的接口集合和feature内容模板生成各个接口的feature文件。

package fiddler.module;

import java.io.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set; /**
* Created by linyanghua on 2017/10/10.
*/
public class ProductFeatureTool {
//生成文件路径
private static String path = "E:\\xcollection\\collectioncucumbertest\\src\\test\\resources\\fiddler\\";
//template文件路径
private static String templatePath = "E:\\xcollection\\collectioncucumbertest\\src\\test\\resources\\template\\"; //文件路径+名称
private static String filenameTemp; //feature文件
//template路径 文件路径+名称
private static String templatefile; //template文件
/**
* 创建文件
* @param fileName 文件名称
* @param templateName 文件内容
* @return 是否创建成功,成功则返回true
*/
public static boolean createFile(String fileName,String templateName){
Boolean bool = false;
filenameTemp = path+fileName+".feature";//文件路径+名称+文件类型
templatefile = templatePath + templateName;//模板文件 File file = new File(filenameTemp);
try {
//如果文件不存在,则创建新的文件
if(!file.exists()){
file.createNewFile();
bool = true;
System.out.println("success create file,the file is "+filenameTemp);
//创建文件成功后,写入内容到文件里
// writeFileContent(filenameTemp, filecontent);
copyTemplate(templatefile,filenameTemp);
}
} catch (Exception e) {
e.printStackTrace();
} return bool;
} /**
* 向文件中写入内容
* @param filepath 文件路径与名称
* @param newstr 写入的内容
* @return
* @throws IOException
*/
public static boolean writeFileContent(String filepath,String newstr) throws IOException{
Boolean bool = false;
String filein = newstr+"\r\n";//新写入的行,换行
String temp = ""; FileInputStream fis = null;
InputStreamReader isr = null;
BufferedReader br = null;
FileOutputStream fos = null;
PrintWriter pw = null;
try {
File file = new File(filepath);//文件路径(包括文件名称)
//将文件读入输入流
fis = new FileInputStream(file);
isr = new InputStreamReader(fis);
br = new BufferedReader(isr);
StringBuffer buffer = new StringBuffer(); //文件原有内容
for(int i=0;(temp =br.readLine())!=null;i++){
buffer.append(temp);
// 行与行之间的分隔符 相当于“\n”
buffer = buffer.append(System.getProperty("line.separator"));
}
buffer.append(filein); fos = new FileOutputStream(file);
pw = new PrintWriter(fos);
pw.write(buffer.toString().toCharArray());
pw.flush();
bool = true;
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}finally {
//不要忘记关闭
if (pw != null) {
pw.close();
}
if (fos != null) {
fos.close();
}
if (br != null) {
br.close();
}
if (isr != null) {
isr.close();
}
if (fis != null) {
fis.close();
}
}
return bool;
} /**
* 删除文件
* @param fileName 文件名称
* @return
*/
public static boolean delFile(String fileName){
Boolean bool = false;
filenameTemp = path+fileName+".txt";
File file = new File(filenameTemp);
try {
if(file.exists()){
file.delete();
bool = true;
}
} catch (Exception e) {
// TODO: handle exception
}
return bool;
} /**
* 读取文件内容
* @param path
* @param fileName
* @return
*/
public static String readtxtFile(String path,String fileName) {
String lineTxt = null;
StringBuffer stringBuffer = null;
try {
File file = new File(path + fileName);
//判断文件是否存在
if (file.isFile() && file.exists()) {
InputStreamReader read = new InputStreamReader(new FileInputStream(file), "UTF-8");
BufferedReader bufferedReader = new BufferedReader(read);
stringBuffer = new StringBuffer();
while ((lineTxt = bufferedReader.readLine()) != null) {
stringBuffer.append(lineTxt);
// System.out.println(lineTxt);
}
// logger.info(stringBuffer);
read.close();
} else {
System.out.println("找不到指定的文件");
}
} catch (Exception e) {
System.out.println("读取文件内容出错");
e.printStackTrace();
}
return String.valueOf(stringBuffer);
} /**
* 拷贝模板内容至文件中
* @param templatePath
* @param featurePath
* @return
*/
public static boolean copyTemplate(String templatePath, String featurePath) {
File af = new File(templatePath);
File bf = new File(featurePath);
FileInputStream is = null;
FileOutputStream os = null;
if (!bf.exists()) {
try {
bf.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
is = new FileInputStream(af);
os = new FileOutputStream(bf);
byte b[] = new byte[1024];
int len;
try {
len = is.read(b);
while (len != -1) {
os.write(b, 0, len);
len = is.read(b);
}
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
try {
if (is != null) is.close();
if (os != null) os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
public static boolean createFeatureFileProc(String fileDir,String sheetName,String templateName){
ExcelManage em = new ExcelManage();
List<ReqestEntity> list = em.readFromExcel(fileDir,sheetName);
Set<String> featureNameSet = new HashSet<>();
for (ReqestEntity r : list){
String featureName =r.getUrl().split(":")[2];
featureName =featureName.replace("/","_").substring(6); featureNameSet.add(featureName);
}
for (String fn : featureNameSet){
createFile(fn,templateName);
}
return true;
} public static void main(String[] args) {
// String inputFilePath = "E:\\xcollection\\collectioncucumbertest\\src\\test\\resources\\template\\featuretemplate.txt";
// String outputFilePath = "E:\\xcollection\\collectioncucumbertest\\src\\test\\resources\\feature\\" + "myfile.feature";
// boolean b = copyTemplate(inputFilePath,outputFilePath);
// System.out.println("b:"+b);
createFeatureFileProc("fiddlerData/test2.xls","sheet1","featuretemplate.txt");
}
}
模板文件内容:
  @tag1
Scenario Outline:请重写场景描述 # Given prepare data for url "/address/create" with case "<caseRow>" SQL
# | staffCode | <staffCode> |
When I use "fiddlerData/test2.xls" file case-"<index>" to send request
Then the response status should be "200"
And the JSON response "$.success" equals "<success>"
And the JSON response "$.message" equals "<message>"
And the JSON response "$.msgCode" equals "<msgCode>"
And the JSON response "$.attributes" start with "null"
# And the JSON response "$.model.id" start with "<id>"
Examples:
|index| success|message| msgCode |
|1 | true |操作成功|0000|
生成的feature文件位置:

基于cucumber接口测试框架的扩展——测试框架总结之cucumber

每个feature文件的内容跟模板一致。

stepsDefines类中定义了feature的代码实现和断言:

package testcases;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.restdriver.serverdriver.RestServerDriver;
import com.github.restdriver.serverdriver.http.Header;
import com.github.restdriver.serverdriver.http.response.Response;
import com.jayway.jsonpath.JsonPath;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When; import static com.github.restdriver.serverdriver.RestServerDriver.body;
import static com.github.restdriver.serverdriver.RestServerDriver.header;
import static testcases.Common.hostUrl; import fiddler.module.ExcelManage;
import fiddler.module.ReqestEntity;
import util.ReadTxtFile;import jdbc.DBIdConstant;
import jdbc.ManagedSqlUtil;
import org.junit.Assert; import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*; /**
* Created by linyanghua on 2017/8/28.
*/
public class StepsDefines {
Response response = null;
String paramters="";
ReqestEntity req = null;
@When("^I send a GET request to \"(.*?)\"$")
public void getRequest(String path,Map<String,String> paramMap) throws InterruptedException {
String cookie =Common.getcookie(Common.loginurl,Common.loginparam);
Header header_cookie = header("Cookie",cookie);
paramters =getStringParams(paramMap);
response = RestServerDriver.get(hostUrl+path+"?"+paramters, header_cookie);
} @When("^I send a POST request to \"(.*?)\" with text$" )
public void postRequest(String apiPath,Map<String,String> paramMap) throws Throwable {
String cookie =Common.getcookie(Common.loginurl,Common.loginparam);
Header header_cookie = header("Cookie",cookie);
paramters =getStringParams(paramMap);
response = RestServerDriver.post(hostUrl+apiPath,body(paramters, Common.applicationContext), header_cookie);
} @When("^I send a POST request to \"(.*?)\"$" )
public void postRequest(String apiPath) throws Throwable {
String cookie =Common.getcookie(Common.loginurl,Common.loginparam);
Header header_cookie = header("Cookie",cookie);
response = RestServerDriver.post(hostUrl+apiPath,header_cookie);
}
/**
* @param apiPath
* @param paramMap
* @throws InterruptedException
* @return Response
*/ @When("^I send a POST request to \"(.*?)\" with json$")
public void postRequestWithJson(String apiPath, Map<String,String> paramMap) throws InterruptedException {
String cookie =Common.getcookie(Common.loginurl,Common.loginparam);
Header header_cookie = header("Cookie",cookie);
paramters =getJSONParams(paramMap);
response = RestServerDriver.post(hostUrl+apiPath,body(paramters, Common.applicationJson), header_cookie);
} @When("^I use a \"(.*?)\" file to send a POST request to \"(.*?)\"$")
public void postRequestWihtFile(String fileName, String path) throws InterruptedException {
String cookie =Common.getcookie(Common.loginurl,Common.loginparam);
Header header_cookie = header("Cookie",cookie);
String paramters = ReadTxtFile.readtxtFile(fileName);
response = RestServerDriver.post(hostUrl+path,body(paramters, Common.applicationJson), header_cookie);
} @Then("^the JSON response equals$")
public void assertResponseJson(String expected) {
String responseJson = response.asText();
assertJsonEquals(responseJson, expected);
} @Then("^the JSON response equals json file \"(.*?)\"$")
public void theJSONResponseEqualsJsonFile(String fileName) {
String responseJson = response.asText();
String fileJson = ReadTxtFile.readtxtFile(fileName);
assertJsonEquals(responseJson, fileJson);
} @Then("^the response status should be \"(\\d{3})\"$")
public void assertStatusCode(int statusCode) {
int responseStatusCode =response.getStatusCode();
Assert.assertEquals(statusCode,responseStatusCode);
} @Then("^the JSON response \"(.*?)\" equals \"(.*?)\"$")
public void assertEquals(String str, String expected) {
String jsonValue = getJsonPathValue(response, str);
Assert.assertEquals(expected,jsonValue);
} @Then("^the JSON response \"(.*?)\" should be not null$")
public void assertNotNull(String str) {
String jsonValue = getJsonPathValue(response, str);
Assert.assertNotNull(jsonValue);
} @Then("^the JSON response \"(.*?)\" start with \"(.*?)\"$")
public void assertStartWith(String str, String start) {
String jsonValue = getJsonPathValue(response, str);
boolean act_start=jsonValue.startsWith(start);
Assert.assertEquals(act_start, true);
}
@Then("^the JSON response \"(.*?)\" end with \"(.*?)\"$")
public void assertEndWith(String str, String end) {
String jsonValue = getJsonPathValue(response, str);
Assert.assertEquals(jsonValue.endsWith(end),true);
} @Then("^the JSON response \"(.*?)\" include \"(.*?)\"$")
public void assertInclude(String str, String include) {
String jsonValue = getJsonPathValue(response, str);
Assert.assertEquals(jsonValue.contains(include),true);
}
@Then("^the response model \"(.*?)\" size equals \"(.*?)\"$")
public void assertModelJsonNode(String jsonPath,int size) {
List medelJsonArray = getJsonArray(response, jsonPath);
Assert.assertEquals(size,medelJsonArray.size());
} @Then("^the JSON response \"(.*?)\" size more than \"(.*?)\"$")
public void assertSizeMoreThan(String jsonPath,int size) {
List medelJsonArray = getJsonArray(response, jsonPath);
Assert.assertTrue(medelJsonArray.size()>size);
} @Then("^the response jsonArraylist \"(.*?)\" equals \"(.*?)\"$")
public void assertJsonArrayList(String jsonPath,String str) {
String medelJsonArray = getJsonArray(response, jsonPath).toString().replace("\"","");
String exceptedValue=str;
Assert.assertEquals(str.equals(exceptedValue),true);
} /**
* @param method
* @param path
* @param contentType
* @param map
* @return
* @throws Throwable
*/
@When("^I send a \"(.*?)\" request to \"(.*?)\" with contentType:\"(.*?)\"$" )
public Response sendRequestStep(String method,String path, String contentType, Map<String,String> map)throws Throwable{
Response response = sendRequest(method, path, contentType,map);
return response;
} /**
* @param path
* @param casename
* @param map
* @throws Throwable
*/
@Given("^prepare data for url \"([^\"]*)\" with case \"([^\"]*)\" SQL$")
public void prepareData(String path,String casename,Map<String,String> map) throws Throwable {
if (path.equals("case/List/phone")){
InitDBTestDataFactory.initCaseListPhoneDB(map,casename);
}else if(path.equals("/caseCollection/list")){
InitDBTestDataFactory.initCaseCollectlistDB(map,casename);
}else if(path.equals("/caseCollection/list")){
InitDBTestDataFactory.initCaseCollectlistDB(map,casename);
}else if(path.equals("/caseCollection/insert")){
InitDBTestDataFactory.initCaseCollectInstertDB(map,casename);
}else if(path.equals("/caseCollection/update")){
InitDBTestDataFactory.initCaseCollectUpdateDB(map,casename);
}else if(path.equals("/caseCollection/delete")){
InitDBTestDataFactory.initCaseCollectdeleteDB(map,casename);
}else if(path.equals("/staff/save")){
InitDBTestDataFactory.initStaffSaveDB(map,casename);
}
}
/**
* @param jsonPath
* @param tableName
* @param propertiName
* @param map
* @throws Throwable
*/
@Then("^the JSON response \"([^\"]*)\" equals table \"([^\"]*)\" property \"([^\"]*)\"$")
public void theJSONResponseEqualsTable(String jsonPath, String tableName,String propertiName,Map<String,String> map) throws Throwable {
List<String> listA =selectCheckDataFromDB(tableName,map,propertiName);
List<String> listB = getJsonArray(response, jsonPath);
Collections.sort(listA);
Collections.sort(listB);
Assert.assertEquals(listA.size(),listB.size());
if (listA.size()!=0){
for (int i=0;i<listA.size();i++){
Assert.assertEquals(listA.get(i),listB.get(i));
}
}
}
public String getWhereSelectSql(Map<String,String> map){
Set keySet = map.keySet();
String where = "";
int i = 0;
for (Object keyName:keySet){
i++;
if (keySet.size()!=i){
where =where + keyName+"="+"\""+map.get(keyName)+"\""+" and ";
}else {
where = where + keyName+"="+"\""+map.get(keyName)+"\"";
}
}
return where;
} public List<String> selectCheckDataFromDB(String table,Map<String,String> map,String field1) throws SQLException{
String whereSql = getWhereSelectSql(map);
String selectSql = "select "+field1+" from "+table+" where "+whereSql+";";
ResultSet rs = ManagedSqlUtil.selectBySql(selectSql, DBIdConstant.collectonDB);
List list = new ArrayList<String>();
while(rs.next()){
list.add(rs.getInt(0));
}
return list;
} public void assertJsonEquals(String responseJson,String excepted){
JSONObject jsonContent = JSON.parseObject(responseJson);
JSONObject extContent = JSON.parseObject(excepted); boolean act_succ = JsonPath.read(jsonContent,"$.success");
boolean ext_succ = JsonPath.read(extContent,"$.success");
Assert.assertEquals(act_succ, ext_succ); String act_msg = JsonPath.read(jsonContent,"$.message");
String ext_msg = JsonPath.read(extContent,"$.message");
Assert.assertEquals(act_msg,ext_msg); String act_msgCode = JsonPath.read(jsonContent,"$.msgCode");
String ext_msgCode = JsonPath.read(extContent,"$.msgCode");
Assert.assertEquals(act_msgCode,ext_msgCode); String act_attr = JsonPath.read(jsonContent,"$.attributes");
String ext_attr = JsonPath.read(extContent,"$.attributes");
Assert.assertEquals(act_attr,ext_attr);
} public String getJsonPathValue(Response response,String jsonPath){
String str="";
String responesJson = response.asText();
JSONObject jsonContent = JSON.parseObject(responesJson);
if (JsonPath.read(jsonContent,jsonPath)!=null) {
str = JsonPath.read(jsonContent,jsonPath).toString();
return str;
}else {
return "null";
}
}
// 返回JSONArray中某个字段的所有值
public List getJsonArray(Response response, String jsonPath){
String responesJson = response.asText();
JSONObject jsonContent = JSON.parseObject(responesJson);
List list = JsonPath.read(jsonContent,jsonPath);
return list;
} /**
*
* @param map
* @return StringParams
*/ public static String getStringParams(Map<String,String> map){
Set keySet = map.keySet();
String paramters="";
int i = 0; for (Object keyName : keySet) {
i++;
if (map.get(keyName)!=null) {
if (keySet.size() != i) {
paramters = paramters + keyName + "=" + map.get(keyName) + "&";
} else {
paramters = paramters + keyName + "=" + map.get(keyName);
}
}
System.out.println("键名:" + paramters);
}
return paramters;
} /**
*
* @param map
* @return JSONParams
*/ public static String getJSONParams(Map<String,String> map){
Map jsonMap = new HashMap<String,String>();
Set keySet = map.keySet();
for (Object keyName:keySet){
if(map.get(keyName)!=null){
jsonMap.put(keyName,map.get(keyName));
}
}
String paramters = JSONObject.toJSONString(jsonMap);
return paramters;
} public Response sendRequest(String method,String path, String contentType, Map<String,String> map) throws Throwable {
if(method=="Post"&&contentType=="json"){
response = sendPostRequestWithJson(path,map);
}else if (method=="Post"&&contentType=="form"){
response = sendPostRequest(path,map);
}else if (method=="Get"){
response = sendGetRequest(path,map);
}
return response;
}
public Response sendGetRequest(String path,Map<String,String> paramMap) throws InterruptedException {
String cookie =Common.getcookie(Common.loginurl,Common.loginparam);
Header header_cookie = header("Cookie",cookie);
paramters =getStringParams(paramMap);
response = RestServerDriver.get(hostUrl+path+"?"+paramters, header_cookie);
return response;
}
public Response sendPostRequest(String apiPath,Map<String,String> paramMap) throws Throwable {
String cookie =Common.getcookie(Common.loginurl,Common.loginparam);
Header header_cookie = header("Cookie",cookie);
paramters =getStringParams(paramMap);
response = RestServerDriver.post(hostUrl+apiPath,body(paramters, Common.applicationContext), header_cookie);
return response;
}
public Response sendPostRequestWithJson(String apiPath, Map<String,String> paramMap) throws InterruptedException {
String cookie =Common.getcookie(Common.loginurl,Common.loginparam);
Header header_cookie = header("Cookie",cookie);
paramters =getJSONParams(paramMap);
response = RestServerDriver.post(hostUrl+apiPath,body(paramters, Common.applicationJson), header_cookie);
return response;
} public void waitResponse(Response response) throws InterruptedException {
int i;
for ( i=0;i<5;i++){
Thread.sleep(3000);
if (response!=null){
break;
} }
return ;
} @When("^I use \"([^\"]*)\" file case-\"([^\"]*)\" to send request$")
public void iUseAFileToSendRequest(String fileDir,int index) throws Throwable {
String cookie = Common.getcookie(Common.loginurl, Common.loginparam);
Header header_cookie = header("Cookie", cookie);
req= ExcelManage.getRequestData(fileDir,"sheet1",index);
if (req != null) {
if (req.getMethod().equals("GET") && req.getParams() != "") {
response = RestServerDriver.get(req.getUrl() + "?" + req.getParams(), header_cookie);
}
if (req.getMethod().equals("GET") && req.getParams() == "") {
response = RestServerDriver.get(req.getUrl(), header_cookie);
}
if (req.getMethod().equals("POST") && req.getContentType().trim().equals("application/x-www-form-urlencoded")) {
response = RestServerDriver.post(req.getUrl(), body(req.getParams(), Common.applicationContext), header_cookie);
}
if (req.getMethod().equals("POST") && req.getContentType().trim().equals( "application/json")) {
response = RestServerDriver.post(req.getUrl(), body(req.getParams(), Common.applicationJson), header_cookie);
}
}
}
public static void main(String[] args) throws Throwable {
StepsDefines sd = new StepsDefines();
sd.iUseAFileToSendRequest("fiddlerData/test2.xls",1);
}

4、修改feature文件,指定excel中的执行数据,添加预期值。

index值取1,2,3,4,5,6时对应excel中的case-1,case-2,case-3,...对应的测试数据

5、执行feature文件,使用cucumber report在jenkins中执行和统计结果。

1)新建执行类AppTest ,右键run即可执行。

package testcases;

import cucumber.api.CucumberOptions;
import cucumber.api.junit.Cucumber;
import org.junit.runner.RunWith; /**
* Created by linyanghua on 2017/8/12.
*/
@RunWith(Cucumber.class)
@CucumberOptions(
features = {"classpath:feature/"},//feature文件所在目录
glue = {"testcases"},
// tags = { "@tag" },
plugin ={"pretty",
"html:target/cucumber-report/",
"json:target/cucumber-report/cucumber.json"}
) //@CucumberOptions(plugin={"pretty", "html:target/cucumber",
// "json:target/cucumber.json", "junit:target/junit"},
// features = "classpath:feature/staffTest.feature")
public class AppTest {
} 2)集成Jenkins cucumber report 生成报告
执行类名为APPTest

基于cucumber接口测试框架的扩展——测试框架总结之cucumber

2)源码库:

基于cucumber接口测试框架的扩展——测试框架总结之cucumber

3)构建:

基于cucumber接口测试框架的扩展——测试框架总结之cucumber

基于cucumber接口测试框架的扩展——测试框架总结之cucumber

4)构建后报告:

基于cucumber接口测试框架的扩展——测试框架总结之cucumber

基于cucumber接口测试框架的扩展——测试框架总结之cucumber

构建后的报告:

基于cucumber接口测试框架的扩展——测试框架总结之cucumber

POM依赖包:

<plugin>
<groupId>net.masterthought</groupId>
<artifactId>maven-cucumber-reporting</artifactId>
<version>3.9.0</version>
<executions>
<execution>
<id>execution</id>
<phase>verify</phase>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<projectName>collectioncucumbertest</projectName>
<outputDirectory>${project.build.directory}/cucumber-html-reports</outputDirectory>
<cucumberOutput>${project.build.directory}/cucumber.json</cucumberOutput>
<parallelTesting>false</parallelTesting>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>net.masterthought</groupId>
<artifactId>maven-cucumber-reporting</artifactId>
<version>3.9.0</version>
</dependency>
<!--Json-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.1.15</version>
</dependency> <dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.2.0</version>
</dependency>
<!--读取Excel-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.10-FINAL</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.185</version>
</dependency>
<dependency>
<groupId>com.github.rest-driver</groupId>
<artifactId>rest-server-driver</artifactId>
<version>1.1.43</version>
</dependency>
<dependency> <!--mysql 数据库 driver jar-->
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/info.cukes/cucumber-jvm -->
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-jvm</artifactId>
<version>1.2.4</version>
<type>pom</type>
</dependency>
    <groupId>info.cukes</groupId>
<artifactId>cucumber-core</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-java</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-junit</artifactId>
<version>1.2.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/info.cukes/cucumber-html -->
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-html</artifactId>
<version>0.2.3</version>
</dependency>
上一篇:[Python]读写文件方法


下一篇:『TensorFlow』0.x_&_1.x版本框架改动汇总