TestNG
TestNG是一個測試Java應用程序的開源框架,類似JUnit和NUnit。
環境配置
- 配置好java環境,命令行
java -version
查看 - 官網 , 下載對應系統下jar文件
- 系統環境變量中添加指向jar文件所在路徑
- Eclipse中安裝testng,Help -> Install New Software,Add http://beust.com/eclipse
TestNG 基本用法
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.testng.annotations.Test;
public class TestNGLearn1 {
@BeforeClass
public void beforeClass() {
System.out.println("this is before class");
}
@Test
public void TestNgLearn() {
System.out.println("this is TestNG test case");
}
@AfterClass
public void afterClass() {
System.out.println("this is after class");
}
}
TestNG 基本註解
註解 | 描述 |
---|---|
@BeforeSuite | 註解的方法將只運行一次,運行所有測試前此套件中 |
@AfterSuite | 註解的方法將只運行一次此套件中的所有測試都運行之後 |
@BeforeClass | 註解的方法將只運行一次先行先試在當前類中的方法調用 |
@AfterClass | 註解的方法將只運行一次後已經運行在當前類中的所有測試方法 |
@BeforeTest | 註解的方法將被運行之前的任何測試方法屬於內部類的 <test>標籤的運行 |
@AfterTest | 註解的方法將被運行後,所有的測試方法,屬於內部類的<test>標籤的運行 |
@BeforeGroups | 組的列表,這種配置方法將之前運行。此方法是保證在運行屬於任何這些組第一個測試方法,該方法被調用 |
@AfterGroups | 組的名單,這種配置方法後,將運行。此方法是保證運行後不久,最後的測試方法,該方法屬於任何這些組被調用 |
@BeforeMethod | 註解的方法將每個測試方法之前運行 |
@AfterMethod | 被註釋的方法將被運行後,每個測試方法 |
@DataProvider | 標誌着一個方法,提供數據的一個測試方法。註解的方法必須返回一個Object[] [],其中每個對象[]的測試方法的參數列表中可以分配。該@Test 方法,希望從這個DataProvider的接收數據,需要使用一個dataProvider名稱等於這個註解的名字 |
@Factory | 作爲一個工廠,返回TestNG的測試類的對象將被用於標記的方法。該方法必須返回Object[] |
@Listeners | 定義一個測試類的監聽器 |
@Parameters | 將xml文件中參數傳遞給@Test方法 |
@Test | 標記一個類或方法作爲測試的一部分 |
testng.xml
屬性 | 描述 |
---|---|
name | suite的名字(他會出現在測試報告中) |
junit | 是否以junit模式運行 |
verbose | 在控制檯中如何輸出,這個設置不影響html版本的測試報告 |
parallel | 是否使用多線程測試(可加速測試) |
configfailurepolicy | 是否在運行失敗了一次後繼續嘗試或跳過 |
thread-count | 如果設置了parallel,可以設置線程數 |
annotations | 有‘javadoc’的時候尋找,沒有的話使用jdk5的註釋 |
time-out | 在終止method (如果parallel="methods") 或者test (如果parallel="tests")之前設置以毫秒爲單位的等待時間 |
skipfailedinvocationcounts | 是否跳過失敗的調用 |
data-provider-thread-count | 提供一個整數線程池的範圍爲了使用parallel data |
object-factory | 一個繼承IObjectFactory的類,被用來實例化測試對象 |
allow-return-values | 如果設置true,將會運行測試用例並返回值 |
示例
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="Suite" parallel="tests" thread-count="5">
<test name="Test" preserve-order="true" verbose="2">
<parameter name="userName" value="15952031403"></parameter>
<parameter name="originPwd" value="c12345"></parameter>
<classes>
<class name="com.unionpay.testng.UPPayRegisterTest">
</class>
</classes>
</test>
<!-- <test name="Test1" preserve-order="true">
<classes>
<class name="com.unionpay.testng.Test2">
</class>
</classes>
</test>
<test name="Test2" preserve-order="true">
<classes>
<class name="com.unionpay.testng.Test3">
</class>
</classes>
</test> -->
</suite> <!-- Suite -->
parallel
和thread-count
分別指定並行化測試的範圍(tests
,methods
、classes
)和並行線程數preserve-order
,設爲true時,節點下的方法按順序執行verbose
表示記錄的日誌級別,在0-10之間取值<parameter name="userName", value="1595203xxxx"/>
給測試代碼傳遞參數鍵值對,在測試類中通過註解@Parameters({"userName","originPwd"})
獲取
參數化測試
當測試邏輯一樣,只是參數不一樣時,採用數據驅動測試機制,避免寫重複代碼。TestNG中通過
@DataProvider
實現數據驅動。
利用@DataProvider做數據驅動,數據源文件可以是EXCEL,XML,甚至可以是TXT文本。以讀取xml文件爲例,通過@DataProvider讀取XML文件中數據,然後測試方法只要標示獲取數據來源的DataProvider,那麼對應的DataProvider會把讀取的數據傳給該test方法。
DataProvider原理
1. 構建xml數據文件
<?xml version="1.0" encoding="UTF-8"?>
<data>
<login>
<username>user1</username>
<password>123456</password>
</login>
<login>
<username>user2</username>
<password>345678</password>
</login>
</data>
2. 讀取xml文件
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
public class ParseXml {
/**
* 利用Dom4j解析xml文件,返回list
* @param xmlFileName
* @return
*/
public static List parse3Xml(String xmlFileName){
File inputXml = new File(xmlFileName);
List list= new ArrayList();
int count = 1;
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(inputXml);
Element items = document.getRootElement();
for (Iterator i = items.elementIterator(); i.hasNext();) {
Element item = (Element) i.next();
Map map = new HashMap();
Map tempMap = new HashMap();
for (Iterator j = item.elementIterator(); j.hasNext();) {
Element node = (Element) j.next();
tempMap.put(node.getName(), node.getText());
}
map.put(item.getName(), tempMap);
list.add(map);
}
} catch (DocumentException e) {
System.out.println(e.getMessage());
}
System.out.println(list.size());
return list;
}
}
3. DataProvider類
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
public class GenerateData {
public static List list = new ArrayList();
@DataProvider(name = "dataProvider")
public static Object[][] dataProvider(Method method){
list = ParseXml.parse3Xml("absolute path of xml file");
List<Map<String, String>> result = new ArrayList<Map<String, String>>();
for (int i = 0; i < list.size(); i++) {
Map m = (Map) list.get(i);
if(m.containsKey(method.getName())){
Map<String, String> dm = (Map<String, String>) m.get(method.getName());
result.add(dm);
}
}
if(result.size() > 0){
Object[][] files = new Object[result.size()][];
for(int i=0; i<result.size(); i++){
files[i] = new Object[]{result.get(i)};
}
return files;
}else {
Assert.assertTrue(result.size()!=0,list+" is null, can not find"+method.getName() );
return null;
}
}
}
4. 在test方法中引用dataprovider
public class LoginTest {
@Test(dataProvider="dataProvider", dataProviderClass= GenerateData.class)
public void login(Map<String, String> param) throws InterruptedException{
List<WebElement> edits = findElementsByClassName(AndroidClassName.EDITTEXT);
edits.get(0).sendkeys(param.get("username"));
edits.get(1).sendkeys(param.get("password"));
}
}
xml中的父節點與test方法名對應,因此xml中同名父節點的個數就意味着該test方法會被重複執行多少次; 當dataprovider與test方法不在同一個類時,需指明dataprovider類,如
dataProviderClass= GenerateData.class
TestNG重寫監聽類
TestNG會監聽每個測試case的運行結果,有時候我們需要定製一些其他功能,如自動截圖,發送數據給服務器等。方法是新建一個繼承TestListenerAdapter的類。
重寫完成後,在需要的test方法前添加註解@Listeners(TestNGListener.class)
package com.unionpay.listener;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.testng.ITestContext;
import org.testng.ITestResult;
import org.testng.TestListenerAdapter;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.unionpay.base.BaseTest;
import com.unionpay.constants.CapabilitiesBean;
import com.unionpay.constants.CaseCountBean;
import com.unionpay.constants.ResultBean;
import com.unionpay.util.Assertion;
import com.unionpay.util.PostService;
import com.unionpay.util.ReadCapabilitiesUtil;
/**
* 帶有post請求的testng監聽
* @author lichen2
*/
public class TestNGListenerWithPost extends TestListenerAdapter{
//接收每個case結果的接口
private String caseUrl;
//接收整個test運行數據的接口
private String countUrl;
//接收test運行狀態的接口
private String statusUrl;
private JsonObject caseResultJson = new JsonObject();
private JsonObject caseCountJson = new JsonObject();
private Gson gson = new Gson();
private ResultBean result = new ResultBean();
private CaseCountBean caseCount = new CaseCountBean();
private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private CapabilitiesBean capabilitiesBean = ReadCapabilitiesUtil.readCapabilities("setting.json");
private String testStartTime;
private String testEndTime;
private String runId;
//testng初始化
@Override
public void onStart(ITestContext testContext) {
super.onStart(testContext);
String serverUrl = capabilitiesBean.getServerurl();
caseUrl = "http://"+serverUrl+"/api/testcaseResult";
countUrl = "http://"+serverUrl+"/api/testcaseCount";
statusUrl = "http://"+serverUrl+"/api/testStatus";
runId = capabilitiesBean.getRunid();
result.setRunId(runId);
caseCount.setRunId(runId);
}
//case開始
@Override
public void onTestStart(ITestResult tr) {
Assertion.flag = true;
Assertion.errors.clear();
sendStatus("運行中");
result.setStartTime(format.format(new Date()));
}
//case成功執行
@Override
public void onTestSuccess(ITestResult tr) {
super.onTestSuccess(tr);
sendResult(tr);
takeScreenShot(tr);
}
//case執行失敗
@Override
public void onTestFailure(ITestResult tr) {
super.onTestFailure(tr);
sendResult(tr);
try {
takeScreenShot(tr);
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
this.handleAssertion(tr);
}
//case被跳過
@Override
public void onTestSkipped(ITestResult tr) {
super.onTestSkipped(tr);
takeScreenShot(tr);
sendResult(tr);
this.handleAssertion(tr);
}
//所有case執行完成
@Override
public void onFinish(ITestContext testContext) {
super.onFinish(testContext);
sendStatus("正在生成報告");
sendFinishData(testContext);
}
/**
* 發送case測試結果
* @param tr
*/
public void sendResult(ITestResult tr){
result.setTestcaseName(tr.getName());
result.setEndTime(format.format(new Date()));
float tmpDuration = (float)(tr.getEndMillis() - tr.getStartMillis());
result.setDuration(tmpDuration / 1000);
switch (tr.getStatus()) {
case 1:
result.setTestResult("SUCCESS");
break;
case 2:
result.setTestResult("FAILURE");
break;
case 3:
result.setTestResult("SKIP");
break;
case 4:
result.setTestResult("SUCCESS_PERCENTAGE_FAILURE");
break;
case 16:
result.setTestResult("STARTED");
break;
default:
break;
}
caseResultJson.addProperty("result", gson.toJson(result));
PostService.sendPost(caseUrl, caseResultJson.toString());
}
/**
* 通知test完成
* @param testContext
*/
public void sendFinishData(ITestContext tc){
testStartTime = format.format(tc.getStartDate());
testEndTime = format.format(tc.getEndDate());
long duration = getDurationByDate(tc.getStartDate(), tc.getEndDate());
caseCount.setTestStartTime(testStartTime);
caseCount.setTestEndTime(testEndTime);
caseCount.setTestDuration(duration);
caseCount.setTestSuccess(tc.getPassedTests().size());
caseCount.setTestFail(tc.getFailedTests().size());
caseCount.setTestSkip(tc.getSkippedTests().size());
caseCountJson.addProperty("count", gson.toJson(caseCount));
PostService.sendPost(countUrl, caseCountJson.toString());
}
/**
* 通知test運行狀態
*/
public void sendStatus(String status){
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("runId", runId);
jsonObject.addProperty("status", status);
JsonObject sendJson = new JsonObject();
sendJson.addProperty("status", jsonObject.toString());
PostService.sendPost(statusUrl, sendJson.toString());
}
//計算date間的時差(s)
public long getDurationByDate(Date start, Date end){
long duration = end.getTime() - start.getTime();
return duration / 1000;
}
//截圖
private void takeScreenShot(ITestResult tr) {
BaseTest b = (BaseTest) tr.getInstance();
b.takeScreenShot(tr);
}
}
** BaseTest **
package com.unionpay.base;
import org.testng.ITestResult;
import com.unionpay.listener.TestNGListenerWithPost;
@Listeners(TestNGListenerWithPost.class)
public abstract class BaseTest {
public AndroidDriver<WebElement> driver;
public BaseTest() {
driver = DriverFactory.getDriverByJson();
}
/**
* 截屏並保存到本地
* @param tr
*/
public void takeScreenShot(ITestResult tr) {
String fileName = tr.getName() + ".jpg";
File dir = new File("target/snapshot");
if (!dir.exists()) {
dir.mkdirs();
}
String filePath = dir.getAbsolutePath() + "/" + fileName;
if (driver != null) {
try {
File scrFile = driver.getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(scrFile, new File(filePath));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}