TestNG概述
TestNG is a testing framework inspired from JUnit and NUnit but introducing some new functionalities that make it more powerful and easier to use, such as:
Annotations.
- Run your tests in arbitrarily big thread pools with various policies available (all methods in their own thread, one thread per test class, etc…)
- Test that your code is multithread safe.
- Flexible test configuration.
- Support for data-driven testing (with @DataProvider).
- Support for ** parameters **.
- Powerful execution model (
no more TestSuite
). - Supported by a variety of tools and plug-ins (Eclipse, IDEA, Maven, etc…).
- Embeds BeanShell for further flexibility.
- Default JDK functions for runtime and logging (no dependencies).
- Dependent methods for application server testing.
TestNG is designed to cover all categories of tests: unit, functional, end-to-end, integration
, etc…
单元测试,功能测试,端到端测试,集成测试…
SampleTest
package example1;
import org.testng.annotations.*;
public class SimpleTest {
@BeforeClass
public void setUp() {
// code that will be invoked when this test is instantiated
}
@Test(groups = { "fast" })
public void aFastTest() {
System.out.println("Fast test");
}
@Test(groups = { "slow" })
public void aSlowTest() {
System.out.println("Slow test");
}
}
-
setUp()
会在所有test method
方法前执行,且执行一次。 -
如果只执行 group
fast
,aFastTest()
会被执行,但是aSlowTest()
会被跳过.
1 - Introduction
编写一个测试通常需要3个步骤:
- 在业务代码中 插入
TestNG annotations
- 在
testng.xml
文件或build.xml(ant)
文件中添加关于你的测试的信息(例如,类名,你希望运行的组
,等等)。 - 运行TestNG
本文档涉及的概念如下:
- suite: 套件。一个套件由一个XML文件表示。它由
<suit>
标记,它可以包含一个或多个测试; - test:测试。它由
<test>
标记,可以包含一个或多个TestNG类。 - TestNG类是至少包含一个TestNG注释的Java类。它由
<class>
标记,可以包含一个或多个测试方法。 - 测试方法是源代码中带有
@Test
注释的Java方法。
TestNG测试可以通过@BeforeXXX和@AfterXXX注释
配置,这些注释允许在某个点之前和之后执行一些Java逻辑。
2 - Annotations
@Before* & @After*
- @BeforeSuite: 该注解方法会在
before all tests in this suite have run.
仅执行一次 - ****@BeforeTest: ** 该注解方法会在
before any test method belonging to the classes inside the <test> tag is run
. - **@BeforeGroups: 该注解方法会在
before the first test method that belongs to any of these groups is invoked
- **@BeforeClass: ** 该注解方法会在
before the first test method in the current class is invoked.
- ****@BeforeMethod: ** 该注解方法会在 每一个Test方法前执行
@After*
不再赘述。环绕注解:
before1 -> before2 -> xx -> after2 -> after1
当将上面的注释放在TestNG类的父类
中时,也会被继承。
TestNG保证“@Before”
方法按照继承顺序执行(首先是最高的父类,然后沿着继承链向下),而“@After”
方法按照相反的顺序执行(沿着继承链向上)。
@DataProvider
将方法标记为为测试方法提供数据。带有该注释的方法必须返回一个Object[][]
,其中每个Object[][]
都可以被分配测试方法的参数列表
。@Test
方法通过dataProvider
属性指定所需的提供数据的方法。
@Factory
将方法标记为工厂,该工厂返回的对象
将被TestNG用作Test类
。该方法必须返回Object[]。
@Listeners
在测试类上定义侦听器。
@Parameters
描述如何将参数传递给@Test方法。
@Test
将类或方法标记为测试的一部分。
3 - testng.xml
你可以用几种不同的方式调用TestNG:
- 使用testng.xml文件
- 使用ant
- 从命令行
examples
testng.xml
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd" >
<suite name="Suite1" verbose="1" >
<test name="Nopackage" >
<classes>
<class name="NoPackageTest" />
</classes>
</test>
<test name="Regression1">
<classes>
<class name="test.sample.ParameterSample"/>
<class name="test.sample.ParameterTest"/>
</classes>
</test>
</suite>
扫描package
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd" >
<suite name="Suite1" verbose="1" >
<test name="Regression1" >
<packages>
<!--
package test.sample 下所有带有@Test的类都会执行。
*注意*: 若仅method上带有@Test注解,但是类上没有,则会被跳过。
-->
<package name="test.sample" />
</packages>
</test>
</suite>
included and excluded
<test name="Regression1">
<groups>
<run>
<!-- groups 支持included and excluded-->
<exclude name="brokenTests" />
<include name="checkinTests" />
</run>
</groups>
<classes>
<class name="test.IndividualMethodsTest">
<methods>
<!-- 支持included and excluded-->
<include name="testMethod" />
</methods>
</class>
</classes>
</test>
preserve-order
<!-- 默认情况下: test下的class和method会按照定义顺序执行,
若希望以一种不可预测的顺序执行: 加上 preserve-order=false
-->
<test name="Regression1" preserve-order="false">
<classes>
<class name="test.Test1">
<methods>
<include name="m1" />
<include name="m2" />
</methods>
</class>
<class name="test.Test2" />
</classes>
</test>
4 - Run TestNG
TestNG 可以以如下几种方式执行
- Command line
- ant
- Eclipse , IntelliJ’s IDEA
下面以Command line为例。
java org.testng.TestNG testng1.xml [testng2.xml testng3.xml ...]
它执行以下参数信息:
例:
java -Dtestng.test.classpath="c:/build;c:/java/classes;" org.testng.TestNG testng.xml
java org.testng.TestNG -groups windows,linux -testclass org.test.MyTest
5 - Test methods, Test classes and Test groups
5.1 Test methods
带@Test注解的方法返回值将会被忽略,除非你在testng.xml中将allow-return-values设置为true:
<suite allow-return-values="true">
or
<test allow-return-values="true">
5.2 Test Groups
可以在<test>
或<suite>
标签下定义<groups>
组。
<suite>
标记中指定的组应用于下面的所有<test>标记
。
注意,如果您在<suite>
中指定组“a”,在<test>
中指定组“b”,那么**“a”和“b”都将被包含**。
example1
例如,有至少两类测试是很常见的
Check-in tests
。在提交新代码之前,应该运行这些测试。他们通常应该是快速的,只是确保没有基本的功能被破坏。Functional tests
。这些测试应该覆盖软件的所有功能,并且至少每天运行一次,尽管理想情况下您希望连续运行它们。
JAVA代码
public class Test1 {
@Test(groups = { "functest", "checkintest" })
public void testMethod1() {
}
@Test(groups = {"functest", "checkintest"} )
public void testMethod2() {
}
@Test(groups = { "functest" })
public void testMethod3() {
}
}
testng.xml
<test name="Test1">
<groups>
<run>
<include name="functest"/>
</run>
</groups>
<classes>
<class name="example1.Test1"/>
</classes>
</test>
仅 testMethod1() , testMethod2()
被执行。
example2
java代码
@Test
public class Test1 {
@Test(groups = { "windows.checkintest" })
public void testWindowsOnly() {
}
@Test(groups = {"linux.checkintest"} )
public void testLinuxOnly() {
}
@Test(groups = { "windows.functest" )
public void testWindowsToo() {
}
}
testng.xml
<test name="Test1">
<groups>
<run>
<include name="windows.*"/>
</run>
</groups>
<classes>
<class name="example1.Test1"/>
</classes>
</test>
仅执行windows
相关用例。
Method Groups(不建议使用)
<test name="Test1">
<classes>
<class name="example1.Test1">
<methods>
<include name=".*enabledTestMethod.*"/>
<exclude name=".*brokenTestMethod.*"/>
</methods>
</class>
</classes>
</test>
5.3 Groups of Groups
组还可以包括其他组。这些组被称为“MetaGroups”。
<test name="Regression1">
<groups>
<define name="functest"> <!-- 定义metagroups -->
<include name="windows"/>
<include name="linux"/>
</define>
<define name="all">
<include name="functest"/>
<include name="checkintest"/> <!-- windows + linux -->
</define>
<run>
<include name="all"/>
</run>
</groups>
<classes>
<class name="test.sample.Test1"/>
</classes>
</test>
5.4 - Exclusion groups
java
@Test(groups = {"checkintest", "broken"} )
public void testMethod2() {
}
testng.xml
<test name="Simple example">
<groups>
<run>
<include name="checkintest"/>
<exclude name="broken"/>
</run>
</groups>
<classes>
<class name="example1.Test1"/>
</classes>
</test>
本例中,不会有测试用例执行。
您还可以使用
@Test和@Before/After注释上可用的“enabled”
属性禁用单个测试。
5.5 - Partial groups
类级别定义组,然后在方法级别添加组
:
@Test(groups = { "checkin-test" })
public class All {
@Test(groups = { "func-test" )
public void method1() { ... } // 属于 func-test + checkin-test组
public void method2() { ... } // 仅属于 checkin-test组
}
5.6 - Parameters
有两种方法设置这些参数:使用testng.xml
或以编程方式
。
parameters from testng.xml
java
@Parameters({ "first-name" })
@Test
public void testSingleString(String firstName) {
System.out.println("Invoked testString " + firstName);
assert "Cedric".equals(firstName);
}
testng.xml
<suite name="My suite">
<parameter name="first-name" value="Cedric"/>
<test name="Simple example">
<-- ... -->
也可以用于@Before/After和@Factory
注释:
@Parameters({ "datasource", "jdbcDriver" })
@BeforeMethod
public void beforeTest(String ds, String driver) {
m_dataSource = ...; // look up the value of datasource
m_jdbcDriver = driver;
}
参数可选
@Parameters("db")
@Test
public void testNonExistentParameter(@Optional("mysql") String db) { ... } //mysql作为默认值,
注意
- XML参数的顺序必须与java代码中顺序一致且个数相同,否则TestNG会报错。
- 在testng.xml中,可以在
<suite>
标记下声明它们,也可以在<test>
标记下声明它们。如果两个参数具有相同的名称,则<test>
中定义的参数具有优先级。
parameters with DataProviders
** 成员方法1 **
//This method will provide data to any test method that declares that its Data Provider
//is named "test1"
@DataProvider(name = "test1")
public Object[][] createData1() {
return new Object[][] {
{ "Cedric", new Integer(36) },
{ "Anne", new Integer(37)},
};
}
//This test method declares that its data should be supplied by the Data Provider
//named "test1"
@Test(dataProvider = "test1")
public void verifyData1(String n1, Integer n2) {
System.out.println(n1 + " " + n2);
}
** static 静态方法**
public class StaticProvider {
@DataProvider(name = "create")
public static Object[][] createData() {
return new Object[][] {
new Object[] { new Integer(42) }
};
}
}
public class MyTest {
@Test(dataProvider = "create", dataProviderClass = StaticProvider.class)
public void test(Integer n) {
// ...
}
}
多种返回类型: MyCustomData[][] or Iterator
返回类型不仅限于Object,因此也可以使用MyCustomData[][]或Iterator<Supplier>
。
@DataProvider(name = "test1")
public Iterator<Object[]> createData() {
return new MyIterator(DATA);
}
//----------//
@DataProvider(name = "test1")
public MyCustomData[] createData() {
return new MyCustomData[]{ new MyCustomData() };
}
//----------//
@DataProvider(name = "test1")
public Iterator<MyCustomData> createData() {
return Arrays.asList(new MyCustomData()).iterator();
}
//----------//
@DataProvider(name = "test1")
public Iterator<Stream> createData() {
return Arrays.asList(Stream.of("a", "b", "c")).iterator();
}
** 打印调用DataProvider的方法**
@DataProvider(name = "dp")
public Object[][] createData(Method m) { // 第一个参数为 java.reflect.Method
System.out.println(m.getName()); // print test method name
return new Object[][] { new Object[] { "Cedric" }};
}
@Test(dataProvider = "dp")
public void test1(String s) {
}
@Test(dataProvider = "dp")
public void test2(String s) {
}
5.7 - Dependencies
有时,您需要以特定的顺序调用测试方法。
Dependencies with annotations
以下是两种依赖关系:
- Hard Dependency。您所依赖的所有方法必须已经运行并成功运行。如果您的依赖项中发生了一个故障,则本次测试将被标记为
SKIP
。 - Soft Dependency。当您只想确保您的测试方法以特定的顺序运行,不关注依赖的执行结果。在@Test注释中添加
“alwaysRun=true”
来设置。
Hard Dependency Example
@Test
public void serverStartedOk() {}
@Test(dependsOnMethods = { "serverStartedOk" })
public void method1() {}
依赖 groups
@Test(groups = { "init" })
public void serverStartedOk() {}
@Test(groups = { "init" })
public void initEnvironment() {}
@Test(dependsOnGroups = { "init.*" })
public void method1() {}
Dependencies in XML
<test name="My suite">
<groups>
<dependencies>
<group name="c" depends-on="a b" />
<group name="z" depends-on="c" />
</dependencies>
</groups>
</test>
5.8 - Factories
工厂允许您动态地创建测试.
** 例如**,假设您想要创建一个测试方法,它将用不同的参数多次访问Web站点上的一个页面。
public class TestWebServer {
@Test(parameters = { "number-of-times" })
public void accessPage(int numberOfTimes) {
while (numberOfTimes-- > 0) {
// access the web page
}
}
}
<test name="T1">
<parameter name="number-of-times" value="10"/>
<classes>
<class name= "TestWebServer" />
</classes>
</test>
<test name="T2">
<parameter name="number-of-times" value="20"/>
<classes>
<class name= "TestWebServer"/>
</classes>
</test>
<test name="T3">
<parameter name="number-of-times" value="30"/>
<classes>
<class name= "TestWebServer"/>
</classes>
当后面出现T4,T5.... Tn
种测试需求时,整个xml文件将变得异常庞大,且无法管理。
使用factory
- 定义factory
public class WebTestFactory {
@Factory
public Object[] createInstances() {
Object[] result = new Object[10];
for (int i = 0; i < 10; i++) {
result[i] = new WebTest(i * 10); //生产 WebTest对象
}
return result;
}
}
- 重新定义test
public class WebTest {
private int m_numberOfTimes;
public WebTest(int numberOfTimes) {
m_numberOfTimes = numberOfTimes;
}
@Test
public void testServer() {
for (int i = 0; i < m_numberOfTimes; i++) {
// access the web page
}
}
}
-
引入factory
<class name="WebTestFactory" />
或者
TestNG testNG = new TestNG();
testNG.setTestClasses(WebTestFactory.class);
testNG.run();
Factories + DataProvider
@Factory(dataProvider = "dp") // 引用@DataProvider
public FactoryDataProviderSampleTest(int n) {
super(n);
}
@DataProvider
static public Object[][] dp() {
return new Object[][] {
new Object[] { 41 },
new Object[] { 42 },
};
}
5.9 - Class level annotations
@Test
public class Test1 {
public void test1() {
}
public void test2() {
}
}
//为test添加属性
@Test
public class Test1 {
public void test1() {
}
@Test(groups = "g1")
public void test2() {
}
}
5.10 - Ignoring tests
5.10 - Ignoring tests
//ignore class
@Ignore
public class TestcaseSample {
@Test
public void testMethod1() {
}
@Test
public void testMethod2() {
}
}
//ignore package
@Ignore
package com.testng.master;
import org.testng.annotations.Ignore;
5.11 - Parallelism and time-outs
Parallel suites
# 不同的suite在不同的线程中执行 ; 指定的线程池大小为3
java org.testng.TestNG -suitethreadpoolsize 3 testng1.xml testng2.xml testng3.xml
parallel
<!--
parallel值选项为:
- methods 所有的测试用例都在独立的线程中执行
- tests <test>标签下的所有用例,在一个线程中执行; 各<test>标签在独立线程执行
- classes class内的method,在同一个线程中执行; 不同的class独立线程执行
- instances 同一个java对象实例中的所有方法,在同一个线程执行; 不同实例&相同方法 在独立线程执行。
-->
<suite name="My suite" parallel="methods" thread-count="5">
5.12 - Rerunning failed tests
result
每次suite中的测试失败时,TestNG都会在输出目录中创建一个名为TestNG -failed.xml
的文件。
这个XML文件包含只重新运行这些失败的方法所需的信息,允许您快速地重现失败,而不必运行整个测试。
java -classpath testng.jar;%CLASSPATH% org.testng.TestNG -d test-outputs testng.xml
java -classpath testng.jar;%CLASSPATH% org.testng.TestNG -d test-outputs test-outputs\testng-failed.xml
retry
//1. 定义 IRetryAnalyzer 实现类
import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;
public class MyRetry implements IRetryAnalyzer {
private int retryCount = 0;
private static final int maxRetryCount = 3;
@Override
public boolean retry(ITestResult result) {
if (retryCount < maxRetryCount) {
retryCount++;
return true;
}
return false;
}
}
//2. 引入retry
@Test(retryAnalyzer = MyRetry.class)
public void test2() {
Assert.fail();
}
5.13 - JUnit tests(略)
5.14 - Running TestNG programmatically
编程方式
TestListenerAdapter tla = new TestListenerAdapter();
TestNG testng = new TestNG();
testng.setTestClasses(new Class[] { Run2.class });
testng.addListener(tla); // 或者 实现ITestListener
testng.run();
virtual testng.xml
//1. 构建 XmlSuite
XmlSuite suite = new XmlSuite();
suite.setName("TmpSuite");
XmlTest test = new XmlTest(suite);
test.setName("TmpTest");
List<XmlClass> classes = new ArrayList<XmlClass>();
classes.add(new XmlClass("test.failures.Child"));
test.setXmlClasses(classes) ;
//2. 执行
List<XmlSuite> suites = new ArrayList<XmlSuite>();
suites.add(suite);
TestNG tng = new TestNG();
tng.setXmlSuites(suites);
tng.run();
执行效果同
<suite name="TmpSuite" >
<test name="TmpTest" >
<classes>
<class name="test.failures.Child" />
<classes>
</test>
</suite>
5.15 - BeanShell and advanced group selection(略)
例:若 include和exclude无法满足需求
<test name="BeanShell test">
<method-selectors>
<method-selector>
<script language="beanshell"><![CDATA[
groups.containsKey("test1")
]]></script>
</method-selector>
</method-selectors>
<!-- ... -->
5.16 - Annotation Transformers
TestNG允许您在运行时修改所有注释的内容。
IAnnotationTransformer接口
public interface IAnnotationTransformer {
public void transform(ITest annotation, Class testClass,
Constructor testConstructor, Method testMethod);
}
自定义MyTransformer
public class MyTransformer implements IAnnotationTransformer {
public void transform(ITest annotation, Class testClass,
Constructor testConstructor, Method testMethod)
{
if ("invoke".equals(testMethod.getName())) {
annotation.setInvocationCount(5);
}
}
}
执行命令
java org.testng.TestNG -listener MyTransformer testng.xml
或者
TestNG tng = new TestNG();
tng.setAnnotationTransformer(new MyTransformer());
// ...
5.17 - Method Interceptors
可以通过Interceptors来个性化指定各个method的执行顺序。
public List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context) {
List<IMethodInstance> result = new ArrayList<IMethodInstance>();
for (IMethodInstance m : methods) {
Test test = m.getMethod().getConstructorOrMethod().getAnnotation(Test.class);
Set<String> groups = new HashSet<String>();
for (String group : test.groups()) {
groups.add(group);
}
if (groups.contains("fast")) {
result.add(0, m); // fast group优先执行
}
else {
result.add(m);
}
}
return result;
}
执行命令:
java -classpath "testng-jdk15.jar:test/build" org.testng.TestNG -listener test.methodinterceptors.NullMethodInterceptor
-testclass test.methodinterceptors.FooTest
5.18 - TestNG Listeners
- IAnnotationTransformer
- IAnnotationTransformer2
- IHookable
- IInvokedMethodListener
- IMethodInterceptor
- IReporter
- ISuiteListener
- ITestListener
Specifying listeners with testng.xml or in Java
<suite>
<listeners>
<listener class-name="com.example.MyListener" />
<listener class-name="com.example.MyMethodInterceptor" />
</listeners>
或者
@Listeners({ com.example.MyListener.class, com.example.MyMethodInterceptor.class })
public class MyTest {
}
使用SPI方式注入
# META-INF/services/org.testng.ITestNGListener
test.tmp.TmpSuiteListener
5.19 - Dependency injection(略)
TestNG支持两种不同类型的依赖项注入:本地(由TestNG本身执行)和外部(由Guice等依赖项注入框架执行)。
5.20 - Listening to method invocations
IInvokedMethodListener
public interface IInvokedMethodListener extends ITestNGListener {
void beforeInvocation(IInvokedMethod method, ITestResult testResult);
void afterInvocation(IInvokedMethod method, ITestResult testResult);
}
5.21 - Overriding test methods
TestNG允许覆盖甚至可能跳过测试方法的调用。
如果您需要使用特定的安全管理器来测试方法。你可以通过提供一个实现IHookable监听器来实现这一点。
public class MyHook implements IHookable {
public void run(final IHookCallBack icb, ITestResult testResult) {
// Preferably initialized in a @Configuration method
mySubject = authenticateWithJAAs(); //模拟登录
Subject.doAs(mySubject, new PrivilegedExceptionAction() {
public Object run() {
icb.callback(testResult);
}
};
}
}
5.22 - Altering suites (or) tests
有时您可能只需要在运行时
更改套件xml中的测试标记,而不需要更改套件文件的内容。
public class AlterSuiteNameListener implements IAlterSuiteListener {
@Override
public void alter(List<XmlSuite> suites) {
XmlSuite suite = suites.get(0);
suite.setName(getClass().getSimpleName());
}
}
6 - Test results
6.1 - Success, failure and assert
如果测试完成时没有抛出任何异常,或者抛出了预期的异常
,则认为测试成功。
import static org.testng.AssertJUnit.*;
//...
@Test
public void verify() {
assertEquals("Beust", m_lastName);
}
6.2 - Logging and results
使用带有Listeners
和Reporters
的TestNG可以很容易地生成自己的报表:
LoggingListener
public class DotTestListener extends TestListenerAdapter {
private int m_count = 0;
@Override
public void onTestFailure(ITestResult tr) {
log("F");
}
@Override
public void onTestSkipped(ITestResult tr) {
log("S");
}
@Override
public void onTestSuccess(ITestResult tr) {
log(".");
}
private void log(String string) {
System.out.print(string);
if (++m_count % 40 == 0) {
System.out.println("");
}
}
}
执行命令:java -classpath testng.jar;%CLASSPATH% org.testng.TestNG -listener org.testng.reporters.DotTestListener test\testng.xml
IReporter
public interface IReporter {
public void generateReport(List<ISuite> suites, String outputDirectory)
}
6.2.3 - JUnitReports(略)
6.2.4 - Reporter API(略)
6.2.5 - XML Reports(略)
7 - YAML
XML
<suite name="SingleSuite" verbose="2" thread-count="4">
<parameter name="n" value="42" />
<test name="Regression2">
<groups>
<run>
<exclude name="broken" />
</run>
</groups>
<classes>
<class name="test.listeners.ResultEndMillisTest" />
</classes>
</test>
</suite>
YAML
name: SingleSuite
threadCount: 4
parameters: { n: 42 }
tests:
- name: Regression2
parameters: { count: 10 }
excludedGroups: [ broken ]
classes:
- test.listeners.ResultEndMillisTest
默认情况下,TestNG不会将与YAML相关的库引入到类路径中。因此,你需要在你的构建文件中添加一个对YAML库的显式引用。
<dependency> <groupid>org.yaml</groupid> <artifactid>snakeyaml</artifactid> <version>1.23</version> </dependency>