artdanil 的回答并没有完全解决我的问题,测试名称没有在电子邮件报告中更新。
@jersey-city-ninja 发布的答案确实更新了 Emailable 报告中的名称,但它为所有 Dataprovider 值重复了上次更新的测试名称,因为 Pilotg2 发布的内容对于使用 Dataprovider 的测试是正确的,即 getTestName 方法不断返回最后一组方法的名称和数据提供者的所有测试名称都相同。
所以这里的答案是@pilotg2 和@jersey-city-ninja 发布的答案的组合,以及克服重复方法名称的额外步骤。
请注意,这会更新 Emailable 报告、XML 报告、HTML 报告、Junit 报告中的测试名称。我没有看到它在更新 Eclipse - TestNg 执行视图 - 如果我发现了什么,将会更新
import org.testng.Assert;
import org.testng.ITest;
import org.testng.ITestContext;
import org.testng.ITestResult;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
public class NewDataProviderTest implements ITest {
//The Java ThreadLocal class enables you to create variables that can only be read and written by the same thread
private ThreadLocal<String> testName = new ThreadLocal<>();
/*
* TestNG, for some reason, when building different reports, calls getName() on the test while building the report.
* This is fine if you are not using a data provider to generate different runs and set a unique name for each run by using the ITest strategy.
* If you are using a data provider to generate multiple runs of the same test and want each run to have a unique name then there is a problem.
* As the ITest strategy returns the name for the test as the name set by the last run.
* */
private int emailNameIndex = 0;
private int htmlNameIndex = 0;
private int xmlNameIndex = 0;
private ArrayList<String> allTests = new ArrayList<String>();
/*
* TestHTMLReporter gets the name by first getting all the names for failed tests and then the names for passing tests
* Hence keeping them in 2 separate lists
* */
private ArrayList<String> passedTests = new ArrayList<String>();
private ArrayList<String> failedTests = new ArrayList<String>();
@BeforeClass(alwaysRun = true)
public void initialize() {
this.testName.set("");
}
@BeforeMethod(alwaysRun = true)
public void setCustomTestcaseName(Method method, Object[] testData) {
//Set the default name
this.testName.set(method.getName());
//Change the test name only if Dataprovider is used
//Check if data provider is used in the test
if (testData != null && testData.length > 0) {
System.out.println("\n\nParameters "+testData[0]+" are passed to the test - "+method.getName());
//Taking 1st field in the Dataprovider for updating testname - can be changed as desired maybe using a variable
//I'm changing the name only if the Dataprovider field is String
if (testData[0] instanceof String) {
//Taking 1st field in the Dataprovider for updating testname - can be changed as desired
System.out.println("I'm setting custom name to the test as "+method.getName() + "_" + testData[0]);
this.testName.set(method.getName() + "_" + testData[0]);
}
}
//Add the name to the collection that stores all list names
allTests.add(testName.get());
}
@AfterMethod (alwaysRun = true)
public void setTheTestcaseNameInResult(ITestResult result, Method method) {
//Fill the Passed and Failed tests collections
try {
if(result.getStatus() == ITestResult.SUCCESS) {
System.out.println("Adding "+ result.getTestName() + " to passed tests collection");
passedTests.add(result.getTestName());
}
if(result.getStatus() == ITestResult.FAILURE) {
System.out.println("Adding " + result.getTestName() + " to FAILURE tests collection");
failedTests.add(result.getTestName());
}
} catch (Exception e) {
e.printStackTrace();
}
// To change display name in HTML report
//Only changing the name if the parameter is instance of String
if(iTestResult.getParameters().length > 0) {
if (iTestResult.getParameters()[0] instanceof String) {
System.out.println("Updating the name as Parameters are passed to the test-"+method.getName());
try {
/* This helps in setting unique name to method for each test instance for a data provider*/
Field resultMethod = TestResult.class.getDeclaredField("m_method");
resultMethod.setAccessible(true);
resultMethod.set(iTestResult, iTestResult.getMethod().clone());
Field methodName = org.testng.internal.BaseTestMethod.class.getDeclaredField("m_methodName");
methodName.setAccessible(true);
methodName.set(iTestResult.getMethod(), this.getTestName());
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("New Name is - " + iTestResult.getMethod().getMethodName());
}
}
}
@Override
public String getTestName() {
String name = testName.get();
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();// .toString();
//This is called
if (isCalledFromMethod(stackTrace, "XMLSuiteResultWriter")) {
//System.out.println("Got called from XMLSuiteResultWriter");
if (allTestNames.size() > 0) {
if (xmlNameIndex < allTestNames.size()) {
name = allTestNames.get(xmlNameIndex);
} else {
name = allTestNames.get(0);
}
} else {
name = "undefined";
}
xmlNameIndex++;
if (xmlNameIndex >= allTestNames.size()) {
xmlNameIndex = 0;
}
// System.out.println("Got called from XMLSuiteResultWriter returning name - "+name);
} else if (isCalledFromMethod(stackTrace, "EmailableReporter")) {
if (allTestNames.size() > 0) {
if (emailNameIndex < allTestNames.size()) {
name = allTestNames.get(emailNameIndex);
} else {
name = allTestNames.get(0);
}
} else {
name = "undefined";
}
emailNameIndex++;
if (emailNameIndex >= allTestNames.size()) {
emailNameIndex = 0;
}
System.out.println("Got called from EmailableReporter returning name -"+name);
}
if (isCalledFromMethod(stackTrace, "TestHTMLReporter")) {
if (allTestNames.size() <= 0) {
name = "undefined";
} else {
if (htmlNameIndex < failedTestNames.size()) {
name = failedTestNames.get(htmlNameIndex);
} else {
int htmlPassedIndex = htmlNameIndex - failedTestNames.size();
if (htmlPassedIndex < passedTestNames.size()) {
name = passedTestNames.get(htmlPassedIndex);
} else {
name = "undefined";
}
}
}
htmlNameIndex++;
if (htmlNameIndex >= allTestNames.size()) {
htmlNameIndex = 0;
}
System.out.println("Got called from TestHTMLReporter returning name - "+name);
}
System.out.println("Returning testname as-"+name);
return name;
}
private boolean isCalledFromMethod(StackTraceElement[] stackTrace, String checkForMethod) {
boolean calledFrom = false;
for (StackTraceElement element : stackTrace) {
String stack = element.toString();
// System.out.println("Rohit the called from value is:"+stack);
if (stack.contains(checkForMethod))
calledFrom = true;
}
return calledFrom;
}
@Test(groups= {"L1", "L2", "L3"}, dataProvider = "dp1")
public void dataProviderTest(String username) {
System.out.println("\n\nI'm in dataProviderTest with data-"+username);
/* Fail the test if value is L2 - deliberately so that we have failed test in report */
if(username.contains("L2")) {
Assert.fail();
}
}
@Test(dependsOnMethods = "dataProviderTest", groups= {"L1", "L2", "L3"}, dataProvider = "dp1")
public void dataProviderDependentTest(String username) {
System.out.println("\n\nI'm in dataProvider DEPENDENT Test with data-"+username);
}
//This test consumes data of type list so the name will not be updated in report
@Test(groups= {"L1", "L2", "L3"}, dataProvider = "dp2")
public void dataListProviderTest(List<String[]> list) {
Object[] arr = list.get(0);
List<Object> arrList = Arrays.asList(arr);
Iterator<Object> iterator = arrList.iterator();
while (iterator.hasNext()) {
String[] data = (String[]) iterator.next();
System.out.println("In list test - "+data[0]);
}
}
@DataProvider(name="dp1")
public Object[][] getDataForTest(ITestContext iTestContext){
Object[][] L1 = new Object[][] {
{"L1"}, {"L2"}, {"L3"}
};
return L1;
}
@DataProvider(name="dp2")
public Object[][] getDataListForTest(ITestContext iTestContext){
List<Object[][]> list = new ArrayList<Object[][]>();
Object[][] L1 = new Object[][] {
new String [] {"L1", "l1"},
new String [] {"L1", "l1"}
};
list.add(L1);
return new Object[][] { { list } };
}
}