├── src ├── main │ ├── filters │ │ ├── filter_uat.properties │ │ └── filter_sit.properties │ ├── resources │ │ ├── PublicArgs.properties │ │ ├── env.properties │ │ ├── testCase │ │ │ ├── OpenYg.xls │ │ │ └── SendmsgYg.xls │ │ ├── sql │ │ │ ├── tearDownSQL.sql │ │ │ └── setUpSQL.sql │ │ ├── iiaccount_db.properties │ │ ├── temp │ │ │ ├── environment.properties │ │ │ └── categories.json │ │ ├── testngXml │ │ │ └── testSuite_YG.xml │ │ ├── databaseAssert │ │ │ └── OpenYg.xml │ │ └── log4j.properties │ └── java │ │ └── com │ │ └── iiaccount │ │ ├── exceptions │ │ └── ErrorException.java │ │ ├── functioins │ │ ├── Function.java │ │ ├── PhoneFunction.java │ │ ├── RandomFunction.java │ │ └── IdNoFunction.java │ │ ├── asserts │ │ ├── ConstVariable.java │ │ ├── ContainAssert.java │ │ ├── Asserts.java │ │ ├── RespondAssertForJson.java │ │ └── DatabaseAssert.java │ │ ├── utils │ │ ├── FileUtil.java │ │ ├── GainSqlUtil.java │ │ ├── StringUtil.java │ │ ├── PhoneUtil.java │ │ ├── IsNumericUtil.java │ │ ├── RandomUtil.java │ │ ├── FunctionUtil.java │ │ ├── GetFileMess.java │ │ ├── WritePropertiesUtil.java │ │ ├── ClassFinder.java │ │ ├── IdNoUtil.java │ │ ├── AssembledMessForJson.java │ │ └── SplitXmlForCaseNo.java │ │ ├── listener │ │ ├── FailedRetry.java │ │ └── FailedRetryListener.java │ │ ├── dao │ │ ├── ExcutSqlFile.java │ │ ├── CRUDData.java │ │ └── DBDPConnection.java │ │ ├── common │ │ ├── RunCaseJson.java │ │ └── SetUpTearDown.java │ │ ├── data │ │ ├── DataProviders.java │ │ └── DataBuilders.java │ │ └── report │ │ └── TestStep.java └── test │ └── java │ └── com │ └── iiaccout │ └── yiguan │ ├── SendmsgYg.java │ └── OpenYg.java ├── .idea ├── vcs.xml └── compiler.xml ├── README.md └── pom.xml /src/main/filters/filter_uat.properties: -------------------------------------------------------------------------------- 1 | Environment=uat -------------------------------------------------------------------------------- /src/main/resources/PublicArgs.properties: -------------------------------------------------------------------------------- 1 | #接口公共入参,以英文逗号隔开 2 | pubArgsYg=api,version -------------------------------------------------------------------------------- /src/main/resources/env.properties: -------------------------------------------------------------------------------- 1 | Environment=${Environment} 2 | baseURI=${baseURI} 3 | basePath=${basePath} 4 | port=${port} -------------------------------------------------------------------------------- /src/main/resources/testCase/OpenYg.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tomandy08/ApiAutoTest/HEAD/src/main/resources/testCase/OpenYg.xls -------------------------------------------------------------------------------- /src/main/resources/testCase/SendmsgYg.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tomandy08/ApiAutoTest/HEAD/src/main/resources/testCase/SendmsgYg.xls -------------------------------------------------------------------------------- /src/main/resources/sql/tearDownSQL.sql: -------------------------------------------------------------------------------- 1 | -- 案例执行后数据清理 2 | delete from account where userflag = '2'; 3 | 4 | delete from bindcard where userflag = '2'; -------------------------------------------------------------------------------- /src/main/resources/iiaccount_db.properties: -------------------------------------------------------------------------------- 1 | #数据库配置 2 | DB_Name=${DB_Name} 3 | DB_Password=${DB_Password} 4 | DB_IP=${DB_IP} 5 | #数据池配置 6 | DP_Name=${DP_Name} 7 | DP_Password=${DP_Password} 8 | DP_IP=${DP_IP} -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/exceptions/ErrorException.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.exceptions; 2 | 3 | public class ErrorException extends Exception { 4 | 5 | public ErrorException(String msg){ 6 | super(msg); //父类构造函数 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/resources/temp/environment.properties: -------------------------------------------------------------------------------- 1 | #Thu Nov 01 17:15:00 CST 2018 2 | DatabaseLoginIP=10.232.132.6\:1521\:p2b 3 | DatabaseLoginName=op_iiaccount 4 | DatabaseLoginPass=op_iiaccount 5 | baseURI=http\://10.232.138.107\:8187/v1/gateway.do 6 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/functioins/Function.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.functioins; 2 | 3 | /* 4 | *自定义函数,比如生成随机数函数、加密函数等 5 | */ 6 | public interface Function { 7 | 8 | String excute(String args[]); 9 | 10 | String getReferenceKey(); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/filters/filter_sit.properties: -------------------------------------------------------------------------------- 1 | #SIT环境 2 | Environment=sit 3 | baseURI=http://10.10.10.10 4 | basePath=v1/gateway.do 5 | port=8187 6 | #数据库配置oracle 7 | DB_Name=user 8 | DB_Password=pass 9 | DB_IP=10.10.10.10:1521:p2b 10 | #数据池配置mysql 11 | DP_Name=user 12 | DP_Password=user 13 | DP_IP=10.10.10.10:3306/database -------------------------------------------------------------------------------- /src/main/resources/temp/categories.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "name": "Ignored tests", 3 | "matchedStatuses": ["skipped"] 4 | }, 5 | { 6 | "name": "Product defects", 7 | "matchedStatuses": ["failed"] 8 | }, 9 | { 10 | "name": "Test defects", 11 | "matchedStatuses": ["broken"] 12 | } 13 | ] -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/asserts/ConstVariable.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.asserts; 2 | 3 | /* 4 | *定义公共变量 5 | */ 6 | public class ConstVariable { 7 | 8 | //数据库断言 临时xml文件生成路径,根路径为D:\IntelliJ_IDEA_workspace\ApiAutoTest 9 | //environment.properties路径 10 | public static final String xmlOutputPath = ".\\src\\main\\resources\\temp"; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/functioins/PhoneFunction.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.functioins; 2 | 3 | import com.iiaccount.utils.PhoneUtil; 4 | 5 | /* 6 | *生成11位手机号函数 7 | */ 8 | public class PhoneFunction implements Function{ 9 | 10 | @Override 11 | public String excute(String[] args) { 12 | return PhoneUtil.getPhone(); //返回11位手机号 13 | } 14 | 15 | @Override 16 | public String getReferenceKey() { 17 | return "phone"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/resources/testngXml/testSuite_YG.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/resources/sql/setUpSQL.sql: -------------------------------------------------------------------------------- 1 | --案例执行前参数准备 2 | -- 商户维护,内部商户:2001(正常),2002(正常),2003(正常),2004(正常),2005(失效) 3 | insert when (not exists (select merchant_id from merchant where merchant_id = '2001')) then into merchant 4 | values ('2001', '商户-正常', 1, to_date('06-06-2017', 'dd-mm-yyyy'), to_date('06-06-2017', 'dd-mm-yyyy')) select '2001' from dual; 5 | 6 | insert when (not exists (select merchant_id from merchant where merchant_id = '2001')) then into merchant 7 | values ('2005', '商户-失效', 2, to_date('06-06-2017', 'dd-mm-yyyy'), to_date('06-06-2017', 'dd-mm-yyyy')) select '2001' from dual; -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/asserts/ContainAssert.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.asserts; 2 | 3 | import org.testng.Assert; 4 | 5 | /* 6 | *判断预期结果是否包含在响应报文中 7 | */ 8 | public class ContainAssert { 9 | 10 | public static boolean contains(String source, String search) { 11 | return source.contains(search); 12 | /* 13 | Assert.assertTrue(source.contains(search), 14 | String.format("期待'%s'包含'%s',实际为不包含.", source, search)); 15 | */ 16 | } 17 | 18 | public static void notContains(String source, String search) { 19 | Assert.assertFalse(source.contains(search), 20 | String.format("期待'%s'不包含'%s',实际为包含.", source, search)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/utils/FileUtil.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.utils; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.FileOutputStream; 6 | import java.io.IOException; 7 | 8 | public class FileUtil { 9 | 10 | /* 11 | *移动文件位置 12 | */ 13 | public static void copyFiles(File fromFile, File toFile) throws IOException { 14 | FileInputStream ins = new FileInputStream(fromFile); 15 | FileOutputStream out = new FileOutputStream(toFile); 16 | byte[] b = new byte[1024]; 17 | int n=0; 18 | while((n=ins.read(b))!=-1){ 19 | out.write(b, 0, n); 20 | } 21 | 22 | ins.close(); 23 | out.close(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/utils/GainSqlUtil.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.utils; 2 | 3 | import java.util.Map; 4 | import java.util.Set; 5 | 6 | public class GainSqlUtil { 7 | 8 | /* 9 | *拼装sql语句 10 | */ 11 | public static String gainSqlFromMap(String tableName, 12 | Map map) { 13 | String sql = "select * from " + tableName + " where 1=1 "; 14 | Set set = map.keySet(); 15 | 16 | for (String keyName : set) { 17 | String value = map.get(keyName); 18 | if (value != null) { 19 | sql = sql + " and " + keyName + " = " + "'" + value + "'"; 20 | } 21 | } 22 | return sql; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/utils/StringUtil.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.utils; 2 | 3 | public class StringUtil { 4 | 5 | /** 6 | * 7 | * @param sourceStr 待替换字符串 8 | * @param matchStr 匹配字符串 9 | * @param replaceStr 目标替换字符串 10 | * @return 11 | */ 12 | public static String replaceFirst(String sourceStr,String matchStr,String replaceStr){ 13 | int index = sourceStr.indexOf(matchStr); 14 | int matLength = matchStr.length(); 15 | int sourLength = sourceStr.length(); 16 | String beginStr = sourceStr.substring(0,index); 17 | String endStr = sourceStr.substring(index+matLength,sourLength); 18 | sourceStr = beginStr+replaceStr+endStr; 19 | return sourceStr; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/listener/FailedRetry.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.listener; 2 | 3 | import com.iiaccount.exceptions.ErrorException; 4 | import org.testng.IRetryAnalyzer; 5 | import org.testng.ITestResult; 6 | 7 | /* 8 | *案例执行抛异常则重试 9 | */ 10 | public class FailedRetry implements IRetryAnalyzer { 11 | private int retryCount = 1; 12 | private static final int maxRetryCount = 2; 13 | 14 | public boolean retry(ITestResult iTestResult) { 15 | //抛出异常则重跑失败案例 16 | if (iTestResult.getThrowable() instanceof ErrorException && retryCount % maxRetryCount != 0) { 17 | retryCount++; 18 | return true; 19 | } else { 20 | retryCount = 1; 21 | return false; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/utils/PhoneUtil.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.utils; 2 | 3 | public class PhoneUtil { 4 | 5 | private static String[] telFirst="134,135,136,137,138,139,150,151,152,157,158,159,130,131,132,155,156,133,153,185,186".split(","); 6 | 7 | public static int getNum(int start,int end) { 8 | return (int)(Math.random()*(end-start+1)+start); 9 | } 10 | 11 | /** 12 | * 返回手机号码 13 | */ 14 | public static String getPhone() { 15 | int index=getNum(0,telFirst.length-1); 16 | String first=telFirst[index]; 17 | String second=String.valueOf(getNum(1,888)+10000).substring(1); 18 | String third=String.valueOf(getNum(1,9100)+10000).substring(1); 19 | return first+second+third; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/functioins/RandomFunction.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.functioins; 2 | 3 | import com.iiaccount.utils.RandomUtil; 4 | 5 | /* 6 | *生成随机数函数,默认为6位 7 | */ 8 | public class RandomFunction implements Function { 9 | 10 | @Override 11 | public String excute(String[] args) { 12 | int len = args.length; 13 | int length = 6;// 默认为6 14 | boolean flag = false;// 默认为false,true为纯数字 15 | if (len > 0) {// 第一个参数字符串长度 16 | length = Integer.valueOf(args[0]); 17 | } 18 | if (len > 1) {// 第二个参数是否纯字符串 19 | flag = Boolean.valueOf(args[1]); 20 | } 21 | return RandomUtil.getRandom(length, flag); 22 | } 23 | 24 | @Override 25 | public String getReferenceKey() { 26 | return "random"; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/databaseAssert/OpenYg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ACCOUNT_NO 7 | CUST_ID 8 | MERCHANT_ID 9 | 1 10 |
11 |
12 | 13 | 14 | 15 | ACCOUNT_NO 16 | CUST_ID 17 | MERCHANT_ID 18 | 2 19 |
20 |
21 | 22 |
23 | 24 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/utils/IsNumericUtil.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.utils; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.regex.Matcher; 5 | import java.util.regex.Pattern; 6 | 7 | public class IsNumericUtil { 8 | 9 | /* 10 | * 判断送入的字符串是否为数值(包括正数,负数,含小数位的数) //add by lrb 20170927 11 | */ 12 | public static boolean isNumeric(String str) { 13 | // 该正则表达式可以匹配所有的数字 包括负数 14 | Pattern pattern = Pattern.compile("-?[0-9]+\\.?[0-9]*"); 15 | String bigStr; 16 | try { 17 | bigStr = new BigDecimal(str).toString(); 18 | } catch (Exception e) { 19 | return false;//异常 说明包含非数字。 20 | } 21 | 22 | Matcher isNum = pattern.matcher(bigStr); // matcher是全匹配 23 | if (!isNum.matches()) { 24 | return false; 25 | } 26 | return true; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/functioins/IdNoFunction.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.functioins; 2 | 3 | import com.iiaccount.utils.RegisterLocationUtil; 4 | 5 | import static com.iiaccount.utils.IdNoUtil.*; 6 | 7 | /* 8 | *生成18位身份证号码函数 9 | */ 10 | public class IdNoFunction implements Function { 11 | @Override 12 | public String excute(String[] args) { 13 | StringBuffer strBuffer = new StringBuffer(); 14 | strBuffer.append(randomLocationCode(RegisterLocationUtil.registerLocation())); 15 | strBuffer.append(randomBirthday()); 16 | strBuffer.append(randomCode()); 17 | String eighteenth = verificationCode(strBuffer.toString()); 18 | strBuffer.append(eighteenth); 19 | return strBuffer.toString(); //返回18位身份证号 20 | } 21 | 22 | @Override 23 | public String getReferenceKey() { 24 | return "idno"; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/listener/FailedRetryListener.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.listener; 2 | 3 | import org.testng.IAnnotationTransformer; 4 | import org.testng.IRetryAnalyzer; 5 | import org.testng.annotations.ITestAnnotation; 6 | 7 | import java.lang.reflect.Constructor; 8 | import java.lang.reflect.Field; 9 | import java.lang.reflect.Method; 10 | import java.lang.reflect.Parameter; 11 | 12 | /* 13 | *实现IAnnotationTransformer接口,修改@Test的retryAnalyzer属性 14 | */ 15 | public class FailedRetryListener implements IAnnotationTransformer { 16 | public void transform(ITestAnnotation iTestAnnotation, Class aClass, Constructor constructor, Method method) { 17 | { 18 | IRetryAnalyzer retry = iTestAnnotation.getRetryAnalyzer(); 19 | if (retry == null) { 20 | iTestAnnotation.setRetryAnalyzer(FailedRetry.class); 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/dao/ExcutSqlFile.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.dao; 2 | 3 | import com.iiaccount.utils.GetFileMess; 4 | import org.apache.ibatis.io.Resources; 5 | import org.apache.ibatis.jdbc.ScriptRunner; 6 | import org.testng.annotations.Test; 7 | 8 | import java.io.IOException; 9 | import java.nio.charset.Charset; 10 | import java.sql.Connection; 11 | import java.sql.SQLException; 12 | 13 | //执行sql文件语句 14 | public class ExcutSqlFile { 15 | 16 | public static void excute(String fileName) throws SQLException, IOException, ClassNotFoundException { 17 | Connection conn = DBDPConnection.getDBConnection(); 18 | ScriptRunner runner = new ScriptRunner(conn); 19 | Resources.setCharset(Charset.forName("UTF-8")); 20 | runner.setDelimiter(";"); //语句结束符号设置 21 | runner.setLogWriter(null);//设置是否输出日志 22 | 23 | //案例执行前的参数维护 24 | runner.runScript(Resources.getResourceAsReader("./sql/"+ fileName +".sql")); 25 | runner.closeConnection(); 26 | conn.close(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/utils/RandomUtil.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.utils; 2 | 3 | import java.util.Random; 4 | 5 | public class RandomUtil { 6 | 7 | public static String randomBase = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";//字母和数字 8 | public static String randomNumberBase = "0123456789"; 9 | 10 | public static Random random = new Random(); 11 | 12 | public static String getRandom(int length, boolean onlyNumber) { 13 | String base; 14 | if (onlyNumber) { 15 | base = randomNumberBase; 16 | } else { 17 | base = randomBase; 18 | } 19 | 20 | StringBuilder sb = new StringBuilder(); 21 | for (int i = 0; i < length; i++) { 22 | char chr; 23 | do { 24 | int number = random.nextInt(base.length()); 25 | chr = base.charAt(number); 26 | } while (i==0&&chr=='0') ;//第一个字符不能为0, 27 | 28 | sb.append(chr); 29 | } 30 | return sb.toString(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/com/iiaccout/yiguan/SendmsgYg.java: -------------------------------------------------------------------------------- 1 | package com.iiaccout.yiguan; 2 | 3 | import com.iiaccount.common.RunCaseJson; 4 | import com.iiaccount.common.SetUpTearDown; 5 | import com.iiaccount.data.DataProviders; 6 | import io.qameta.allure.Feature; 7 | import io.qameta.allure.Story; 8 | import io.restassured.response.Response; 9 | import org.testng.annotations.Test; 10 | 11 | import java.io.IOException; 12 | import java.sql.SQLException; 13 | 14 | import static com.iiaccount.asserts.Asserts.asserts; 15 | 16 | /* 17 | *一贯短信发送接口 18 | * 环境参数在SetUpTearDown 父类定义 19 | */ 20 | @Feature("分类账户改造") 21 | public class SendmsgYg extends SetUpTearDown { 22 | 23 | @Story("发送短信") 24 | @Test(dataProvider = "dataprovider", dataProviderClass = DataProviders.class, 25 | description = "发送短信") 26 | public void runCase(String caseMess, String bodyString) throws IOException, SQLException, ClassNotFoundException { 27 | 28 | //发送请求 29 | Response response = RunCaseJson.runCase(bodyString, "post"); 30 | 31 | //只进行响应报文断言 32 | asserts(caseMess, bodyString, response.asString(), "", null); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=INFO, stdout, D , E 2 | 3 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 4 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 5 | log4j.appender.stdout.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [ %C.%M(%L) ] - [ %p ] %m%n 6 | 7 | # 文件达到指定大小的时候产生一个新的文件 8 | log4j.appender.D=org.apache.log4j.DailyRollingFileAppender 9 | # TODO 部署时,修改为指定路径,done 10 | log4j.appender.D.File=D:/tools/ApiAutoTesting/logs/apiAutoTest_debug.log 11 | log4j.appender.D.Append = true 12 | # 输出DEBUG级别以上的日志 13 | log4j.appender.D.Threshold = DEBUG 14 | log4j.appender.D.layout=org.apache.log4j.PatternLayout 15 | log4j.appender.D.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [ %C.%M(%L) ] - [ %p ] %m%n 16 | 17 | ### 保存异常信息到单独文件 ### 18 | log4j.appender.E = org.apache.log4j.DailyRollingFileAppender 19 | ## 异常日志文件名 20 | # TODO 部署时,修改为指定路径,done 21 | log4j.appender.E.File = D:/tools/ApiAutoTesting/logs/apiAutoTest_error.log 22 | log4j.appender.E.Append = true 23 | ## 只输出ERROR级别以上的日志!!! 24 | log4j.appender.E.Threshold = ERROR 25 | log4j.appender.E.layout = org.apache.log4j.PatternLayout 26 | log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %C.%M(%L) ] - [ %p ] %m%n -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/common/RunCaseJson.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.common; 2 | 3 | import io.restassured.RestAssured; 4 | import io.restassured.path.json.JsonPath; 5 | import io.restassured.response.Response; 6 | import ru.yandex.qatools.allure.annotations.Step; 7 | 8 | import static io.restassured.RestAssured.given; 9 | 10 | public class RunCaseJson { 11 | 12 | /* 13 | *post或get方式请求,返回响应报文(json格式) 14 | *@bodyString:json格式的请求报文体 15 | *@para:requestType post或get 16 | */ 17 | public static Response runCase(String bodyString,String requestType){ 18 | Response response = null; 19 | if(requestType.toLowerCase().equals("get")) 20 | response = given() 21 | .contentType("application/json;charset=UTF-8") 22 | .request() 23 | .body(bodyString) 24 | .get(); 25 | else 26 | response = given() 27 | .contentType("application/json;charset=UTF-8") 28 | .request() 29 | .body(bodyString) 30 | .post(); 31 | 32 | //打印格式化的参数 33 | //response.prettyPrint(); ////去掉部分日志 add by lrb 20181029 34 | 35 | return response; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/data/DataProviders.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.data; 2 | 3 | import com.iiaccount.utils.AssembledMessForJson; 4 | import jxl.read.biff.BiffException; 5 | import org.testng.annotations.DataProvider; 6 | 7 | import java.io.IOException; 8 | import java.lang.reflect.Method; 9 | import java.net.URISyntaxException; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | public class DataProviders { 14 | 15 | /* 16 | *map包含两部分json,key为caseNo等信息,value为接口入参 17 | */ 18 | @DataProvider(name = "dataprovider",parallel = true) 19 | public static Object[][] dataP(Method method) throws IOException, BiffException, URISyntaxException { 20 | String className = method.getDeclaringClass().getSimpleName(); //获取类名 21 | String caseFileName = className+".xls"; //测试案例名称为:类名.xls 22 | 23 | Object[][] objects = null; 24 | Map map = new HashMap(); 25 | map = AssembledMessForJson.assembleMess(caseFileName,""); //""表示读取所有的为Y的case 26 | objects = new Object[map.size()][2]; 27 | int i=0; 28 | for(Map.Entry entry : map.entrySet()){ 29 | objects[i][0] = entry.getKey(); 30 | objects[i][1] = entry.getValue(); 31 | i++; 32 | } 33 | map.clear(); //需清空map,否则案例会不断叠加 2018-10-19 add by lrb 34 | return objects; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/utils/FunctionUtil.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.utils; 2 | 3 | import com.iiaccount.functioins.Function; 4 | 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | public class FunctionUtil { 10 | 11 | //所有继承Function的类 12 | private static final Map> functionsMap = new HashMap>(); 13 | static { 14 | List> clazzes = ClassFinder.getAllAssignedClass(Function.class); 15 | clazzes.forEach((clazz) -> { 16 | try { 17 | // function 18 | Function tempFunc = (Function) clazz.newInstance(); 19 | String referenceKey = tempFunc.getReferenceKey(); 20 | 21 | if (referenceKey.length() > 0) { // ignore self 22 | functionsMap.put(referenceKey, tempFunc.getClass()); 23 | } 24 | } catch (Exception ex) { 25 | ex.printStackTrace(); 26 | } 27 | }); 28 | } 29 | 30 | public static boolean isFunction(String functionName){ 31 | return functionsMap.containsKey(functionName); 32 | } 33 | 34 | public static String getValue(String functionName,String[] args){ 35 | try { 36 | return functionsMap.get(functionName).newInstance().excute(args); 37 | } catch (Exception e) { 38 | e.printStackTrace(); 39 | return ""; 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ApiAutoTest 2 | 框架介绍详见文章:[基于TestNG+Rest Assured+Allure的接口自动化测试框架](https://www.jianshu.com/p/880f5eeba016) 3 | # 约定规则 4 | - 数据库使用的是oracle,数据池使用mysql 5 | - 部署时根据需要配置日志输出路径,log4j.properties 6 | - 案例执行前依赖参数在sql/setUpSQL.sql维护 7 | - 案例执行后清理数据在sql/tearDownSQL.sql维护 8 | - resources/testCase及databaseAssert目录下的文件名与test测试类类名保持一致 9 | - 接口公共入参字段在resources/PublicArgs.properties文件配置 10 | - 自定义函数(后续根据需要扩展即可) 11 | 1、__random(var1,var2),生成var1长度的随机数 12 | 2、__phone():生成11位的手机号 13 | 3、__idno():生成18位身份证号 14 | - 预期结果断言支持json格式及包含函数(不区分大小写)可结合使用。 15 | 示例:$.status=200;$.data.accountNo=6230780501000877514;__contAIN(SUCCESS) 16 | - 目前暂只支持post,get方式的请求。 17 | - 案例抛异常则自动重试; 18 | - 对于无需数据库断言的接口,参考发SendmsgYg.java例子 19 | - 对于需要数据库断言的接口,则需将数据库断言xml的变量添加到map,参考OpenYg.java例子 20 | - 接口测试类需继承SetUpTearDown类。 21 | 示例:public class SendmsgYg extends SetUpTearDown {} 22 | - 目前提供以下五种供数方式,通过以下方式使用。 23 | 1、查询数据池供数:${dp.sql(select accountNo from account where status = 1)} 24 | 2、查询数据库供数:${db.sql(select accountNo from account_card where status = 1)} 25 | 3、先接口请求,然后提取响应报文供数:${SendmsgYg.case023.post($.data.code)} 或 ${SendmsgYg.case023.get($.data.code)} 26 | 4、先接口请求,然后查询数据库供数:${SendmsgYg.case023.post.db.sql(select accountNo from M_account_card where status = 1)} 27 | 5、自定义函数 28 | - 数据库及数据池操作通过以下方法来访问: 29 | 数据池查询:CRUDData.selectData(sql,"DP"),返回多个值的话只取第一个 30 | 数据池新增修改删除:CRUDData.cUDData(sql,"DP") 31 | 数据库查询:CRUDData.selectData(sql,"DB"),返回多个值的话只取第一个 32 | 数据库新增修改删除:CRUDData.cUDData(sql,"DB") 33 | - 其他功能后续可根据需要扩展,比如文件上传、加密、解密等。 34 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/report/TestStep.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.report; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import io.qameta.allure.Attachment; 5 | 6 | /* 7 | *测试步骤,测试报告中展现 8 | */ 9 | public class TestStep { 10 | 11 | public static void requestAndRespondBody(String URL, String Body,String Respond){ 12 | requestBody(URL,Body); 13 | respondBody(Respond); 14 | } 15 | 16 | @Attachment("请求报文") 17 | public static String requestBody(String URL, String body) { 18 | 19 | //格式化json串 20 | boolean prettyFormat = true; //格式化输出 21 | JSONObject jsonObject = JSONObject.parseObject(body); 22 | String str = JSONObject.toJSONString(jsonObject,prettyFormat); 23 | 24 | //报告展现请求报文 25 | return URL+"\n"+str; 26 | } 27 | 28 | @Attachment("响应报文") 29 | public static String respondBody(String respond) { 30 | 31 | //格式化json串 32 | boolean prettyFormat = true; //格式化输出 33 | JSONObject jsonObject = JSONObject.parseObject(respond); 34 | String str = JSONObject.toJSONString(jsonObject,prettyFormat); 35 | 36 | //报告展现响应报文 37 | return str; 38 | } 39 | 40 | @Attachment("数据库断言结果") 41 | public static StringBuffer databaseAssertResult(StringBuffer assertResult){ 42 | //报告展现数据库断言结果 43 | return assertResult; 44 | } 45 | 46 | @Attachment("响应报文断言结果") 47 | public static StringBuffer assertRespond(StringBuffer assertResult){ 48 | //报告展现数据库断言结果 49 | return assertResult; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/utils/GetFileMess.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.utils; 2 | 3 | import io.restassured.path.json.JsonPath; 4 | import org.apache.log4j.Logger; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.net.URL; 9 | import java.util.Properties; 10 | 11 | /* 12 | *读取文件的信息 13 | */ 14 | public class GetFileMess { 15 | private static Properties properties; 16 | Logger log = Logger.getLogger(GetFileMess.class); 17 | 18 | /* 19 | *根据properties文件主键获取对应的值 20 | */ 21 | public String getValue(String key,String propertiesFileName) throws IOException { 22 | InputStream stream = this.getClass().getClassLoader().getResourceAsStream("\\"+propertiesFileName); 23 | properties = new Properties(); 24 | properties.load(stream); 25 | String value = properties.getProperty(key); 26 | return value; 27 | } 28 | 29 | /* 30 | *获取文件路径 31 | */ 32 | public String getFilePath(String directory,String fileName) { 33 | 34 | try{ 35 | URL resource = this.getClass().getClassLoader().getResource(directory+"/"+fileName); 36 | String filePath = resource.toURI().getPath(); 37 | log.info("filePath:"+filePath); 38 | return filePath; 39 | }catch (Exception e){ 40 | log.info(e.getMessage()); 41 | return null; 42 | } 43 | } 44 | 45 | /* 46 | *获取测试案例数据的预期结果 47 | */ 48 | public String getCaseMessKeyValue(String caseMess,String key){ 49 | JsonPath caseMessToJson = new JsonPath(caseMess); 50 | String value = caseMessToJson.get(key); 51 | return value; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/dao/CRUDData.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.dao; 2 | 3 | import org.apache.log4j.Logger; 4 | 5 | import java.io.IOException; 6 | import java.sql.*; 7 | 8 | public class CRUDData { 9 | static Logger log = Logger.getLogger(CRUDData.class); 10 | 11 | static Connection connection = null; 12 | static ResultSet result = null; 13 | 14 | /* 15 | * 查询 16 | * 只针对select a from talbe 类型的语句; 17 | * select * from 或 select a,b from等暂时用不到,暂不支持; 18 | */ 19 | public static String selectData(String sql,String flag) throws SQLException, IOException, ClassNotFoundException { 20 | String value = ""; 21 | if(flag.toLowerCase().equals("db")) 22 | connection = DBDPConnection.getDBConnection(); 23 | else 24 | connection = DBDPConnection.getDPConnection(); 25 | 26 | Statement statement = connection.createStatement(); 27 | result = statement.executeQuery(sql); 28 | 29 | if (result.next()) { //如果存在多个值,只取第一个 30 | value = result.getString(1); 31 | } 32 | log.info(sql+"取值为:"+value); 33 | 34 | if (result != null) 35 | result.close(); 36 | if (connection != null) 37 | connection.close(); 38 | 39 | return value; 40 | } 41 | 42 | //新增,修改,删除 43 | public static void cUDData(String sql,String flag) throws SQLException, IOException, ClassNotFoundException { 44 | 45 | if(flag.toLowerCase().equals("db")) 46 | connection = DBDPConnection.getDBConnection(); 47 | else 48 | connection = DBDPConnection.getDPConnection(); 49 | 50 | PreparedStatement preparedStatement = connection.prepareStatement(sql); 51 | preparedStatement.executeUpdate(); 52 | 53 | log.info("新增/修改/删除语句:"+sql); 54 | 55 | if (connection != null){ 56 | preparedStatement.close(); 57 | connection.close(); 58 | } 59 | 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/dao/DBDPConnection.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.dao; 2 | 3 | import com.iiaccount.utils.GetFileMess; 4 | import org.apache.log4j.Logger; 5 | 6 | import java.io.IOException; 7 | import java.sql.Connection; 8 | import java.sql.DriverManager; 9 | import java.sql.SQLException; 10 | 11 | public class DBDPConnection { 12 | 13 | static Logger log = Logger.getLogger(DBDPConnection.class); 14 | 15 | static Connection dBConnection = null; //数据库链接 oracle 16 | static Connection dPConnection = null; //数据池链接 mysql 17 | 18 | //数据库连接,oracle 19 | public static Connection getDBConnection() throws ClassNotFoundException, IOException, SQLException { 20 | String database = "iiaccount_db.properties"; //通过filter配置数据库环境 21 | String url = new GetFileMess().getValue("DB_IP",database); 22 | String user = new GetFileMess().getValue("DB_Name",database); 23 | String password = new GetFileMess().getValue("DB_Password",database); 24 | 25 | Class.forName("oracle.jdbc.driver.OracleDriver"); 26 | url = "jdbc:oracle:thin:@" + url; 27 | log.info("数据库:"+url+"|"+user+"|"+password); 28 | dBConnection = DriverManager.getConnection(url, user, password); 29 | return dBConnection; 30 | } 31 | 32 | //数据池连接,mysql 33 | public static Connection getDPConnection() throws IOException, ClassNotFoundException, SQLException { 34 | String database = "iiaccount_db.properties"; //通过filter配置数据库环境 35 | String url = new GetFileMess().getValue("DP_IP",database); 36 | String user = new GetFileMess().getValue("DP_Name",database); 37 | String password = new GetFileMess().getValue("DP_Password",database); 38 | 39 | // 加载驱动程序 40 | Class.forName("com.mysql.jdbc.Driver"); 41 | url = "jdbc:mysql://" + url +"?characterEncoding=gb2312&serverTimezone=UTC"; //ip配置; 42 | log.info("数据池:"+url+"|"+user+"|"+password); 43 | dPConnection = DriverManager.getConnection(url, user, password); 44 | return dPConnection; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/com/iiaccout/yiguan/OpenYg.java: -------------------------------------------------------------------------------- 1 | package com.iiaccout.yiguan; 2 | 3 | import com.iiaccount.asserts.RespondAssertForJson; 4 | import com.iiaccount.common.RunCaseJson; 5 | import com.iiaccount.common.SetUpTearDown; 6 | import com.iiaccount.dao.CRUDData; 7 | import com.iiaccount.data.DataProviders; 8 | import io.qameta.allure.Feature; 9 | import io.qameta.allure.Story; 10 | import io.restassured.response.Response; 11 | import org.testng.annotations.Test; 12 | 13 | import java.io.IOException; 14 | import java.sql.SQLException; 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | import static com.iiaccount.asserts.Asserts.asserts; 19 | 20 | /* 21 | *一贯开立II/III类户 22 | * 环境参数在SetUpTearDown 父类定义 23 | */ 24 | @Feature("分类账户改造") 25 | public class OpenYg extends SetUpTearDown { 26 | 27 | @Story("分类账户开户") 28 | @Test(dataProvider = "dataprovider", dataProviderClass = DataProviders.class 29 | , description = "开户") 30 | public void runCase(String caseMess, String bodyString) throws IOException, SQLException, ClassNotFoundException { 31 | 32 | //发送请求 33 | Response response = RunCaseJson.runCase(bodyString, "post"); 34 | 35 | //如果需要数据库断言,此处添加断言文件变量的map映射 36 | //可通过调用封装的方法取值,比如查数据库、提取响应报文、调用接口等方式。 37 | Map map = new HashMap<>(); 38 | //查询数据库获取,取不到值返回"" 39 | String account = CRUDData.selectData("select accountNo from ACCOUNT where status =1","DB"); 40 | //提取响应报文,取不到值返回NULL 41 | String custId = RespondAssertForJson.getBuildValue(response.asString(),"$.data.custid"); 42 | //执行SendmsgYg接口的case023案例,然后提取响应报文的merchanId ,取不到值返回NULL 43 | String merchanId = RespondAssertForJson.getBuildValue("","${SendmsgYg.case023.post($.data.merchanId)}"); 44 | 45 | map.put("ACCOUNT_NO", account); 46 | map.put("CUST_ID", custId); 47 | map.put("MERCHANT_ID", merchanId); 48 | 49 | //断言(包含响应报文断言和数据库断言) 50 | String xmlFileName = this.getClass().getSimpleName(); //数据库断言xml文件名(与类名保持一致) 51 | asserts(caseMess, bodyString, response.asString(), xmlFileName, map); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/utils/WritePropertiesUtil.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.utils; 2 | 3 | import com.iiaccount.asserts.ConstVariable; 4 | 5 | import java.io.*; 6 | import java.util.Iterator; 7 | import java.util.Map; 8 | import java.util.Properties; 9 | 10 | import static com.iiaccount.utils.FileUtil.copyFiles; 11 | 12 | //写Properties文件 13 | public class WritePropertiesUtil { 14 | 15 | public static void writePropertiesFile(Map data) throws IOException { 16 | 17 | String filename = "environment.properties"; 18 | String filename1 = "categories.json"; 19 | // 生成xml文件 20 | File file = new File(ConstVariable.xmlOutputPath + "\\" + filename); //temp目录下 21 | 22 | // 判断是否存在,如果不存在,则创建 23 | if (!file.exists()) { 24 | file.createNewFile(); 25 | } 26 | String filepath = file.getAbsolutePath(); 27 | 28 | Properties props = new Properties(); 29 | InputStream is = WritePropertiesUtil.class.getClassLoader().getResourceAsStream(filepath); 30 | try { 31 | InputStream input = new FileInputStream(filepath); 32 | props.load(input); 33 | 34 | //在保存配置文件之前还需要取得该配置文件的输出流, 35 | if (data != null) { 36 | Iterator> iter = data.entrySet().iterator(); 37 | while (iter.hasNext()) { 38 | Map.Entry entry = iter.next(); 39 | props.setProperty(entry.getKey(), entry.getValue()); 40 | } 41 | } 42 | 43 | OutputStream out = new FileOutputStream(filepath); 44 | props.store(out, null); 45 | input.close(); 46 | out.close(); 47 | 48 | //拷贝environment.properties和categories.json文件至allure-results路径下 49 | File file1 = new File(ConstVariable.xmlOutputPath + "\\" + filename1); 50 | File toFile = new File(".\\target\\allure-results\\"+filename); 51 | File toFile1 = new File(".\\target\\allure-results\\"+filename1); 52 | copyFiles(file,toFile); 53 | copyFiles(file1,toFile1); 54 | 55 | } catch (IOException e) { 56 | e.printStackTrace(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/common/SetUpTearDown.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.common; 2 | 3 | import com.iiaccount.dao.ExcutSqlFile; 4 | import com.iiaccount.utils.GetFileMess; 5 | import io.restassured.RestAssured; 6 | import org.testng.annotations.AfterSuite; 7 | import org.testng.annotations.BeforeClass; 8 | import org.testng.annotations.BeforeSuite; 9 | 10 | import java.io.IOException; 11 | import java.sql.SQLException; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | import static com.iiaccount.utils.WritePropertiesUtil.writePropertiesFile; 16 | 17 | /* 18 | *父类的注解可以被子类继承,所有的环境变量在父类进行配置 19 | *根据testng.xml文件传入的参数来选择环境参数,默认为yg_env 20 | */ 21 | public class SetUpTearDown { 22 | 23 | @BeforeSuite 24 | public void dataSetUp() throws SQLException, IOException, ClassNotFoundException { 25 | ExcutSqlFile.excute("setUpSQL"); //案例执行前参数维护 26 | } 27 | 28 | //环境配置 29 | @BeforeClass 30 | public void envSetUp() { 31 | try { 32 | String system = "env.properties"; //环境由filter配置 33 | RestAssured.baseURI = new GetFileMess().getValue("baseURI", system); 34 | RestAssured.basePath = new GetFileMess().getValue("basePath", system); 35 | RestAssured.port = Integer.parseInt(new GetFileMess().getValue("port", system)); 36 | } catch (IOException e) { 37 | e.printStackTrace(); 38 | } 39 | } 40 | 41 | /* 42 | *创建environment.properties并放到allure-results目录下,测试报告展现 43 | */ 44 | @AfterSuite 45 | public void createEnvPropertiesForReport() throws IOException { 46 | Map data = new HashMap<>(); 47 | String database = "iiaccount_db.properties"; 48 | data.put("DatabaseLoginName", new GetFileMess().getValue("DB_Name", database)); 49 | data.put("DatabaseLoginPass", new GetFileMess().getValue("DB_Password", database)); 50 | data.put("DatabaseLoginIP", new GetFileMess().getValue("DB_IP", database)); 51 | data.put("baseURI", RestAssured.baseURI + ":" + RestAssured.port + "/" + RestAssured.basePath); 52 | 53 | writePropertiesFile(data); 54 | } 55 | 56 | @AfterSuite 57 | public void dataTearDown() throws SQLException, IOException, ClassNotFoundException { 58 | //案例执行结束后,对数据池的数据进行清理(删除或更新状态) 59 | ExcutSqlFile.excute("tearDownSQL"); //案例执行后数据清理 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/utils/ClassFinder.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.utils; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.io.UnsupportedEncodingException; 6 | import java.net.URLDecoder; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public class ClassFinder { 11 | 12 | static ClassLoader classloader = Thread.currentThread().getContextClassLoader(); 13 | /** 14 | * 获取同一路径下所有子类或接口实现类 15 | * 16 | * @param intf 17 | * @return 18 | * @throws IOException 19 | * @throws ClassNotFoundException 20 | */ 21 | public static List> getAllAssignedClass(Class cls) { 22 | List> classes = new ArrayList>(); 23 | for (Class c : getClasses(cls)) { 24 | if (cls.isAssignableFrom(c) && !cls.equals(c)) { 25 | classes.add(c); 26 | } 27 | } 28 | return classes; 29 | } 30 | 31 | /** 32 | * 取得当前类路径下的所有类 33 | * 34 | * @param cls 35 | * @return 36 | * @throws IOException 37 | * @throws ClassNotFoundException 38 | */ 39 | public static List> getClasses(Class cls) { 40 | String pk = cls.getPackage().getName(); 41 | String path = pk.replace('.', '/'); 42 | // URL url = classloader.getResource(path); 43 | // return getClasses(new File(url.getFile()), pk); 44 | try { 45 | String dirPath = URLDecoder.decode(classloader.getResource(path).getPath(),"utf-8"); 46 | return getClasses(new File(dirPath), pk); 47 | } catch (UnsupportedEncodingException e) { 48 | e.printStackTrace(); 49 | } 50 | return new ArrayList>(); 51 | } 52 | 53 | /** 54 | * 迭代查找类 55 | * 56 | * @param dir 57 | * @param pk 58 | * @return 59 | * @throws ClassNotFoundException 60 | */ 61 | private static List> getClasses(File dir, String pk) { 62 | List> classes = new ArrayList>(); 63 | if (!dir.exists()) { 64 | return classes; 65 | } 66 | for (File f : dir.listFiles()) { 67 | if (f.isDirectory()) { 68 | classes.addAll(getClasses(f, pk + "." + f.getName())); 69 | } 70 | String name = f.getName(); 71 | if (name.endsWith(".class")) { 72 | try{ 73 | classes.add(Class.forName(pk + "." + name.substring(0, name.length() - 6))); 74 | }catch(Exception ex){ 75 | } 76 | } 77 | } 78 | return classes; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/asserts/Asserts.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.asserts; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.iiaccount.utils.GetFileMess; 5 | import io.restassured.RestAssured; 6 | import org.apache.log4j.Logger; 7 | import org.testng.Assert; 8 | 9 | import java.io.IOException; 10 | import java.sql.SQLException; 11 | import java.util.Map; 12 | 13 | import static com.iiaccount.asserts.DatabaseAssert.verifyDatabase; 14 | import static com.iiaccount.asserts.RespondAssertForJson.verifyResult; 15 | import static com.iiaccount.report.TestStep.assertRespond; 16 | import static com.iiaccount.report.TestStep.databaseAssertResult; 17 | import static com.iiaccount.report.TestStep.requestAndRespondBody; 18 | import static com.iiaccount.utils.SplitXmlForCaseNo.xmlOutTemp; 19 | 20 | /* 21 | *数据库断言及响应报文断言 22 | */ 23 | public class Asserts { 24 | 25 | static Logger log = Logger.getLogger(Asserts.class); 26 | 27 | public static void asserts(String caseMess, String bodyString,String response,String xmlFileName,Map map) throws SQLException, IOException, ClassNotFoundException { 28 | 29 | 30 | //测试报告展现请求报文 响应报文 31 | String url = RestAssured.baseURI + ":" + RestAssured.port + "/" + RestAssured.basePath; 32 | requestAndRespondBody(url,bodyString,response); 33 | 34 | String preResult = new GetFileMess().getCaseMessKeyValue(caseMess, "preResult"); 35 | String tableCheck = new GetFileMess().getCaseMessKeyValue(caseMess, "tableCheck"); 36 | String caseNo = new GetFileMess().getCaseMessKeyValue(caseMess, "caseNo"); 37 | 38 | //格式化json串 39 | boolean prettyFormat = true; //格式化输出 40 | JSONObject jsonObject = JSONObject.parseObject(response); 41 | 42 | response = JSONObject.toJSONString(jsonObject,prettyFormat); 43 | 44 | log.info("案例编号:"+ caseNo); 45 | log.info("响应报文:"+response); 46 | //断言(包含响应报文断言和数据库断言) 47 | databaseAndRespondAsserts(response,preResult,tableCheck,xmlFileName, caseNo, map); 48 | } 49 | 50 | public static void databaseAndRespondAsserts(String sourceData, String verifyData, String tableCheck, String fileName, String caseNo, Map map) throws IOException, SQLException, ClassNotFoundException { 51 | 52 | StringBuffer stringBufferResult = new StringBuffer(); 53 | StringBuffer stringBufferDatabase = new StringBuffer(); 54 | String path = ""; 55 | boolean assertFlag = true; 56 | 57 | //响应报文断言 58 | stringBufferResult = verifyResult(sourceData, verifyData); 59 | //测试报告展现 响应报文断言结果(无论成功还是失败) 60 | assertRespond(stringBufferResult); 61 | // 断言不通过,flag标志赋值为false 62 | if (stringBufferResult.indexOf("断言false") != -1) { 63 | assertFlag = false; 64 | } 65 | 66 | //tableCheck为Y/y才进行数据库响应断言 67 | if (tableCheck.toUpperCase().trim().equals("Y")) { 68 | //临时xml文件 69 | path = xmlOutTemp(fileName, caseNo, map); 70 | 71 | if (!path.equals("")) { 72 | stringBufferDatabase = verifyDatabase(caseNo, path); 73 | 74 | //测试报告展现 数据库断言结果(无论成功还是失败) 75 | databaseAssertResult(stringBufferDatabase); 76 | 77 | if (stringBufferDatabase.indexOf("检查不通过") != -1) { 78 | assertFlag = false; 79 | } 80 | } 81 | } 82 | 83 | //断言 84 | Assert.assertTrue(assertFlag, "响应报文断言或数据库断言失败,请查看断言结果!"); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/utils/IdNoUtil.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.utils; 2 | 3 | import java.util.*; 4 | 5 | /* 6 | *参考资料:https://blog.csdn.net/u012898245/article/details/79404745 7 | *参考资料:https://www.cnblogs.com/liuhongfeng/p/4789274.html?tvd 8 | *证件号组成规则 9 | * 户籍所在地(第1到第6位)+ 出生日期(第7到第14位)+ 落户派出所代码(第15、16位)+ 性别代码(第17位)+ 验证码(第18位) 10 | */ 11 | public class IdNoUtil { 12 | 13 | /** 14 | * 生成第18位身份证号 15 | * 16 | * @param 17 | * @return 身份证校验码的计算方法 18 | * 将前面的身份证号码17位数分别乘以不同的系数。从第一位到第十七位的系数分别为:7-9-10-5-8-4-2-1-6-3-7-9-10-5-8-4-2。 19 | * 将这17位数字和系数相乘的结果相加。 20 | * 用加出来和除以11,看余数是多少? 21 | * 余数只可能有0-1-2-3-4-5-6-7-8-9-10这11个数字。 22 | * 其分别对应的最后一位身份证的号码为1-0-X -9-8-7-6-5-4-3-2。 23 | */ 24 | public static String verificationCode(String str17) { 25 | char[] chars = str17.toCharArray(); 26 | if (chars.length < 17) { 27 | return " "; 28 | } 29 | int[] coefficient = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}; 30 | char[] resultChar = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'}; 31 | int[] numberArr = new int[17]; 32 | int result = 0; 33 | for (int i = 0; i < numberArr.length; i++) { 34 | numberArr[i] = Integer.parseInt(chars[i] + ""); 35 | } 36 | for (int i = 0; i < numberArr.length; i++) { 37 | result += coefficient[i] * numberArr[i]; 38 | } 39 | return String.valueOf(resultChar[result % 11]); 40 | 41 | } 42 | 43 | /** 44 | * 随机获取落户派出所代码(第15、16位) + 性别代码(第17位) 45 | * 直接生成三位数 46 | * 47 | * @return 48 | */ 49 | public static String randomCode() { 50 | int code = (int) (Math.random() * 1000); 51 | if (code < 10) { 52 | return "00" + code; 53 | } else if (code < 100) { 54 | return "0" + code; 55 | } else { 56 | return "" + code; 57 | } 58 | } 59 | 60 | /* 61 | * 随机生成出生日期 62 | * @return 63 | */ 64 | public static String randomBirthday() { 65 | Calendar birthday = Calendar.getInstance(); 66 | birthday.set(Calendar.YEAR, (int) (Math.random() * 60) + 1950); 67 | birthday.set(Calendar.MONTH, (int) (Math.random() * 12)); 68 | birthday.set(Calendar.DATE, (int) (Math.random() * 31)); 69 | StringBuilder builder = new StringBuilder(); 70 | builder.append(birthday.get(Calendar.YEAR)); 71 | long month = birthday.get(Calendar.MONTH) + 1; 72 | if (month < 10) { 73 | builder.append("0"); 74 | } 75 | builder.append(month); 76 | long date = birthday.get(Calendar.DATE); 77 | if (date < 10) { 78 | builder.append("0"); 79 | } 80 | builder.append(date); 81 | return builder.toString(); 82 | } 83 | 84 | /** 85 | * 随机获取区号 86 | * 87 | * @param registerLocation 88 | * @return 89 | */ 90 | public static String randomLocationCode(Map registerLocation) { 91 | int index = (int) (Math.random() * registerLocation.size()); 92 | Collection values = registerLocation.values(); 93 | Iterator it = values.iterator(); 94 | int i = 0; 95 | int locationCode = 0; 96 | while (i <= index && it.hasNext()) { 97 | i++; 98 | if (i == index) { 99 | locationCode = it.next(); 100 | } 101 | } 102 | return String.valueOf(locationCode); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/utils/AssembledMessForJson.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.utils; 2 | 3 | import com.google.gson.Gson; 4 | import com.iiaccount.asserts.RespondAssertForJson; 5 | import jxl.Sheet; 6 | import jxl.Workbook; 7 | import jxl.read.biff.BiffException; 8 | import org.apache.log4j.Logger; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | /* 16 | *根据测试用例文件拼装报文body(json格式) 17 | * 输入一贯改造_短信发送.xls,return json串列表 18 | */ 19 | public class AssembledMessForJson { 20 | 21 | static Logger log = Logger.getLogger(AssembledMessForJson.class); 22 | static Map bodyMap = new HashMap(); 23 | static Map dataMap = new HashMap(); 24 | static Map caseMessMap = new HashMap(); 25 | static Map map = new HashMap(); 26 | 27 | /* 28 | @Test() 29 | public void test() throws IOException, BiffException, URISyntaxException { 30 | assembleMess("sendmsgYg.xls","case022"); 31 | } 32 | */ 33 | 34 | 35 | /* 36 | *入参为testCase目录下的案例数据文件名 37 | */ 38 | public static Map assembleMess(String fileName,String caseNo) throws IOException, BiffException { 39 | log.info("文件名:" + fileName); 40 | 41 | String filePath = new GetFileMess().getFilePath("testCase", fileName); 42 | File xlsFile = new File(filePath); 43 | Workbook workbook = Workbook.getWorkbook(xlsFile); // 获得工作簿对象 44 | Sheet sheet = workbook.getSheet(0); // 获得工作表 45 | int rows = sheet.getRows(); // 获得行数 46 | int cols = sheet.getColumns(); // 获得列数 47 | 48 | String pubArgs = new GetFileMess().getValue("pubArgsYg", "PublicArgs.properties"); 49 | log.info("接口公共入参pubArgsYg:" + pubArgs); 50 | 51 | bodyMap.clear(); 52 | dataMap.clear(); 53 | caseMessMap.clear(); 54 | 55 | for (int row = 1; row < rows; row++) { 56 | String yOn = sheet.getCell(3, row).getContents(); 57 | String caseNo1 = sheet.getCell(0, row).getContents().toLowerCase(); //文件中的案例编号 58 | if (yOn.equals("Y") && caseNo.equals("")) { //获取全部为Y的案例报文体 59 | getMap(sheet, cols, row, pubArgs); 60 | } else if (caseNo1.equals(caseNo.toLowerCase())) { //获取单条案例报文体,不管执行标识是否为Y 61 | getMap(sheet, cols, row, pubArgs); 62 | } 63 | } 64 | 65 | workbook.close(); 66 | return map; 67 | } 68 | 69 | public static void getMap(Sheet sheet, int cols, int row, String pubArgs){ 70 | 71 | for (int col = 0; col < cols; col++) { 72 | 73 | String cellKey = sheet.getCell(col, 0).getContents();//表头 74 | String cellValue = sheet.getCell(col, row).getContents();//值 75 | if (col >= 5) { 76 | //appid,api,version属于公共入参,公共入参字段在PublicArgs.properties文件进行配置 77 | // getBuildValue(value1,value2)方法用于转换${}或者函数为对应的值 78 | if (pubArgs.toLowerCase().contains(cellKey.toLowerCase().trim())) { 79 | bodyMap.put(cellKey, RespondAssertForJson.getBuildValue("", sheet.getCell(col, row).getContents())); 80 | } else { 81 | dataMap.put(cellKey, RespondAssertForJson.getBuildValue("", sheet.getCell(col, row).getContents())); 82 | } 83 | } else { 84 | caseMessMap.put(cellKey, cellValue); 85 | } 86 | } 87 | bodyMap.put("data", dataMap); 88 | map.put(new Gson().toJson(caseMessMap), new Gson().toJson(bodyMap)); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/data/DataBuilders.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.data; 2 | 3 | import com.iiaccount.asserts.RespondAssertForJson; 4 | import com.iiaccount.common.RunCaseJson; 5 | import com.iiaccount.dao.CRUDData; 6 | import com.iiaccount.utils.AssembledMessForJson; 7 | import io.restassured.response.Response; 8 | import jxl.read.biff.BiffException; 9 | import org.apache.log4j.Logger; 10 | 11 | import java.io.IOException; 12 | import java.sql.SQLException; 13 | import java.util.regex.Matcher; 14 | import java.util.regex.Pattern; 15 | 16 | //数据提供 17 | public class DataBuilders { 18 | 19 | static Logger log = Logger.getLogger(DataBuilders.class); 20 | 21 | /* 22 | *目前提供以下四种供数方式: 23 | * -查询数据池供数:${dp.sql(select accountNo from M_account where status = 1)} 24 | 表达式:(dp.sql)\((.*)\) 25 | * -查询数据库供数:${db.sql(select accountNo from M_account_card where status = 1)} 26 | 表达式:(db.sql)\((.*)\) 27 | * -先接口请求,然后提取响应报文供数:${SendmsgYg.case023.post($.data.code)} 或${SendmsgYg.case023.get($.data.code)} 28 | 表达式:(.*case.*)\((.*)\) 29 | * -先接口请求,然后查询数据库/池供数:${SendmsgYg.case023.post.db.sql(select accountNo from M_account_card where status = 1)} 30 | 表达式:(.*case.*).db.sql\((.*)\) 31 | 表达式:(.*case.*).dp.sql\((.*)\) --暂时不用 32 | */ 33 | protected static Pattern dataPoolPattern = Pattern.compile("(dp.sql)\\((.*)\\)"); 34 | protected static Pattern databasePattern = Pattern.compile("(db.sql)\\((.*)\\)"); 35 | protected static Pattern reponsePattern = Pattern.compile("(.*case.*).post\\((.*)\\)"); 36 | protected static Pattern httpDataPoolPattern = Pattern.compile("(.*case.*).dp.sql\\((.*)\\)"); 37 | protected static Pattern httpDdatabasePattern = Pattern.compile("(.*case.*).db.sql\\((.*)\\)"); 38 | 39 | public static String dataprovide(String var) throws SQLException, IOException, ClassNotFoundException, BiffException { 40 | String value = ""; 41 | Matcher dpMatch = dataPoolPattern.matcher(var); 42 | Matcher dbMatch = databasePattern.matcher(var); 43 | Matcher responseMatch = reponsePattern.matcher(var); 44 | Matcher httpDbMatch = httpDdatabasePattern.matcher(var); 45 | Matcher httpDpMatch = httpDataPoolPattern.matcher(var); 46 | 47 | 48 | if(dpMatch.find()){//查询数据池供数 49 | String sql = dbMatch.group(2); 50 | value = CRUDData.selectData(sql,"DP"); 51 | log.info("查询数据池供数:"+ value); 52 | }else if(dbMatch.find()){//查询数据库供数 53 | String sql = dbMatch.group(2); 54 | value = CRUDData.selectData(sql,"DB"); 55 | log.info("查询数据库供数:"+ value); 56 | }else if(responseMatch.find()){//先接口请求,然后提取响应报文供数 57 | String jsonPath = responseMatch.group(2); //$.data.code 58 | String response = runCase(responseMatch); 59 | 60 | //根据$.data.code获取响应报文中对应的值 61 | value = RespondAssertForJson.getBuildValue(response, jsonPath); 62 | 63 | }else if(httpDbMatch.find()){//先接口请求,然后查询数据库供数 64 | String sql = httpDbMatch.group(2); //select accountNo from M_account_card where status = 1 65 | runCase(httpDbMatch); 66 | 67 | //根据sql语句获取相应的值 68 | value = CRUDData.selectData(sql,"DB"); 69 | 70 | }else if(httpDpMatch.find()){//先接口请求,然后查询数据池供数(由于接口请求后数据不入数据池,此场景可以用于只执行依赖接口) 71 | String sql = httpDpMatch.group(2); //select accountNo from M_account_card where status = 1 72 | runCase(httpDpMatch); 73 | 74 | //根据sql语句获取相应的值 75 | value = CRUDData.selectData(sql,"DP"); 76 | } 77 | 78 | return value; 79 | } 80 | 81 | //根据caseNo进行接口请求 82 | public static String runCase(Matcher matcher) throws IOException, BiffException { 83 | 84 | String[] caseMess = matcher.group(1).split("."); //SendmsgYg.case023.post 85 | String fileName = caseMess[0]+".xls"; //案例文件名 86 | String caseNo = caseMess[1]; //案例编号 87 | String requestType = caseMess[2]; //请求类型post/get 88 | //String sql = matcher.group(2); //select accountNo from M_account_card where status = 1 89 | String bodyString = ""; 90 | //该map只有一条记录 91 | for(String value1:AssembledMessForJson.assembleMess(fileName,caseNo).values()){ 92 | bodyString = value1; 93 | } 94 | 95 | log.info("matcher.group(1):"+matcher.group(1)); 96 | log.info("matcher.group(2):"+matcher.group(2)); 97 | log.info("bodyString:"+bodyString); 98 | 99 | //发送请求 100 | Response response = RunCaseJson.runCase(bodyString,requestType); 101 | 102 | return response.asString(); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/asserts/RespondAssertForJson.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.asserts; 2 | 3 | import com.alibaba.fastjson.JSONPath; 4 | import com.iiaccount.data.DataBuilders; 5 | import com.iiaccount.utils.FunctionUtil; 6 | import com.iiaccount.utils.StringUtil; 7 | import org.apache.log4j.Logger; 8 | 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | 12 | public class RespondAssertForJson { 13 | 14 | static Logger log = Logger.getLogger(RespondAssertForJson.class); 15 | 16 | /** 17 | * 替换符,如果数据中包含“${}”则会被替换成公共参数中存储的数据 18 | */ 19 | protected static Pattern replaceParamPattern = Pattern.compile("\\$\\{(.*?)\\}"); 20 | 21 | /** 22 | * 截取自定义函数正则表达式:__random(value1,value2) 23 | */ 24 | protected static Pattern funPattern = Pattern 25 | .compile("__(\\w*?)\\((([\\w]*,?)*)\\)"); 26 | 27 | /* 28 | *包含断言的正则表达式,__contain(sssss) 29 | */ 30 | protected static Pattern containPattern = Pattern.compile("__(contain)\\(.+\\)"); 31 | 32 | /* 33 | *对预期结果断言,json断言和contain断言允许在同一预期结果中使用,比如$.status=200;__contain(tomandy) 34 | *无论断言成功还是失败,测试报告展现断言结果 35 | */ 36 | public static StringBuffer verifyResult(String sourceData, String verifyData) { 37 | if (verifyData.equals("") || verifyData == null) 38 | return null; 39 | 40 | log.info("待验证的预期结果为:" + verifyData); 41 | 42 | boolean assertFlag = true; 43 | StringBuffer stringBuffer = new StringBuffer(); 44 | 45 | String assertStr[] = verifyData.split(";"); 46 | for (String assertString : assertStr) { 47 | 48 | if (assertString.toLowerCase().contains("__contain(")) { 49 | // 验证包含断言 50 | log.info("contain断言表达式:" + assertString); 51 | //提取__contain()函数里的字符串 52 | String containMess = assertString.substring(10, assertString.length() - 1); 53 | assertFlag = ContainAssert.contains(sourceData, containMess); 54 | if (!assertFlag) 55 | stringBuffer.append("【" + assertString + "断言" + assertFlag + String.format(",期待\n'%s'\n包含'%s',实际不包含!】\n", sourceData, containMess)); 56 | else 57 | stringBuffer.append("【" + assertString + "断言" + assertFlag + String.format(",期待\n'%s'\n包含'%s',实际包含!】\n", sourceData, containMess)); 58 | } else if (assertString.toLowerCase().contains("$.")) { 59 | log.info("json断言表达式:" + assertString); 60 | //json断言,通过;隔开 61 | Pattern pattern = Pattern.compile("([^;]*)=([^;]*)"); 62 | Matcher matcher = pattern.matcher(assertString.trim()); 63 | while (matcher.find()) { 64 | //根据$.status的json格式匹配响应报文中的值 65 | String actualValue = getBuildValue(sourceData, matcher.group(1)); 66 | log.info("matcher.group(1):" + matcher.group(1)); 67 | 68 | //假如预期结果为$.status=200,则匹配值200,200也可以替换为自定义函数 69 | String exceptValue = getBuildValue(sourceData, matcher.group(2)); 70 | log.info("matcher.group(2):" + matcher.group(2)); 71 | 72 | log.info(String.format("验证转换后的值%s=%s", actualValue, 73 | exceptValue)); 74 | 75 | //如果有多个断言,前面断言的失败,不会再校验后面的断言 76 | //Assert.assertEquals(actualValue, exceptValue, "验证预期结果失败!"); 77 | //无论断言成功还是失败,都保存断言 78 | if (exceptValue.equals(actualValue)) { 79 | assertFlag = true; 80 | stringBuffer.append("【" + matcher.group() + "断言" + assertFlag + String.format(",期待预期结果为'%s',实际结果为'%s'!】\n", exceptValue, actualValue)); 81 | } else { 82 | assertFlag = false; 83 | stringBuffer.append("【" + matcher.group() + "断言" + assertFlag + String.format(",期待预期结果为'%s',实际结果为'%s'!】\n", exceptValue, actualValue)); 84 | } 85 | } 86 | } else { 87 | //Assert.assertTrue(false, "【预期结果断言格式有误,目前仅支持Json及contain断言,多个断言使用英文分号隔开,例如:$.status=200;__contain(tomandy)】"); 88 | assertFlag = false; 89 | stringBuffer.append("【预期结果断言" + assertFlag + ",断言格式有误,目前仅支持Json及contain断言,多个断言使用英文分号隔开,例如:$.status=200;__contain(tomandy)】\n"); 90 | } 91 | } 92 | return stringBuffer; 93 | } 94 | 95 | /** 96 | * 支持json串转换 97 | * 支持自定义函数的转换 98 | * 支持${}变量转换 99 | * 100 | * @param sourchJson 101 | * @param key 102 | * @return 103 | */ 104 | public static String getBuildValue(String sourchJson, String key) { 105 | key = key.trim(); 106 | Matcher funMatch = funPattern.matcher(key); 107 | Matcher replacePattern = replaceParamPattern.matcher(key); 108 | 109 | //log.info("key is:" + key); //去掉部分日志 add by lrb 20181029 110 | try{ 111 | if (key.startsWith("$.")) {// jsonpath 112 | key = JSONPath.read(sourchJson, key).toString(); //jsonpath读取对应的值 113 | log.info("key start with $.,value is:" + key); 114 | } else if (funMatch.find()) {//函数 115 | 116 | String args = funMatch.group(2); //函数入参 117 | log.info("key is a function,args is:" + args); 118 | String[] argArr = args.split(","); 119 | for (int index = 0; index < argArr.length; index++) { 120 | String arg = argArr[index]; 121 | if (arg.startsWith("$.")) { //函数入参亦支持json格式 122 | argArr[index] = JSONPath.read(sourchJson, arg).toString(); 123 | } 124 | } 125 | log.info("argArr:"+argArr.length); 126 | String value = FunctionUtil.getValue(funMatch.group(1), argArr); //函数名不区分大小写,返回函数值 127 | log.info("函数名 funMatch.group(1):" + funMatch.group(1)); 128 | key = StringUtil.replaceFirst(key, funMatch.group(), value); //把函数替换为生成的值 129 | log.info("函数 funMatch.group():" + funMatch.group()); 130 | log.info("key is a function,value is:" + key); 131 | } else if (replacePattern.find()) {//${}变量 132 | log.info("${}变量体:"+replacePattern.group(1)); 133 | String var = replacePattern.group(1).trim(); 134 | 135 | String value1 = DataBuilders.dataprovide(var); 136 | key = StringUtil.replaceFirst(key, replacePattern.group(), value1); //把变量替换为生成的值 137 | log.info("key is a ${} pattern,value is:" + key); 138 | } 139 | return key; 140 | 141 | }catch(Exception e){ 142 | 143 | log.info(e.getMessage()); 144 | return null; 145 | } 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | HFIIACCOUNT 8 | ApiAutoTest 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 13 | 1.8.10 14 | 15 | UTF-8 16 | 17 | 18 | testSuite_YG.xml 19 | 20 | 21 | 22 | 23 | org.testng 24 | testng 25 | 6.11 26 | 27 | 28 | 29 | io.rest-assured 30 | rest-assured 31 | 3.1.0 32 | 33 | 34 | 35 | ru.yandex.qatools.allure 36 | allure-testng-adaptor 37 | 1.3.6 38 | 39 | 40 | org.testng 41 | testng 42 | 43 | 44 | 45 | 46 | 47 | io.qameta.allure 48 | allure-testng 49 | 2.0-BETA14 50 | 51 | 52 | 53 | net.sourceforge.jexcelapi 54 | jxl 55 | 2.6.12 56 | 57 | 58 | 59 | com.google.code.gson 60 | gson 61 | 2.8.2 62 | 63 | 64 | 65 | com.alibaba 66 | fastjson 67 | [1.2.31,) 68 | 69 | 70 | 71 | com.oracle 72 | ojdbc14 73 | 10.2.0.4.0 74 | 75 | 76 | 77 | org.mybatis 78 | mybatis 79 | 3.4.4 80 | 81 | 82 | 83 | mysql 84 | mysql-connector-java 85 | 6.0.6 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | src/main/resources 94 | true 95 | 96 | 97 | 98 | 99 | src/main/filters/filter_${env}.properties 100 | 101 | 102 | 103 | 104 | org.apache.maven.plugins 105 | maven-surefire-plugin 106 | 2.20 107 | 108 | 109 | -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar" 110 | 111 | 112 | 113 | 114 | allure.results.directory 115 | ./target/allure-results 116 | 117 | 118 | 119 | 120 | 121 | org.aspectj 122 | aspectjweaver 123 | ${aspectj.version} 124 | 125 | 126 | 127 | 128 | 129 | org.apache.maven.plugins 130 | maven-surefire-plugin 131 | 2.19 132 | 133 | 134 | 135 | 136 | ${project.basedir}/target/classes/testngXml/${xmlFileName} 137 | 138 | 139 | 140 | 141 | 142 | 143 | org.apache.maven.plugins 144 | maven-compiler-plugin 145 | 146 | 8 147 | 8 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | org.apache.maven.plugins 156 | maven-resources-plugin 157 | 2.6 158 | 159 | 160 | 161 | xls 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | uat 174 | 175 | uat 176 | 177 | 178 | 179 | 180 | 181 | 182 | sit 183 | 184 | sit 185 | 186 | 187 | true 188 | 189 | 190 | 191 | 192 | 193 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/utils/SplitXmlForCaseNo.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.utils; 2 | 3 | import com.iiaccount.asserts.ConstVariable; 4 | import org.apache.log4j.Logger; 5 | import org.w3c.dom.Document; 6 | import org.w3c.dom.Element; 7 | import org.w3c.dom.Node; 8 | import org.w3c.dom.NodeList; 9 | 10 | import javax.xml.parsers.DocumentBuilder; 11 | import javax.xml.parsers.DocumentBuilderFactory; 12 | import javax.xml.transform.Transformer; 13 | import javax.xml.transform.TransformerFactory; 14 | import javax.xml.transform.dom.DOMSource; 15 | import javax.xml.transform.stream.StreamResult; 16 | import java.io.File; 17 | import java.io.FileOutputStream; 18 | import java.util.Map; 19 | 20 | /* 21 | *将databaseAssert目录下的数据库断言按caseNo拆分为临时xml文件 22 | */ 23 | public class SplitXmlForCaseNo { 24 | 25 | static Logger log = Logger.getLogger(SplitXmlForCaseNo.class) ; 26 | 27 | /* 28 | @Test 29 | public void test() throws IOException { 30 | String fileName = "OpenYg"; 31 | String caseNo = "case085"; 32 | Map map = new HashMap<>(); 33 | 34 | xmlOutTemp(fileName,caseNo,map); 35 | } 36 | */ 37 | 38 | /* 39 | * 读取包含全部case的xml文件,区分caseNo生成临时的xml文件,并替换xml文件中的变量 40 | * @para fileName:文件名,约定跟类名保持一致 41 | * @para caseNo:案例编号 42 | * @para map:xml文件的变量映射 43 | */ 44 | public static String xmlOutTemp(String fileName, String caseNo, 45 | Map map) { // 区分case生成临时文件 46 | 47 | String table_name = ""; 48 | String filePathIn = new GetFileMess().getFilePath("databaseAssert",fileName+".xml"); 49 | String filePathOut = ""; 50 | 51 | if(filePathIn != null){ 52 | DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 53 | try { 54 | DocumentBuilder db = dbf.newDocumentBuilder(); 55 | 56 | Document doc = db.parse(new File(filePathIn)); // 读取xml文件 57 | 58 | NodeList caseList = doc.getElementsByTagName("caseNo"); 59 | 60 | log.info("共有" + caseList.getLength() + "个caseNo节点"); 61 | 62 | for (int i = 0; i < caseList.getLength(); i++) { 63 | Node case_node = caseList.item(i); // 第一个caseNo节点 64 | 65 | Element elem0 = (Element) case_node; // caseNo节点对象 66 | String caseNo_name = elem0.getAttribute("case_no"); 67 | 68 | log.info("案例编号:" + caseNo_name); 69 | 70 | if (caseNo_name.equals(caseNo)) { // 根据caseNo入参选择性遍历 71 | 72 | // ----------------------------------------------------------------------------- 73 | // 操作的Document对象 74 | Document document = db.newDocument(); 75 | // 设置XML的版本 76 | document.setXmlVersion("1.0"); 77 | // 创建根节点 78 | Element root = document.createElement("caseNo"); 79 | root.setAttribute("case_no", caseNo); 80 | // 将根节点添加到Document对象中 81 | document.appendChild(root); 82 | // ------------------------------------------------------------------------------ 83 | 84 | for (Node table_node = case_node.getFirstChild(); table_node != null; table_node = table_node 85 | .getNextSibling()) { 86 | 87 | if (table_node.getNodeType() == Node.ELEMENT_NODE) // 如果当前节点为元素节点 88 | { 89 | Element elem1 = (Element) table_node; // table节点对象 90 | table_name = elem1.getAttribute("table_name"); // 获取表名 91 | log.info("表名:" + table_name); 92 | } 93 | // ------------------------------------------------------------------------------ 94 | Element tableElement = document.createElement("table"); 95 | // 设置page节点的name属性 96 | tableElement.setAttribute("table_name", table_name); 97 | root.appendChild(tableElement); 98 | // ------------------------------------------------------------------------------ 99 | 100 | for (Node column_node = table_node.getFirstChild(); column_node != null; column_node = column_node 101 | .getNextSibling()) { 102 | if (column_node.getNodeType() == Node.ELEMENT_NODE) // 如果当前节点为元素节点 103 | { 104 | Element elem2 = (Element) column_node; // column节点对象 105 | String priKey_name = elem2 106 | .getAttribute("key_name"); // 获取主键名 107 | String column_name = elem2 108 | .getAttribute("column_name"); // 获取检查字段名 109 | 110 | String value = ""; 111 | if(column_node.getFirstChild() != null){ //可能会存在 这类节点,导致取getNodeValue抛异常 112 | value = column_node.getFirstChild().getNodeValue(); //获取值 113 | }else{ 114 | value = ""; 115 | } 116 | 117 | if (!priKey_name.equals("")) { 118 | 119 | log.info("主键名:" + priKey_name + " 值:" + value); 120 | // ------------------------------------------------------------------------------ 121 | Element priKeyElement = document 122 | .createElement("priKey"); 123 | // 设置page节点的name属性 124 | priKeyElement.setAttribute("key_name", 125 | priKey_name); 126 | priKeyElement.setTextContent(value); 127 | 128 | for (String keyName : map.keySet()) { // 遍历map,替换变量 129 | if (value.equals(keyName)) { // xml的变量存在map中,则使用map的值 130 | priKeyElement.setTextContent(map 131 | .get(keyName)); 132 | break; 133 | } 134 | } 135 | tableElement.appendChild(priKeyElement); 136 | // ------------------------------------------------------------------------------ 137 | } 138 | 139 | if (!column_name.equals("")) { 140 | 141 | log.info("列名:" + column_name + " 值:" + value); 142 | // ------------------------------------------------------------------------------ 143 | Element columnElement = document 144 | .createElement("column"); 145 | // 设置page节点的name属性 146 | columnElement.setAttribute("column_name", 147 | column_name); 148 | columnElement.setTextContent(value); 149 | 150 | log.info("map:" + map); 151 | for (String keyName : map.keySet()) { // 遍历map,替换变量 152 | if (value.equals(keyName)) { // xml的变量存在map中,则使用map的值 153 | columnElement.setTextContent(map 154 | .get(keyName)); 155 | break; 156 | } 157 | } 158 | tableElement.appendChild(columnElement); 159 | // ------------------------------------------------------------------------------ 160 | } 161 | } 162 | } 163 | table_name = ""; 164 | } 165 | // ------------------------------------------------------------------------------ 166 | // 开始把Document映射到文件 167 | TransformerFactory transFactory = TransformerFactory 168 | .newInstance(); 169 | Transformer transFormer = transFactory.newTransformer(); 170 | // 设置输出结果 171 | DOMSource domSource = new DOMSource(document); 172 | // 生成xml文件 173 | File file = new File(ConstVariable.xmlOutputPath +"\\" +fileName + "_temp.xml"); //temp目录下 174 | 175 | // 判断是否存在,如果不存在,则创建 176 | if (!file.exists()) { 177 | file.createNewFile(); 178 | } 179 | // 文件输出流 180 | FileOutputStream out = new FileOutputStream(file); 181 | // 设置输入源 182 | StreamResult xmlResult = new StreamResult(out); 183 | // 输出xml文件 184 | transFormer.transform(domSource, xmlResult); 185 | // 测试文件输出的路径 186 | log.info("数据库断言临时xml文件路径:"+file.getAbsolutePath()); 187 | 188 | filePathOut = file.getAbsolutePath(); 189 | // ------------------------------------------------------------------------------ 190 | } 191 | } 192 | } catch (Exception e) { 193 | log.info(e.getMessage()); 194 | e.printStackTrace(); 195 | return ""; 196 | } 197 | } 198 | 199 | return filePathOut; 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/main/java/com/iiaccount/asserts/DatabaseAssert.java: -------------------------------------------------------------------------------- 1 | package com.iiaccount.asserts; 2 | 3 | import com.iiaccount.dao.DBDPConnection; 4 | import org.apache.log4j.Logger; 5 | import org.w3c.dom.Document; 6 | import org.w3c.dom.Element; 7 | import org.w3c.dom.Node; 8 | import org.w3c.dom.NodeList; 9 | 10 | import javax.xml.parsers.DocumentBuilder; 11 | import javax.xml.parsers.DocumentBuilderFactory; 12 | import java.io.File; 13 | import java.io.IOException; 14 | import java.sql.*; 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | import static com.iiaccount.utils.GainSqlUtil.gainSqlFromMap; 19 | import static com.iiaccount.utils.IsNumericUtil.isNumeric; 20 | 21 | public class DatabaseAssert { 22 | 23 | static Logger log = Logger.getLogger(DatabaseAssert.class); 24 | 25 | private static Connection connection = null; 26 | 27 | /* 28 | * 读取临时xml文件,根据xml的检查点进行数据库检查,并返回检查结果 29 | */ 30 | public static StringBuffer verifyDatabase(String caseNo, String filePath) throws IOException, ClassNotFoundException, SQLException { //返回检查不通过的原因 31 | 32 | StringBuffer excuteResultStr = new StringBuffer(); 33 | String flagResultStr = ""; 34 | String sql = ""; 35 | String table_name = ""; 36 | Map keyMap = new HashMap(); 37 | Map columnMap = new HashMap(); 38 | 39 | // add by lrb 20180921 每检查一张表都需要链接一次数据库,性能损耗大,把connect放到外面 40 | connection = DBDPConnection.getDBConnection(); 41 | 42 | DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 43 | try { 44 | DocumentBuilder db = dbf.newDocumentBuilder(); 45 | Document doc = db.parse(new File(filePath)); 46 | 47 | NodeList caseList = doc.getElementsByTagName("caseNo"); 48 | 49 | for (int i = 0; i < caseList.getLength(); i++) { 50 | Node case_node = caseList.item(i); // 第一个caseNo节点 51 | 52 | Element elem0 = (Element) case_node; // caseNo节点对象 53 | String caseNo_name = elem0.getAttribute("case_no"); 54 | log.info("案例编号:" + caseNo_name); 55 | 56 | if (caseNo_name.equals(caseNo)) { // 根据caseNo入参选择性遍历 57 | for (Node table_node = case_node.getFirstChild(); table_node != null; table_node = table_node 58 | .getNextSibling()) { 59 | 60 | if (table_node.getNodeType() == Node.ELEMENT_NODE) // 如果当前节点为元素节点 61 | { 62 | 63 | Element elem1 = (Element) table_node; // table节点对象 64 | table_name = elem1.getAttribute("table_name"); // 获取表名 65 | log.info("表名:" + table_name); 66 | } 67 | 68 | for (Node column_node = table_node.getFirstChild(); column_node != null; column_node = column_node 69 | .getNextSibling()) { 70 | if (column_node.getNodeType() == Node.ELEMENT_NODE) // 如果当前节点为元素节点 71 | { 72 | Element elem2 = (Element) column_node; // column节点对象 73 | String priKey_name = elem2.getAttribute("key_name"); // 获取主键名 74 | String column_name = elem2.getAttribute("column_name"); // 获取检查字段名 75 | 76 | String value = ""; 77 | if (column_node.getFirstChild() != null) { //可能会存在 这类节点,导致取getNodeValue抛异常 78 | value = column_node.getFirstChild().getNodeValue(); //获取值 79 | } else { 80 | value = ""; 81 | } 82 | 83 | if (!priKey_name.equals("")) { 84 | keyMap.put(priKey_name, value); 85 | log.info("主键名:" + priKey_name + " 值:" + value); 86 | } 87 | 88 | if (!column_name.equals("")) { 89 | columnMap.put(column_name, value); 90 | log.info("列名:" + column_name + " 值:" + value); 91 | } 92 | 93 | } 94 | } 95 | if (keyMap.size() != 0 && columnMap.size() != 0) { 96 | log.info("keyMap: " + keyMap); 97 | log.info("columnMap: " + columnMap); 98 | 99 | sql = gainSqlFromMap(table_name, keyMap); //根据索引拼装sql 100 | log.info("sql: " + sql); 101 | 102 | flagResultStr = checkTable(sql, columnMap); //数据库检查 103 | 104 | String strFlag[] = flagResultStr.split("&&&&%%%%%%@@@@");// 分割字符串得到数组 105 | 106 | if (strFlag[0].equals("false")) { 107 | log.info("检查结果:" + table_name + "检查不通过!"); 108 | excuteResultStr.append("【" + table_name + "表检查不通过】\n" + strFlag[1]); 109 | } else { 110 | excuteResultStr.append("【" + table_name + "表检查通过】\n" + strFlag[1]); 111 | } 112 | keyMap.clear(); 113 | columnMap.clear(); 114 | } 115 | } 116 | } 117 | } 118 | 119 | // add by lrb 20180921 每检查一张表都需要链接一次数据库,性能损耗大,把connect放到外面 120 | if (connection != null) 121 | connection.close(); 122 | 123 | } catch (Exception e) { 124 | e.printStackTrace(); 125 | excuteResultStr.append("XML文件有误:" + e.getMessage()); 126 | return excuteResultStr; 127 | } 128 | return excuteResultStr; //返回检查结果 129 | } 130 | 131 | /* 132 | * 133 | */ 134 | public static String checkTable(String sql, Map map) throws ClassNotFoundException, 135 | IOException { 136 | 137 | StringBuffer resultStr = new StringBuffer(); 138 | StringBuffer flagStr = new StringBuffer(); 139 | 140 | Map mapType = new HashMap(); 141 | 142 | // add by lrb 20180921 每检查一张表都需要链接一次数据库,性能损耗大,把connect放到外面 143 | //Connection connection = null; 144 | ResultSet result = null; 145 | boolean flag = true; 146 | 147 | try { 148 | // add by lrb 20180921 每检查一张表都需要链接一次数据库,性能损耗大,把connect放到外面 149 | //url = "jdbc:oracle:thin:@"+url; 150 | //Class.forName("oracle.jdbc.driver.OracleDriver"); 151 | //connection = DriverManager.getConnection(url, user, password);// 地址,用户名,密码 152 | 153 | result = connection.prepareStatement(sql).executeQuery(); 154 | 155 | ResultSetMetaData metadata = result.getMetaData(); // add by lrb 156 | // 20170927 157 | // 获取每一列的类型NUMBER,VERCHA2,DATE等 158 | for (int i = 1; i <= metadata.getColumnCount(); i++) { // add by lrb 159 | // 20170927 160 | mapType.put(metadata.getColumnName(i), 161 | metadata.getColumnTypeName(i)); // add by lrb 20170927 162 | } 163 | 164 | log.info(mapType); 165 | 166 | if (result.next()) { // 只有一笔记录,无需while,只取第一笔记录 167 | 168 | for (String key : map.keySet()) { 169 | flag = true; // 循环过后需初始化,防止下一次循环取了上一次的结果; 170 | 171 | log.info(key 172 | + "预期结果为:" + map.get(key) + ";实际结果为:" 173 | + result.getString(key)); 174 | 175 | if (result.getString(key) != null) //数据库获取的值不为null 176 | { 177 | if (!result.getString(key).trim().equals(map.get(key).trim()) && !map.get(key).toUpperCase().trim().equals("NOTNULL")) { // 预期结果和实际结果不匹配 178 | 179 | flag = false; 180 | 181 | /* map映射的值是字符串,对于金额11.80映射为11.80,但数据库查询返回的是11.8,导致比对是检查不通过;需做特殊处理; 182 | * 如果列类型是NUMBER类型且为数字,则转换为Double类型再进行比较; 183 | * add by lrb 20170927 mapType.get(key).equals("NUMBER") 184 | */ 185 | if (mapType.get(key).equals("NUMBER") 186 | && isNumeric(result.getString(key).trim()) 187 | && isNumeric(map.get(key).trim())) {// 如果为数值,再进行判断 188 | if (Double.parseDouble(result.getString(key).trim()) == Double 189 | .parseDouble(map.get(key).trim())) // 字符串转换为数值再比较 190 | flag = true; 191 | } 192 | 193 | log.info("flag1:" + flag); 194 | 195 | } 196 | 197 | if (map.get(key).toUpperCase().trim().equals("NOTNULL")) {//只做非空校验 198 | if (result.getString(key).trim().equals("") || result.getString(key) == null) { 199 | flag = false; 200 | } 201 | } 202 | } else { 203 | if (!map.get(key).equals("")) //如果数据库查回来的是null,但上送的预期结果不是空值,则当成校验不通过 204 | flag = false; 205 | } 206 | 207 | log.info("最终的flag:" + flag); 208 | 209 | // 对表的每个字段校验都输出预期和实际结果;//add by linrb 20171031 210 | if (!map.get(key).toUpperCase().trim().equals("NOTNULL")) { 211 | resultStr.append("<" + key + "字段>预期结果为:" + map.get(key) 212 | + ",实际结果为:" + result.getString(key) + "\n"); 213 | } else { 214 | resultStr.append("<" + key + "字段>只做非空校验" + ",实际结果为:" + result.getString(key) + "\n"); 215 | } 216 | 217 | 218 | // if(flag == false) //有一个值检查不通过,则认定该表校验不过,退出循环 //add by 219 | // linrb 20171031 220 | // break; //add by linrb 20171031 221 | 222 | // 此处需把每次循环的flag标志存起来,防止出现字段1检查不通过、字段2检查通过类型的场景,导致flag不准确; 223 | flagStr.append(flag); 224 | 225 | } 226 | // 如果flagStr含有false,则表示有字段检查不通过,flag标志赋值为false 227 | if (flagStr.indexOf("false") != -1) { 228 | flag = false; 229 | } else { 230 | flag = true; 231 | } 232 | 233 | resultStr.append("\n"); 234 | 235 | } else { 236 | log.info("找不到记录"); 237 | resultStr.append("找不到记录!" + "\n"); // add by linrb 20171031 238 | flag = false; 239 | } 240 | if (result != null) 241 | result.close(); 242 | // add by lrb 20180921 减少数据库请求次数,把connect放到外面 243 | //if (connection != null) 244 | // connection.close(); 245 | 246 | } catch (SQLException e) { 247 | flag = false; // 查询数据库抛异常,认为检查不通过; 248 | resultStr.append("比对数据库异常!" + "\n"); // add by linrb 20171031 249 | e.printStackTrace(); 250 | } 251 | return flag + "&&&&%%%%%%@@@@" + resultStr; // 比对结果不通过则返回false 252 | } 253 | } --------------------------------------------------------------------------------