TestNG:如何在IAnnotationTransformer类中获取testng.xml参数
因为IAnnotationTransformer的transform方法在TestNG解析测试类的极早期就会被调用,这时候Suite上下文还没初始化,所以@BeforeSuite或者ITestContext都拿不到参数。这里提供几个靠谱的替代方案:
方案1:通过反射从TestNG实例中获取XmlSuite参数
TestNG内部会用ThreadLocal存储当前运行的TestNG实例,我们可以通过反射拿到这个实例,进而获取XmlSuite里的参数。这种方式不需要额外的监听器,直接在Transformer里实现:
import org.testng.IAnnotationTransformer; import org.testng.ITestAnnotation; import org.testng.TestNG; import org.testng.xml.XmlSuite; import java.lang.reflect.Field; import java.util.List; public class TestDisablerTransformer implements IAnnotationTransformer { @Override public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) { TestNG currentTestNG = getCurrentTestNGInstance(); if (currentTestNG == null || testMethod == null) { return; } // 遍历所有XmlSuite,获取参数(一般只有一个Suite) List<XmlSuite> xmlSuites = currentTestNG.getXmlSuites(); for (XmlSuite suite : xmlSuites) { String disableFlag = suite.getParameter("disableTests"); if ("true".equals(disableFlag)) { annotation.setEnabled(false); break; } } } private TestNG getCurrentTestNGInstance() { try { // 反射获取TestNG内部的ThreadLocal实例 Field threadLocalField = TestNG.class.getDeclaredField("m_testNGThreadLocal"); threadLocalField.setAccessible(true); ThreadLocal<TestNG> testNGThreadLocal = (ThreadLocal<TestNG>) threadLocalField.get(null); return testNGThreadLocal.get(); } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); return null; } } }
注意:这种方式依赖TestNG的内部实现,不同版本的TestNG可能会修改m_testNGThreadLocal这个字段名,使用前要确认你的TestNG版本对应(TestNG 7.x版本是这个字段名)。
方案2:自定义TestNG启动器,提前解析testng.xml参数
如果不想用反射,最稳定的方式是自己写TestNG启动类,提前解析testng.xml文件,把参数存入系统属性或者静态变量,然后在Transformer里读取:
第一步:自定义启动类
import org.testng.TestNG; import org.testng.xml.XmlSuite; import java.io.File; import java.util.Arrays; import java.util.List; import java.util.Map; public class CustomTestRunner { public static void main(String[] args) { // 从命令行参数中提取testng.xml路径(格式:-testngxml=path/to/testng.xml) String xmlPath = extractTestNgXmlPath(args); if (xmlPath == null) { System.err.println("未找到testng.xml路径参数"); return; } // 解析testng.xml,获取Suite参数 try { List<XmlSuite> suites = XmlSuite.loadSuiteFromFile(new File(xmlPath)); if (!suites.isEmpty()) { Map<String, String> suiteParams = suites.get(0).getParameters(); // 将参数存入系统属性,前缀用suite.避免冲突 for (Map.Entry<String, String> entry : suiteParams.entrySet()) { System.setProperty("suite." + entry.getKey(), entry.getValue()); } } } catch (Exception e) { e.printStackTrace(); return; } // 启动TestNG TestNG testng = new TestNG(); testng.setTestSuites(Arrays.asList(xmlPath)); testng.addListener(new TestDisablerTransformer()); testng.run(); } private static String extractTestNgXmlPath(String[] args) { for (String arg : args) { if (arg.startsWith("-testngxml=")) { return arg.substring("-testngxml=".length()); } } return null; } }
第二步:在Transformer中读取参数
import org.testng.IAnnotationTransformer; import org.testng.ITestAnnotation; import java.lang.reflect.Constructor; import java.lang.reflect.Method; public class TestDisablerTransformer implements IAnnotationTransformer { @Override public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) { if (testMethod == null) { return; } // 读取提前存入的系统属性 String disableFlag = System.getProperty("suite.disableTests"); if ("true".equals(disableFlag)) { annotation.setEnabled(false); } } }
这种方式完全不依赖TestNG内部实现,兼容性最好,只是需要你通过自定义启动类来运行测试,而不是直接用TestNG的默认启动器。
方案3:静态变量+ISuiteListener(注意局限性)
你可能会想到用ISuiteListener的onStart方法缓存参数,但要注意:ISuiteListener的onStart是在IAnnotationTransformer的transform之后执行的,所以直接用的话,Transformer里拿不到缓存的参数。除非你修改TestNG的监听器执行顺序,但这几乎做不到。所以这个方案其实不适用,这里提出来是避免你踩坑。
内容的提问来源于stack exchange,提问作者Bernhard Spitzer

