接口框架實現(xiàn)整體步驟:
resource層:
底層是application.properties配置路徑文件
databaseconfig.xml配置數據庫文件
mapper文件夾中是sqlmapper.xml的配置文件
testng.xml配置文件 測試類的配置文件
還有moco的接口文件,json格式的,需要一個jar包依賴
utils層:
configfile類是從application.properties配置文件中獲取url和參數信息,string、json、map等類型
databaseutil類是加載database.xml文件,處理從數據庫中獲取數據信息
model層:
對應的實體類是與數據庫的表一致、還可以定義接口名的枚舉類和參數url的枚舉類
config層:
配置測試報告,extenttestngrepoter監(jiān)聽器類
case層:
寫具體的接口類,用httpclient去實現(xiàn)每個接口的調用和結果驗證
接口與接口之間的數據依賴可以通過testng的數據依賴去傳遞。
一、創(chuàng)建maven項目,配置pom文件
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20170516</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.14</version>
</dependency>
<dependency>
<groupId>com.relevantcodes</groupId>
<artifactId>extentreports</artifactId>
<version>2.41.1</version>
</dependency>
<dependency>
<groupId>com.vimalselvam</groupId>
<artifactId>testng-extentsreport</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>com.aventstack</groupId>
<artifactId>extentreports</artifactId>
<version>3.0.6</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.9.10</version>
</dependency>
二、創(chuàng)建application.properties文件
test.url=http://localhost:8086
#登錄接口
login.uri=/demo/login
#更新用戶信息接口
updateUserInfo.uri=/demo/updateUserInfo
#獲取用戶列表接口
getUserList.uri=/demo/getUserInfo
#獲取用戶信息接口
getUserInfo.uri=/demo/getUserInfo
#添加用戶接口
addUser.uri=/demo/addUser
三、創(chuàng)建databaseConfig.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 注冊對象的空間命名 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 1.加載數據庫驅動 -->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<!-- 2.數據庫連接地址 -->
<property name="url" value="jdbc:mysql://localhost:3306/dbgirl"/>
<!-- 數據庫用戶... -->
<property name="username" value="rootname"/>
<!-- 數據庫密碼... -->
<property name="password" value="passname"/>
</dataSource>
</environment>
</environments>
<!-- 注冊映射文件:java對象與數據庫之間的xml文件路徑! -->
<mappers>
<mapper resource="mapper/SQLMapper.xml"/>
</mappers>
</configuration>
四、在resource下創(chuàng)建mapper文件夾,創(chuàng)建SQLMapper.xml文件,先不寫內容
testng.xml文件也可以先建立

五、利用springboot中的spring-data-jpa創(chuàng)建實體類,創(chuàng)建數據庫表,可以參考在《springboot學習(四)》中的代碼。
也可以手動去創(chuàng)建數據庫表。
六、在java下創(chuàng)建cases、config、model、utils文件夾,分別放不同的內容
現(xiàn)在model中創(chuàng)建對應數據庫表的實體類,代碼如下:
只舉其中一個表的例子,字段值要與數據庫表的值一致
package com.course.model;
import lombok.Data;
@Data
public class User {
private Integer id; //id是主鍵,并且會自增,調接口時不需要傳參數id,傳其他參數就可以
private String userName;
private String password;
private Integer age;
private String sex;
private Integer permission;
private Integer isDelete;
@Override
public String toString() {
return "User{" +
"id=" + id +
", userName='" + userName + '\'' +
", password='" + password + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", permission=" + permission +
", isDelete=" + isDelete +
'}';
}
}
該代碼中沒有寫get和set方法,是因為用了lombok可以自動省略get和set方法
下面會用到logincase的model類,補充一下源碼:
package com.course.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "loginCase")
public class loginCase {
@Id
@GeneratedValue
private Integer id;
private String userName;
private String password;
private String expected;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getExpected() {
return expected;
}
public void setExpected(String expected) {
this.expected = expected;
}
}
在model中寫一個枚舉類,把接口寫成枚舉類型,保證傳進來的接口名稱正確
package com.course.model;
public enum InterfaceName {
LOGIN,UPDATEUSERINFO,GETUSERLIST,GETUSERINFO,ADDUSER
}
七、在config文件中創(chuàng)建ExtentTestNGIReporterListener類,測試報告會用到
Maven pom文件中添加依賴:
<dependency>
<groupId>com.relevantcodes</groupId>
<artifactId>extentreports</artifactId>
<version>2.41.1</version>
</dependency>
<dependency>
<groupId>com.vimalselvam</groupId>
<artifactId>testng-extentsreport</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>com.aventstack</groupId>
<artifactId>extentreports</artifactId>
<version>3.0.6</version>
</dependency>
在resource中增加testng.xml文件:
<?xml version="1.0" encoding="UTF-8" ?>
<suite name="用戶管理系統(tǒng)測試套件">
<test name="用戶管理系統(tǒng)測試用例">
<classes>
<class name="com.course.cases.LoginTest">
<include name="LoginTrue" />
<include name="LoginFalse" />
</class>
<class name="com.course.cases.AddUserTest">
<include name="AddUser" />
</class>
<class name="com.course.cases.GetUserInfoTest">
<include name="getUserInfo" />
</class>
<class name="com.course.cases.GetUserListTest">
<include name="getUserList" />
</class>
<class name="com.course.cases.updateUserInfo">
<include name="updateUserInfo" />
</class>
</classes>
</test>
<listeners>
<listener class-name="com.course.config.ExtentTestNGIReporterListener"/>
</listeners>
</suite>
<listener class-name="com.course.config.ExtentTestNGIReporterListener"/>
中ExtentTestNGIReporterListener類的路徑,就是下面要在config文件夾下創(chuàng)建的ExtentTestNGIReporterListener類的路徑。
在config文件夾下增加監(jiān)聽類:不用改類中的內容:
package com.course.config;
import com.aventstack.extentreports.ExtentReports;
import com.aventstack.extentreports.ExtentTest;
import com.aventstack.extentreports.ResourceCDN;
import com.aventstack.extentreports.Status;
import com.aventstack.extentreports.model.TestAttribute;
import com.aventstack.extentreports.reporter.ExtentHtmlReporter;
import com.aventstack.extentreports.reporter.configuration.ChartLocation;
import com.aventstack.extentreports.reporter.configuration.Theme;
import org.testng.*;
import org.testng.xml.XmlSuite;
import java.io.File;
import java.util.*;
public class ExtentTestNGIReporterListener implements IReporter {
//生成的路徑以及文件名
private static final String OUTPUT_FOLDER = "test-output/";
private static final String FILE_NAME = "index.html";
private ExtentReports extent;
@Override
public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) {
init();
boolean createSuiteNode = false;
if(suites.size()>1){
createSuiteNode=true;
}
for (ISuite suite : suites) {
Map<String, ISuiteResult> result = suite.getResults();
//如果suite里面沒有任何用例,直接跳過,不在報告里生成
if(result.size()==0){
continue;
}
//統(tǒng)計suite下的成功、失敗、跳過的總用例數
int suiteFailSize=0;
int suitePassSize=0;
int suiteSkipSize=0;
ExtentTest suiteTest=null;
//存在多個suite的情況下,在報告中將同一個一個suite的測試結果歸為一類,創(chuàng)建一級節(jié)點。
if(createSuiteNode){
suiteTest = extent.createTest(suite.getName()).assignCategory(suite.getName());
}
boolean createSuiteResultNode = false;
if(result.size()>1){
createSuiteResultNode=true;
}
for (ISuiteResult r : result.values()) {
ExtentTest resultNode;
ITestContext context = r.getTestContext();
if(createSuiteResultNode){
//沒有創(chuàng)建suite的情況下,將在SuiteResult的創(chuàng)建為一級節(jié)點,否則創(chuàng)建為suite的一個子節(jié)點。
if( null == suiteTest){
resultNode = extent.createTest(r.getTestContext().getName());
}else{
resultNode = suiteTest.createNode(r.getTestContext().getName());
}
}else{
resultNode = suiteTest;
}
if(resultNode != null){
resultNode.getModel().setName(suite.getName()+" : "+r.getTestContext().getName());
if(resultNode.getModel().hasCategory()){
resultNode.assignCategory(r.getTestContext().getName());
}else{
resultNode.assignCategory(suite.getName(),r.getTestContext().getName());
}
resultNode.getModel().setStartTime(r.getTestContext().getStartDate());
resultNode.getModel().setEndTime(r.getTestContext().getEndDate());
//統(tǒng)計SuiteResult下的數據
int passSize = r.getTestContext().getPassedTests().size();
int failSize = r.getTestContext().getFailedTests().size();
int skipSize = r.getTestContext().getSkippedTests().size();
suitePassSize += passSize;
suiteFailSize += failSize;
suiteSkipSize += skipSize;
if(failSize>0){
resultNode.getModel().setStatus(Status.FAIL);
}
resultNode.getModel().setDescription(String.format("Pass: %s ; Fail: %s ; Skip: %s ;",passSize,failSize,skipSize));
}
buildTestNodes(resultNode,context.getFailedTests(), Status.FAIL);
buildTestNodes(resultNode,context.getSkippedTests(), Status.SKIP);
buildTestNodes(resultNode,context.getPassedTests(), Status.PASS);
}
if(suiteTest!= null){
suiteTest.getModel().setDescription(String.format("Pass: %s ; Fail: %s ; Skip: %s ;",suitePassSize,suiteFailSize,suiteSkipSize));
if(suiteFailSize>0){
suiteTest.getModel().setStatus(Status.FAIL);
}
}
}
// for (String s : Reporter.getOutput()) {
// extent.setTestRunnerOutput(s);
// }
extent.flush();
}
private void init() {
//文件夾不存在的話進行創(chuàng)建
File reportDir= new File(OUTPUT_FOLDER);
if(!reportDir.exists()&& !reportDir .isDirectory()){
reportDir.mkdir();
}
ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter(OUTPUT_FOLDER + FILE_NAME);
// 設置靜態(tài)文件的DNS
//怎么樣解決cdn.rawgit.com訪問不了的情況
htmlReporter.config().setResourceCDN(ResourceCDN.EXTENTREPORTS);
htmlReporter.config().setDocumentTitle("api自動化測試報告");
htmlReporter.config().setReportName("api自動化測試報告");
htmlReporter.config().setChartVisibilityOnOpen(true);
htmlReporter.config().setTestViewChartLocation(ChartLocation.TOP);
htmlReporter.config().setTheme(Theme.STANDARD);
htmlReporter.config().setCSS(".node.level-1 ul{ display:none;} .node.level-1.active ul{display:block;}");
extent = new ExtentReports();
extent.attachReporter(htmlReporter);
extent.setReportUsesManualConfiguration(true);
}
private void buildTestNodes(ExtentTest extenttest, IResultMap tests, Status status) {
//存在父節(jié)點時,獲取父節(jié)點的標簽
String[] categories=new String[0];
if(extenttest != null ){
List<TestAttribute> categoryList = extenttest.getModel().getCategoryContext().getAll();
categories = new String[categoryList.size()];
for(int index=0;index<categoryList.size();index++){
categories[index] = categoryList.get(index).getName();
}
}
ExtentTest test;
if (tests.size() > 0) {
//調整用例排序,按時間排序
Set<ITestResult> treeSet = new TreeSet<ITestResult>(new Comparator<ITestResult>() {
@Override
public int compare(ITestResult o1, ITestResult o2) {
return o1.getStartMillis()<o2.getStartMillis()?-1:1;
}
});
treeSet.addAll(tests.getAllResults());
for (ITestResult result : treeSet) {
Object[] parameters = result.getParameters();
String name="";
//如果有參數,則使用參數的toString組合代替報告中的name
for(Object param:parameters){
name+=param.toString();
}
if(name.length()>0){
if(name.length()>50){
name= name.substring(0,49)+"...";
}
}else{
name = result.getMethod().getMethodName();
}
if(extenttest==null){
test = extent.createTest(name);
}else{
//作為子節(jié)點進行創(chuàng)建時,設置同父節(jié)點的標簽一致,便于報告檢索。
test = extenttest.createNode(name).assignCategory(categories);
}
//test.getModel().setDescription(description.toString());
//test = extent.createTest(result.getMethod().getMethodName());
for (String group : result.getMethod().getGroups())
test.assignCategory(group);
List<String> outputList = Reporter.getOutput(result);
for(String output:outputList){
//將用例的log輸出報告中
test.debug(output);
}
if (result.getThrowable() != null) {
test.log(status, result.getThrowable());
}
else {
test.log(status, "Test " + status.toString().toLowerCase() + "ed");
}
test.getModel().setStartTime(getTime(result.getStartMillis()));
test.getModel().setEndTime(getTime(result.getEndMillis()));
}
}
}
private Date getTime(long millis) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(millis);
return calendar.getTime();
}
}
要先把test-output文件夾展示出來,需要配置idea:
配置testng報告,進入idea,按著圖中去點擊

選擇左側測試方法,點擊listener,勾選Use default reporters,然后點擊確定

右鍵執(zhí)行testng.xml文件,就能看到test-output文件夾,查看生成的報告:

右鍵點擊index.html選擇copy path,粘貼路徑在瀏覽器中查看報告信息。
八、在utils文件中創(chuàng)建configFile,從配置文件中利用resourcebundle取url,代碼如下
package com.course.utils;
import com.course.model.InterfaceName;
import java.util.Locale;
import java.util.ResourceBundle;
public class configFile {
//從application文件中取url
private static ResourceBundle resourceBundle = ResourceBundle.getBundle("application", Locale.CHINA);
public static String getUrl(InterfaceName name){
//取域名和端口號
String baseurl = resourceBundle.getString("test.url");
String uri = "";
//如果傳進來的接口名稱正確,就返回相應接口的url
if(name == InterfaceName.LOGIN){
uri = resourceBundle.getString("login.uri");
}
if(name == InterfaceName.UPDATEUSERINFO){
uri = resourceBundle.getString("updateUserInfo.uri");
}
if(name == InterfaceName.GETUSERLIST){
uri = resourceBundle.getString("getUserList.uri");
}
if (name == InterfaceName.GETUSERINFO){
uri = resourceBundle.getString("getUserInfo.uri");
}
if(name == InterfaceName.ADDUSER){
uri = resourceBundle.getString("addUser.uri");
}
//拼接url
String testurl = baseurl + uri;
return testurl;
}
}
九、在utils中創(chuàng)建DatabaseUtil,從databaseconfig.xml配置文件中取配置信息,創(chuàng)建sqlsession并返回,在case類中就不用再寫獲取數據庫信息了
package com.course.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.Reader;
/*
把sqlsession封裝起來,從數據庫中獲取數據
*/
public class DatabaseUtil {
public static SqlSession getsqlSession() throws IOException {
//獲取配置的資源文件
Reader reader = Resources.getResourceAsReader("databaseConfig.xml");
//得到SqlSessionFactory,使用類加載器加載xml文件
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
//得到sqlsession對象,這個對象就能執(zhí)行配置文件中的sql語句啦
SqlSession session = factory.openSession();
return session;
}
}

十、在cases文件夾下寫測試類,先寫一個loginTest
package com.course.cases;
import com.course.config.TestConfig;
import com.course.model.InterfaceName;
import com.course.model.loginCase;
import com.course.utils.DatabaseUtil;
import com.course.utils.configFile;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.ibatis.session.SqlSession;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import java.io.IOException;
public class LoginTest {
//加@BeforeTest注解,在用例執(zhí)行前先獲取接口的url
@BeforeTest(groups = "loginTrue",description = "登錄接口")
public void beforeTest(){
//獲取接口地址
TestConfig.loginuri = configFile.getUrl(InterfaceName.LOGIN);
TestConfig.addUseruri = configFile.getUrl(InterfaceName.ADDUSER);
TestConfig.getUserInfouri = configFile.getUrl(InterfaceName.GETUSERINFO);
TestConfig.getUserListuri = configFile.getUrl(InterfaceName.GETUSERLIST);
TestConfig.updateUserInfouri = configFile.getUrl(InterfaceName.UPDATEUSERINFO);
TestConfig.defaultHttpClient = new DefaultHttpClient();
}
@Test(groups = "loginTrue", description = "登錄成功的接口")
public void LoginTrue() throws IOException {
//定義sqlsession對象,從DatabaseUtil類中調getsqlSession方法,直接獲取到sqlsession對象,可以執(zhí)行sql語句,不需要配置數據庫信息
SqlSession sqlSession = DatabaseUtil.getsqlSession();
//調selectone方法,通過mybatis中的sql語句查數據庫表,并返回結果賦值給logincase對象
//sqlstatement與SQLMapper.xml中的id一致,33是傳的參數id
loginCase loginCase = sqlSession.selectOne("logincase",33);
//打印查詢的結果,結果就是從數據庫中查詢到的結果
System.out.println(loginCase.toString());
System.out.println(TestConfig.loginuri);
}
//調數據庫表中第二行數據,可以認為是用戶名和密碼錯誤,登錄失敗的接口
@Test(groups = "loginFalse",description = "登錄失敗的接口")
public void LoginFalse() throws IOException {
SqlSession sqlSession = DatabaseUtil.getsqlSession();
loginCase loginCase = sqlSession.selectOne("logincase",36);
System.out.println(loginCase.toString());
System.out.println(TestConfig.loginuri);
}
}
其中TestConfig.loginuri等屬性,是在config文件夾下定義了一個TestConfig的類,給該類創(chuàng)建了多個uri名稱的屬性,這樣uri名稱就不用隨便再起,直接從這個類中調用就行。
package com.course.config;
import org.apache.http.client.CookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
public class TestConfig {
public static String loginuri;
public static String updateUserInfouri;
public static String getUserListuri;
public static String getUserInfouri;
public static String addUseruri;
public static DefaultHttpClient defaultHttpClient;
public static CookieStore cookieStore;
}

十一、執(zhí)行l(wèi)oginTest類時遇到問題:
報org.apache.ibatis.exceptions.PersistenceException:的錯誤
是因為mybatis沒有連接上數據庫,或者是數據庫和實體類的對應關系沒有建立起來
需要修改databaseconfig.xml文件,數據庫url為:
<property name="url" value="jdbc:mysql://localhost:3306/dbgirl?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8"/>
之前該url是寫在yml中,&符號不需要轉義,寫到xml中需要對 & 轉義成&
還需要加上
<typeAliases>
<typeAlias type="com.course.model.loginCase" />
<typeAlias type="com.course.model.addUserCase" />
</typeAliases>
要加到environments配置前面,要不然會報錯:
The content of element type "configuration" must match "(properties?,settings?,typeAliases?,typeHand......
<typeAlias type="com.course.model.addUserCase" /> 要寫多個,哪個實體類對應的有數據庫表都要單獨寫,要不然也會報錯。
同時mapper/SQLMapper.xml中的namespace也要寫對com.course.model
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.course.model">
<select id="logincase" parameterType="Integer" resultType="com.course.model.loginCase">
select * from loginCase where id=#{id}
</select>
</mapper>
十二、HttpClient的學習總結:
以上的框架中缺少moco接口,請參考《Moco接口框架應用實戰(zhàn)》。
下面會在前十一章搭建框架的基礎上,利用HttpClient去調接口驗證測試。
12.1 moco的接口:
登錄接口

還有一個調列表接口,不再貼圖:
TestDologin 測試類:
public class TestDologin {
//設置全局變量cookieStore
public static CookieStore cookieStore;
// //返回token值
// public static String token;
@BeforeTest(groups = "loginTrue")
public void beforetest(){
//獲取所有接口的uri
TestConfig.doLoginuri = ConfigFile.GetUrl(InterfaceName.DOLOGIN);
TestConfig.getMerchantListuri = ConfigFile.GetUrl(InterfaceName.GETMERCHANTLIST);
//獲取所有接口的參數值,doLoginParams接口是post入參json格式的,可以從配置文件中調,getMerchantListuri中已拼接了接口
TestConfig.doLoginParams = ConfigFile.GetParams(ParamsName.DOLOGINPARAMS);
}
/*
從配置文件中獲取全部的參數轉換成String類型入參
*/
@Test(groups = "loginTrue", description = "登錄成功的接口")
public void testdologin() throws IOException {
System.out.println(TestConfig.doLoginuri);
System.out.println(TestConfig.doLoginParams);
DefaultHttpClient httpClient = new DefaultHttpClient();
//該接口返回的有cookie,獲取cookie,賦值給全局變量,下一個接口setcookiestore,下一個接口入參時使用cookie
cookieStore = httpClient.getCookieStore();
//調moco的接口可以用這個
HttpPost httpPost = new HttpPost(TestConfig.doLoginuri);
//設置參數
//使用json入參,入參是中文時不行。
//設置請求頭header
httpPost.setHeader("Content-Type","application/json;charset=gbk");
//傳入參數
StringEntity entity=new StringEntity(TestConfig.doLoginParams.toString(),"gbk");
httpPost.setEntity(entity);
//獲取返回結果
HttpResponse httpResponse = httpClient.execute(httpPost);
String result = EntityUtils.toString(httpResponse.getEntity());
System.out.println(result);
}
}
TestgetMerchantList測試類:
package com.course.cases;
import com.course.config.TestConfig;
import com.course.utils.DatabaseUtil;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpParams;
import org.apache.http.util.EntityUtils;
import org.apache.ibatis.session.SqlSession;
import org.json.JSONArray;
import org.testng.Assert;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import org.apache.http.client.CookieStore;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONObject;
public class TestgetMerchantList {
public static int MerchantId;
@Test(dependsOnGroups = "loginTrue",description = "獲取列表接口")
public void TestgetMerchantList() throws URISyntaxException, IOException {
System.out.println(TestConfig.getMerchantListuri);
DefaultHttpClient httpClient = new DefaultHttpClient();
//參數已經拼接在url中
HttpGet httpGet = new HttpGet(TestConfig.getMerchantListuri);
//通過moco接口,可以使用setcookiestore方法從上一個接口獲取token值
httpClient.setCookieStore(TestDologin.cookieStore);
//調取真實的接口只能設置header,傳值token
// httpGet.setHeader("token",TestDologin.token);
HttpResponse response = httpClient.execute(httpGet);
/*
下面要驗證幾種斷言方法:
1、判斷接口返回是否是200
2、判斷一頁返回的門店列表有20條
3、判斷從第5位開始評分由大到小
4、判斷與數據庫中獲取數據是否一致
*/
//1、判斷接口返回是否是200
Assert.assertEquals(response.getStatusLine().getStatusCode(),200,"接口返回200成功");
String result = EntityUtils.toString(response.getEntity());
System.out.println(result);
JSONObject resultobject = new JSONObject(result);
JSONObject p2pdataobject = resultobject.getJSONObject("p2pdata");
JSONArray dataArray = p2pdataobject.getJSONArray("data");
//2、判斷一頁返回的門店列表有20條
Assert.assertEquals(dataArray.length(),20,"一頁返回門店列表有20條");
List scorelist = new ArrayList();
for (int i=0;i<dataArray.length();i++){
String dataresult = dataArray.get(i).toString();
JSONObject dataresultobject = new JSONObject(dataresult);
scorelist.add(dataresultobject.getInt("score"));
}
//3、判斷從第5位開始評分由大到小
for (int j=4;j<scorelist.size()-1;j++){
if ((Integer)scorelist.get(j) >= (Integer)scorelist.get(j+1)){
System.out.println("比對成功");
}else{
System.out.println("比對不成功");
}
}
String dataresult = dataArray.get(1).toString();
JSONObject dataresultobject = new JSONObject(dataresult);
MerchantId = dataresultobject.getInt("id");
System.out.println(MerchantId);
//4、判斷與數據庫中獲取數據是否一致
SqlSession sqlSession = DatabaseUtil.getsqlsession();
int marchantid = sqlSession.selectOne("getmarchantid",MerchantId);
Assert.assertEquals(marchantid,MerchantId);
}
}
總結:入參的處理方法有很多種:
get請求:
1、把參數拼接在url中
getMerchantListuri=getMerchantList?longitude=116.41090393066406&latitude=39.96870040893555
2、部分參數拼接在url中,其他參數和url拼接到一起
//url要拼接參數
URIBuilder uriBuidler = new URIBuilder(TestConfig.getCarWashDetailuri);
//需要從其他類中依賴的參數值
uriBuidler.setParameter("merchantId", String.valueOf(TestgetMerchantList.MerchantId));
HttpGet httpGet = new HttpGet(uriBuidler.build());
3、定義一個List<NameValuePair>,傳入參數
URIBuilder uriBuilder = new URIBuilder(TestConfig.getCarWashDetailuritwouri);
//第一種寫入參數的方法,定義一個List,類型是NameValuePair
List<NameValuePair> urlParameters = new ArrayList<>();
urlParameters.add(new BasicNameValuePair("merchantId", String.valueOf(TestgetMerchantList.MerchantId)));
urlParameters.add(new BasicNameValuePair("channel","6"));
uriBuilder.setParameters(urlParameters);
HttpGet httpGet = new HttpGet(uriBuilder.build());
post請求:
1、獲取object類型的參數
配置文件中這些寫:
doLoginParams={"phone": "15677777777","validateCode": "9612"}
寫一個工具類,定義一個獲取object參數的方法
//定義一個靜態(tài)的方法,獲取post接口的參數值,返回類型是Object類型
public static Object GetParams(ParamsName name){
if (name.equals(ParamsName.DOLOGINPARAMS)){
Object doLoginParams = resourceBundle.getObject("doLoginParams");
return doLoginParams;
}
return null;
}
在httpclient中入參直接這樣寫:
TestConfig.doLoginParams = ConfigFile.GetParams(ParamsName.DOLOGINPARAMS);
//傳入參數
StringEntity entity=new StringEntity(TestConfig.doLoginParams.toString(),"gbk");
httpPost.setEntity(entity);
注:直接配置文件中取String類型的參數值也行,不用傳object的也可以
2、配置文件中寫部分參數
doLoginParamstwo={"phone": "15677777777"}
工具類中寫,定義一個返回map類型參數的方法:
//定義一個靜態(tài)的方法,獲取post接口的參數值,返回類型為map,這樣有些參數可以依賴其他的接口,或者手動填寫參數
public static Map<String,Object> GetParamswithMap(ParamsName name){
if (name == ParamsName.DOLOGINPARAMSTWOPARAMS){
String doLoginParams = resourceBundle.getString("doLoginParamstwo");
return JSON.parseObject(doLoginParams);
}
return null;
}
在httpclient中入參直接這樣寫:
TestConfig.doLoginParamstwo = ConfigFile.GetParamswithMap(ParamsName.DOLOGINPARAMSTWOPARAMS);
//傳入參數
Map<String,Object> map = TestConfig.doLoginParamstwo;
map.put("validateCode",TestSendVerificationCode.validate_code);//也可以使用數據依賴傳參數
StringEntity entity=new StringEntity(map.toString(),"gbk");
httpPost.setEntity(entity);
十三、TestNg的擴展學習總結:
1、添加依賴:
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.9.10</version>
</dependency>
2、寫一個方法,使用@Test注解
package com.course.LearnTestNgCases;
import org.testng.annotations.Test;
/*
test注解
*/
public class BasicAnnotation {
@Test
public void testcaseone(){
System.out.println("這是最基本的注解,用來把方法標記為測試的一部分");
}
}
3、@BeforeMethod @AfterMethod注解
package com.course.LearnTestNgCases;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
/*
test注解
*/
public class BasicAnnotation {
@Test
public void testcaseone(){
System.out.println("這是最基本的注解,用來把方法標記為測試的一部分");
}
@Test
public void testcasetwo(){
System.out.println("第二個測試方法");
}
@BeforeMethod
public void beforemethod(){
System.out.println("測試方法之前運行");
}
@AfterMethod
public void aftermethod(){
System.out.println("測試方法之后運行");
}
}
測試結果為:

4、@BeforeClass @AfterClass 注解
package com.course.LearnTestNgCases;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import org.testng.annotations.*;
/*
test注解
*/
public class BasicAnnotation {
public BasicAnnotation(){
System.out.println("kkkkkk");
}
@Test
public void testcaseone(){
System.out.println("這是最基本的注解,用來把方法標記為測試的一部分");
}
@Test
public void testcasetwo(){
System.out.println("第二個測試方法");
}
@BeforeMethod
public void beforemethod(){
System.out.println("測試方法之前運行");
}
@AfterMethod
public void aftermethod(){
System.out.println("測試方法之后運行");
}
@BeforeClass
public void beforeclass(){
System.out.println("在類之前運行");
}
@AfterClass
public void afterclass(){
System.out.println("在類之后運行");
}
}
測試結果:

5、@BeforeTest、@AfterTest注解
package com.course.LearnTestNgCases;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import org.testng.annotations.*;
/*
test注解
*/
public class BasicAnnotation {
@Test
public void testcaseone(){
System.out.println("這是最基本的注解,用來把方法標記為測試的一部分");
}
@Test
public void testcasetwo(){
System.out.println("第二個測試方法");
}
@BeforeSuite
public void beforesuite(){
System.out.println("beforesuite測試套件,在BeforeClass類之前運行");
}
@AfterSuite
public void aftersuite(){
System.out.println("aftersuite測試套件,在Afterclass類之后運行");
}
@BeforeTest
public void beforetest(){
System.out.println("beforetest在測試之前運行");
}
@AfterTest
public void aftertest(){
System.out.println("aftertest在測試之后運行");
}
@BeforeClass
public void beforeclass(){
System.out.println("在類之前運行");
}
@AfterClass
public void afterclass(){
System.out.println("在類之后運行");
}
@BeforeMethod
public void beforemethod(){
System.out.println("測試方法之前運行");
}
@AfterMethod
public void aftermethod(){
System.out.println("測試方法之后運行");
}
}
測試結果:

6、@BeforeSuite、@AfterSuite注解
package com.course.LearnTestNgCases;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import org.testng.annotations.*;
/*
test注解
*/
public class BasicAnnotation {
@Test
public void testcaseone(){
System.out.println("這是最基本的注解,用來把方法標記為測試的一部分");
}
@Test
public void testcasetwo(){
System.out.println("第二個測試方法");
}
@BeforeMethod
public void beforemethod(){
System.out.println("測試方法之前運行");
}
@AfterMethod
public void aftermethod(){
System.out.println("測試方法之后運行");
}
@BeforeClass
public void beforeclass(){
System.out.println("在類之前運行");
}
@AfterClass
public void afterclass(){
System.out.println("在類之后運行");
}
@BeforeSuite
public void beforesuite(){
System.out.println("beforesuite測試套件,在BeforeClass類之前運行");
}
@AfterSuite
public void aftersuite(){
System.out.println("aftersuite測試套件,在Afterclass類之后運行");
}
}
測試結果:

總結:
testng的注解執(zhí)行順序如下:
BeforeSuite > BeforeTest > BeforeClass > BeforeMethod > Test
測試套件 > 測試組 > 測試類 > 測試方法 >具體的測試方法
7、套件測試:先創(chuàng)建3個類,其中一個是SuiteConfig類
package com.course.LearnTestNgCases.Suite;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeSuite;
/*
測試套件的配置
*/
public class SuiteConfig {
@BeforeSuite
public void beforesuite(){
System.out.println("在測試套件前執(zhí)行");
}
@AfterSuite
public void aftersuite(){
System.out.println("在測試套件后執(zhí)行");
}
}
另外兩個是LoginTest類和PayTest類
package com.course.LearnTestNgCases.Suite;
import org.testng.annotations.Test;
/*
測試類
*/
public class LoginTest {
@Test
public void logintest(){
System.out.println("登錄成功");
}
}
package com.course.LearnTestNgCases.Suite;
import org.testng.annotations.Test;
public class PayTest {
@Test
public void paytest(){
System.out.println("支付成功");
}
}
創(chuàng)建suite.xml 名稱可以隨意起,該文件中寫測試類執(zhí)行的順序:

與上面講的注解順序一致。
把suite.xml文件寫全,如下:
<?xml version="1.0" encoding="UTF-8" ?>
<suite name="學習testng的suite測試套件">
<test name="測試LoginTest類" >
<classes>
<class name="com.course.LearnTestNgCases.Suite.LoginTest">
<include name="com.course.LearnTestNgCases.Suite.LoginTest.logintest"/>
</class>
<class name="com.course.LearnTestNgCases.Suite.SuiteConfig"/>
</classes>
</test>
<test name="測試PayTest類">
<classes>
<class name="com.course.LearnTestNgCases.Suite.SuiteConfig"/>
<class name="com.course.LearnTestNgCases.Suite.PayTest"/>
</classes>
</test>
</suite>

右鍵運行suite.xml文件,查看運行結果:

正常情況下,這些寫會比較合理:
<?xml version="1.0" encoding="UTF-8" ?>
<suite name="學習testng的suite測試套件">
<test name="測試LoginTest類" >
<classes>
<class name="com.course.LearnTestNgCases.Suite.SuiteConfig"/>
<class name="com.course.LearnTestNgCases.Suite.LoginTest"/>
<class name="com.course.LearnTestNgCases.Suite.PayTest"/>
</classes>
</test>
</suite>
運行結果:

8、忽略測試
@Test(enabled = false) 忽略測試
@Test(enabled = true) 執(zhí)行測試
package com.course.LearnTestNgCases;
import org.testng.annotations.Test;
public class IgnoreTest {
@Test
public void ignore1(){
System.out.println("執(zhí)行測試");
}
@Test(enabled = false)
public void ignore2(){
System.out.println("enable=false 忽略測試");
}
@Test(enabled = true)
public void ignore3(){
System.out.println("enable=true 執(zhí)行測試");
}
}
執(zhí)行結果:

9、testng組測試
9.1、方法分組
在方法上加@Test(groups="groupsname")進行分組
@BeforeGroups(“groupsname”) 、@AfterGroups(“groupsname”)注解,在對應的測試組執(zhí)行之前執(zhí)行,在對應的測試組執(zhí)行之后執(zhí)行
package com.course.LearnTestNgCases.Groups;
import org.testng.annotations.AfterGroups;
import org.testng.annotations.BeforeGroups;
import org.testng.annotations.Test;
public class TestGroups {
@Test(groups = "server")
public void testserver(){
System.out.println("服務端測試用例");
}
@Test(groups = "client")
public void testclient(){
System.out.println("客戶端測試用例");
}
@BeforeGroups("server")
public void beforeservergroups(){
System.out.println("在服務端測試用例執(zhí)行之前執(zhí)行");
}
@AfterGroups("server")
public void afterservergroups(){
System.out.println("在服務端測試用例執(zhí)行之后執(zhí)行");
}
@BeforeGroups("client")
public void beforeclientgroups(){
System.out.println("在客戶端測試用例執(zhí)行之前執(zhí)行");
}
@AfterGroups("client")
public void afterclientgroups(){
System.out.println("在客戶端測試用例執(zhí)行之后執(zhí)行");
}
}
groups組的名字要一致。
執(zhí)行結果:

9.2 類分組
先建3個類,在每個類上加不同的groups,在testng.xml中通過配置groups來分組執(zhí)行測試用例

groupclass1類,@Test(groups = "student"):
package com.course.LearnTestNgCases.Groups;
import org.testng.annotations.Test;
@Test(groups = "student")
public class groupclass1 {
public void testgroupclass1(){
System.out.println("這是student分組下的測試用例1");
}
}
groupclass2類,@Test(groups = "student")
package com.course.LearnTestNgCases.Groups;
import org.testng.annotations.Test;
@Test(groups = "student")
public class groupclass2 {
public void testgroupclass2(){
System.out.println("這是student分組下的測試用例2");
}
}
groupclass3類,@Test(groups = "teacher"),和上面兩個類的分組不同
package com.course.LearnTestNgCases.Groups;
import org.testng.annotations.Test;
@Test(groups = "teacher")
public class groupclass3 {
public void testgroupclass3(){
System.out.println("這是teacher分組下的測試用例");
}
}
新建一個xml,添加groups配置,只包含student組

右鍵執(zhí)行該xml,查看結果中只執(zhí)行了groupclass1和groupclass2兩個類,groupclass3類沒有執(zhí)行。

加上teacher分組,再次執(zhí)行就能看到執(zhí)行了groupclass3類


10、異常測試:
執(zhí)行中有異常,期望測試結果就是異常,用@Test(expectedExceptions = RuntimeException.class)注解使測試用例執(zhí)行結果為正常
package com.course.LearnTestNgCases;
import org.testng.annotations.Test;
/*
異常測試,期望測試結果就是異常,用@Test(expectedExceptions = RuntimeException.class)注解使測試用例執(zhí)行結果為正常
*/
public class Exceptiontest {
@Test(expectedExceptions = RuntimeException.class)
public void testExceptiontest(){
System.out.println("執(zhí)行時會有異常,預期結果就是希望有異常,所以測試用例會執(zhí)行通過。本用例要拋出一個異常");
//主動拋出一個異常或者制造一個異常
// throw new RuntimeException();
int a=10;
int b=0;
int c=a/b;//b為0,肯定會報異常。
}
}
11、依賴測試
11.1 依賴方法
dependsOnMethods = {"testfirst"}
代碼如下:
@Test
public void testfirst(){
System.out.println("testfirst方法執(zhí)行通過后再執(zhí)行下面的方法,執(zhí)行失敗不執(zhí)行下面的方法");
}
@Test(dependsOnMethods = {"testfirst"})
public void testsencond(){
System.out.println("testsencond方法依賴testfirst方法");
}
11.2 依賴組
dependsOnGroups = "testthird"
代碼如下:
@Test(groups = "testthird")
public void testthird(){
System.out.println("testthird方法執(zhí)行通過后再執(zhí)行下面依賴該組的方法,執(zhí)行失敗不執(zhí)行下面的方法");
}
@Test(dependsOnGroups = "testthird")
public void testfourth(){
System.out.println("testfourth方法依賴testthird方法");
}
12、參數化測試
12.1 參數化測試,xml文件參數化
在xml中寫入:

在類中寫入@parameter({"name","age"})注解,輸入參數

12.2 參數化測試,@DataProvider方法
先寫一個dataprovider方法,定義一個name值,返回類型必須是Object[][]或者Iterator<Object[]>,參數個數可以為一個或者多個
//數據方法
@DataProvider(name = "data")
public Object[][] dataprovider(){
Object[][] objects = new Object[][]{
{"zhaowei",40,"kkk",180},
{"suyoupeng",42,"jjj",170},
{"linxinru",43,"ppp",160}
};
return objects;
}
測試用例方法,@Test注解中要寫(dataProvider = "data"),data名稱與上面定義的name值一致,參數值傳了3遍,執(zhí)行結果有三條記錄:
//測試用例
@Test(dataProvider = "data")
public void testdataprovider(String name,int age,String address,int height){
System.out.println("通過DataProvider傳過來的name是"+name+",age是"+age+",地址是"+address+",身高是"+height);
}

定義兩個方法,分別傳入不同的值,通過dataprovider傳值
方法代碼如下:
@Test(dataProvider = "datatwo")
public void testdologin(String username,String password){
System.out.println("登錄成功,用戶名是"+username+",密碼是"+password);
}
@Test(dataProvider = "datatwo")
public void testgetmerchantlist(int merchantid,String merchantname,String merchantaddress){
System.out.println("查詢商戶列表成功,商戶id是"+merchantid+",商戶名稱是"+merchantname+",商戶地址是"+merchantaddress);
}
dataprovider方法為:
//數據方法二,根據不同的方法傳不同的值進來
@DataProvider(name = "datatwo")
public Object[][] dataprovidertwo(Method method){
Object[][] result = null;
if (method.getName().equals("testdologin")){
result = new Object[][]{
{"18600228000","123456"}
};
}else if (method.getName().equals("testgetmerchantlist")){
result = new Object[][]{
{38159,"秦小姐洗車門店","北京市通州區(qū)"}
};
}
return result;
}
根據調Method類的getName方法獲取不同方法的名稱,判斷不同的名稱返回不同的值。

擴展@dataprovider的使用:
12.3 從另一個類中獲取數據依賴
第一個類中專門寫數據
package com.course.LearnTestNgCases.GroupsAndDataprovider;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
/*
專門提供數據的類,在使用數據信息的地方用@Test(dataProvider = "data",dataProviderClass = class111.class)來引入
*/
public class class111 {
@DataProvider(name = "data")
public static Object[][] dataprovider(){
Object[][] objects = new Object[][]{
{"zhaowei",40,"kkk",180},
{"suyoupeng",42,"jjj",170},
{"linxinru",43,"ppp",160}
};
return objects;
}
}
第二個類用@Test(dataProvider = "data",dataProviderClass = class111.class)來引入測試數據
package com.course.LearnTestNgCases.GroupsAndDataprovider;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class class222 {
//測試用例,從class111類中引入測試數據
@Test(dataProvider = "data",dataProviderClass = class111.class)
public void testdataprovider(String name,int age,String address,int height){
System.out.println("通過DataProvider傳過來的name是"+name+",age是"+age+",地址是"+address+",身高是"+height);
}
}
12.4 返回類型為迭代器Iterator<Object[]>
在class111類中寫入如下代碼:
@DataProvider(name="getmerchatidAndname")
public static Iterator<Object[]> getmerchatidAndnameDataProvider() throws IOException {
//定義Object[]類型的list
List<Object[]> result=new ArrayList<Object[]>();
//創(chuàng)建sqlsession的實例
SqlSession session= DatabaseUtil.getsqlsession();
//從數據庫中獲取數據信息
List<Object> alldata=session.selectList("getmerchatidAndname");
//從數據庫中獲取的數據類型是Object,該方法返回的類型是Object[],所以需要做類型轉換
//用foreach循環(huán)對alldata中的每一條數據做形式轉換,并存儲到result中
for (Object u : alldata) {
//做一個形式轉換
result.add(new Object[] { u });
}
//然后result的迭代器類型的數據
return result.iterator();
//下面的代碼與上面的代碼功能類似就是做一個形式轉換,從Object類型轉換成Object[]類型
//生成一個迭代器,對迭代器中的每一個數據做形式轉換
//迭代器的方法有hasNext()是否有值,有值為真,沒值為假,it.next()獲取具體的值
// Iterator it=alldata.iterator();
// while(it.hasNext()){
// result.add(new Object[] { it.next() });
// }
// return result.iterator();
}
然后在model層建一個實體類:
package com.course.model;
public class Cfwshop {
private int id;
private String shop_name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getShop_name() {
return shop_name;
}
public void setShop_name(String shop_name) {
this.shop_name = shop_name;
}
@Override
public String toString() {
return "Cfwshop{" +
"id=" + id +
", shop_name='" + shop_name + '\'' +
'}';
}
}
再在SQLMapper.xml中寫sql語句,id與session.selectList("getmerchatidAndname");中的值一致。
<select id="getmerchatidAndname" resultType="com.course.model.Cfwshop">
select id,shop_name from autosvc.cfw_shop LIMIT 10;
</select>
再在測試類中調dataprovider數據類,入參類型是Cfwshop cfwshop 實體類,通過get方法獲取具體值。
//測試用例,從class111類中引入Iterator類型的測試數據
//需要創(chuàng)建一個Cfwshop的實體類,這樣才能接收到具體的值
@Test(dataProvider = "getmerchatidAndname",dataProviderClass = class111.class)
public void testgetmerchatidAndname(Cfwshop cfwshop){
System.out.println("商戶id為:"+cfwshop.getId()+",商戶名稱為:"+cfwshop.getShop_name());
}
13、多線程測試
@Test(invocationCount = 10,threadPoolSize = 3)
package com.course.LearnTestNgCases.MultiThread;
import org.testng.annotations.Test;
/*
驗證使用多線程
*/
public class MultiThreadOnAnnotation {
//設置10個線程,3個線程池,從線程池中取3個線程,用3個線程執(zhí)行10遍測試用例,如果不設置threadPoolSize,就會默認用一個線程執(zhí)行10遍,所以要設置一個線程池
@Test(invocationCount = 10,threadPoolSize = 3)
public void testMultiThreadOnAnnotation(){
System.out.println("1");
System.out.printf("Thread id: %s%n",Thread.currentThread().getId());
}
}

在xml中通過配置線程數來控制線程:
先寫一個測試類,有3個測試方法:
package com.course.LearnTestNgCases.MultiThread;
import org.testng.annotations.Test;
public class MultiThreadOnXml {
@Test
public void test1(){
System.out.printf("Thread Id : %s%n",Thread.currentThread().getId());
}
@Test
public void test2(){
System.out.printf("Thread Id : %s%n",Thread.currentThread().getId());
}
@Test
public void test3(){
System.out.printf("Thread Id : %s%n",Thread.currentThread().getId());
}
}
查看xml文件:
parallel="methods":
<?xml version="1.0" encoding="UTF-8" ?>
<suite name="thread" parallel="methods" thread-count="3">
<!--
parallel="methods",methods級別,所有用例都可以在不同的線程中執(zhí)行
thread-count:代表了最大并發(fā)線程數
xml文件配置這種方式不能指定線程池,只有方法上才可以指定線程池
-->
<test name="testthread">
<classes>
<class name="com.course.LearnTestNgCases.MultiThread.MultiThreadOnXml"/>
</classes>
</test>
</suite>
執(zhí)行結果為:

parallel="classes":
<?xml version="1.0" encoding="UTF-8" ?>
<suite name="thread" parallel="classes" thread-count="3">
<!--
parallel="methods",methods級別,所有用例都可以在不同的線程中執(zhí)行
thread-count:代表了最大并發(fā)線程數
xml文件配置這種方式不能指定線程池,只有方法上才可以指定線程池
parallel="classes",classs級別:
相同的class tag 下的用例在同一個線程中執(zhí)行
不同的class tag 下的用例可以在不同的線程中執(zhí)行
-->
<test name="testthread">
<classes>
<class name="com.course.LearnTestNgCases.MultiThread.MultiThreadOnXml"/>
</classes>
</test>
</suite>
測試結果:

創(chuàng)建不同的classs:
<?xml version="1.0" encoding="UTF-8" ?>
<suite name="thread" parallel="classes" thread-count="6" >
<!--
parallel="methods",methods級別,所有用例都可以在不同的線程中執(zhí)行
thread-count:代表了最大并發(fā)線程數
xml文件配置這種方式不能指定線程池,只有方法上才可以指定線程池
parallel="classes",classs級別:
相同的class tag 下的用例在同一個線程中執(zhí)行
不同的class tag 下的用例可以在不同的線程中執(zhí)行
-->
<test name="testthread">
<classes name="demo1">
<class name="com.course.LearnTestNgCases.MultiThread.MultiThreadOnXml"/>
<class name="com.course.LearnTestNgCases.MultiThread.MultiThreadOnXmltwo"/>
</classes>
<classes name="demo2">
<class name="com.course.LearnTestNgCases.MultiThread.MultiThreadOnXml"/>
<class name="com.course.LearnTestNgCases.MultiThread.MultiThreadOnXmltwo"/>
</classes>
</test>
</suite>
測試結果,線程會有些亂:

parallel="tests",tests級別:
相同的test tag 下的用例在同一個線程中執(zhí)行
不同的test tag 下的用例可以在不同的線程中執(zhí)行
<?xml version="1.0" encoding="UTF-8" ?>
<suite name="thread" parallel="tests" thread-count="3" >
<!--
parallel="methods",methods級別,所有用例都可以在不同的線程中執(zhí)行
thread-count:代表了最大并發(fā)線程數
xml文件配置這種方式不能指定線程池,只有方法上才可以指定線程池
parallel="classes",classs級別:
相同的class tag 下的用例在同一個線程中執(zhí)行
不同的class tag 下的用例可以在不同的線程中執(zhí)行
parallel="tests",tests級別:
相同的test tag 下的用例在同一個線程中執(zhí)行
不同的test tag 下的用例可以在不同的線程中執(zhí)行
-->
<test name="testthread">
<classes name="demo1">
<class name="com.course.LearnTestNgCases.MultiThread.MultiThreadOnXml"/>
<class name="com.course.LearnTestNgCases.MultiThread.MultiThreadOnXmltwo"/>
</classes>
</test>
</suite>

設置不同的tests:
<?xml version="1.0" encoding="UTF-8" ?>
<suite name="thread" parallel="tests" thread-count="3" >
<!--
parallel="methods",methods級別,所有用例都可以在不同的線程中執(zhí)行
thread-count:代表了最大并發(fā)線程數
xml文件配置這種方式不能指定線程池,只有方法上才可以指定線程池
parallel="classes",classs級別:
相同的class tag 下的用例在同一個線程中執(zhí)行
不同的class tag 下的用例可以在不同的線程中執(zhí)行
parallel="tests",tests級別:
相同的test tag 下的用例在同一個線程中執(zhí)行
不同的test tag 下的用例可以在不同的線程中執(zhí)行
-->
<test name="testthread">
<classes name="demo1">
<class name="com.course.LearnTestNgCases.MultiThread.MultiThreadOnXml"/>
</classes>
</test>
<test name="testthread2">
<classes name="demo2">
<class name="com.course.LearnTestNgCases.MultiThread.MultiThreadOnXmltwo"/>
</classes>
</test>
</suite>
測試結果:

總結:正常情況下,用methods級別會比較多,多條用例在多個線程下執(zhí)行,會節(jié)省測試時間。
14、超時測試
@Test(timeOut = 3000)//超時等待3000毫秒,就是3秒
package com.course.LearnTestNgCases;
import org.testng.annotations.Test;
/*
超時測試,測試中調接口可能會超時,導致調接口失敗,通過設置超時時間,使用例執(zhí)行通過。
*/
public class TimeOutTest {
@Test(timeOut = 3000)//超時等待3000毫秒,就是3秒
public void test1() throws InterruptedException {
//設置一個線程,讓線程休眠2000毫秒,等待時間大于休眠時間,所以用例會執(zhí)行成功
Thread.sleep(2000);
}
@Test(timeOut = 3000)
public void test2() throws InterruptedException {
//設置一個線程,讓線程休眠4000毫秒,等待時間小于休眠時間,所以用例會執(zhí)行失敗
Thread.sleep(4000);
}
}
注:更多的超時設置可以參考:
摘抄自:https://blog.csdn.net/u011191463/article/details/78664896