TestNG - A Flexible Java Test Framework
By Rad Widmer, OCI Senior Software Engineer
July 2010
Introduction
TestNG is a Java testing framework that is particularly well-suited for use on large projects. It provides especially strong support for writing functional and integration tests as well as unit tests. TestNG key features include:
- test groups: test methods can belong to any number of groups
- test dependencies: tests can be skipped based on the results of other tests
- parameterized test methods and data providers: allows data to be supplied to a test or configuration method from another method, or an XML file
- dependency injection: it's easy to hook up listeners for customizing behavior
- multi-threading support: tests can run in multiple threads in order to speed up execution
TestNG uses Java annotations extensively. This means no reliance on class inheritance or method naming conventions as in JUnit 3. While JUnit 4 also uses annotations and shares some features with TestNG, there are significant differences in philosophy and implementation. JUnit strives to keep tests isolated from one another, while TestNG is more flexible, allowing various types of dependencies between tests. The key difference is the context in which the tests run.
TestNG | JUnit |
---|---|
All test methods in the same class execute in the same instance of the class | Each test method executes in its own class instance |
Easy to share resources among tests | Sharing resources among tests limited and not encouraged |
The Basics
TestNG can be invoked in a number of ways - from the command line, from within an IDE such as Eclipse, IntelliJ IDEA, or NetBeans, from the testng Ant task, or from Maven. In all cases, a suite XML file (also referred to as a testng.xml
file) is used to define the test suites. Although it is not required, a suite XML file provides the most power and flexibility in configuring and selecting which tests to run. Here's a simple command line example, which runs the suite defined by testng.xml
(assuming the testng jar file and any classes used during testing are on the classpath):
java org.testng.TestNG testng.xml
There are several useful optional arguments, including
-groups
: a comma-separated list of groups to run-excludegroups
: a comma-separated list of groups to exclude-listener
: a comma-separated list of classes which implement ITestListener-reporter
: a comma-separated list of classes which implement ITestReporter
The Suite XML (testng.xml) file
A testng.xml
file defines a single test suite. A suite contains one or more test
elements, each of which specifies a set of test classes. The tests which make up the test
element can be specified in one or more of the following ways:
- a list of classes which contain the test methods
- a list of packages to search for test classes
- a list of groups to include or exclude from the
test
element
Here is an example suite which defines a test named CoreTests
that includes classes ClassATest
and ClassBTest
, plus all test classes in the package com.ociweb.jnb.testng.core
and any subpackages (because the package name ends with ".*"). In addition, the group long-tests is excluded from the test.
- <suite name="Suite" parallel="false">
- <test name="CoreTests">
- <classes>
- <class name="com.ociweb.jnb.testng.ClassATest"></class>
- <class name="com.ociweb.jnb.testng.ClassBTest"></class>
- </classes>
- <packages>
- <package name="com.ociweb.jnb.testng.core.*"></package>
- </packages>
- <groups>
- <run>
- <exclude name="long-tests"></exclude>
- </run>
- </groups>
- </test>
- </suite>
For more details on the suite XML file, an HTML version of the DTD schema is available at http://testng.org/dtd/.
Annotations
Any class can be turned into a TestNG test class by adding at least one TestNG annotation. The @Test
annotation is used to designate the test methods in a class. It can be applied to individual methods, as in the following example:
- public class ExampleMethodAnnotations {
- @Test
- public void checkSomething() {}
-
- @Test
- public void checkSomethingElse() {}
-
- public void notATestMethod() {}
- }
The @Test
annotation can also be applied to a class, in which case all public methods of that class will be test methods, unless they are annotated by one of the @BeforeXXX
or @AfterXXX
configuration annotations described below.
- @Test
- public class ExampleClassAnnotation {
- public void checkSomething() {}
-
- public void checkSomethingElse() {}
- }
Configuration Annotations
Configuration annotations can be applied to methods to perform some action either before or after specific events:
@BeforeSuite/@AfterSuite
: Executes before/after any tests in the test suite@BeforeTest/@AfterTest
: Executes before/after any tests in theelement which contains this class
@BeforeClass/@AfterClass
: Executes before/after any tests in this class@BeforeMethod/@AfterMethod
: Executes before/after every test method in this class@BeforeGroups/@AfterGroups
: Executes before/after any tests in the specified groups.
For example, the methods in the class TestBeforeAfter
are executed in the following order:
beforeSuite
beforeTest
beforeClass
beforeMethod
test1
afterMethod
beforeMethod
test2
afterMethod
afterClass
afterTest
afterSuite
- public class TestBeforeAfter {
- @BeforeSuite
- public void beforeSuite() {}
-
- @AfterSuite
- public void afterSuite() {}
-
- @BeforeTest
- public void beforeTest() {}
-
- @AfterTest
- public void afterTest() {}
-
- @BeforeClass
- public void beforeClass() {}
-
- @AfterClass
- public void afterClass() {}
-
- @BeforeMethod
- public void beforeMethod() {}
-
- @AfterMethod
- public void afterMethod() {}
-
- @Test
- public void test1() {}
-
- @Test
- public void test2() {}
- }
Groups
It's natural to want to place tests into groups. For example, you may have a core group of tests which should be run before any files are checked in to the source code repository. You could also have groups for different types of tests, such as integration tests and performance tests. Tests which cover different features could also be in different groups. A "broken tests" group can be useful for tests which are known to be broken, and won't be fixed for a while.
In TestNG, groups are used to control execution of tests, both by including or excluding tests from a suite, and by controlling the order of tests and whether tests are skipped. A test method can be a member of any number of groups. Test methods can be assigned to groups at the class level (all test methods in the class are members of the class-level groups), on a per-method basis, or a combination of both.
An example of assigning groups at the method level:
- public class MethodLevelGrouping {
- @Test(groups={"a"})
- public void a() {}
-
- @Test(groups={"b"})
- public void b() {}
-
- @Test(groups={"a", "b"})
- public void ab() {}
- }
To assign groups at the class level, add a @Test
annotation to the class. This turns all public methods in the class into test methods (unless they have a @BeforeXXX
or @AfterXXX
annotation). These methods also acquire any annotation elements (such as groups
) defined by the class-level @Test
annotation. In the following example, testB1
and testB2
are test methods belonging to group b, and method testBC
belongs to groups b and c.
- @Test(groups={"b"})
- public class ClassLevelGrouping {
- public void testB1() {}
-
- public void testB2() {}
-
- @Test(groups={"c"})
- public void testBC() {}
- }
Test Dependencies
Tests can depend on other methods or groups. This effects test execution order and can cause tests to be skipped depending on the results of other tests. The execution order rules are as follows:
- A test which depends on other methods executes after the methods it depends on.
- A test which depends on groups executes after all methods in the groups it depends on.
There are two types of dependencies, hard (the default) and soft. This determines whether tests are skipped or not.
- A hard dependency (
@Test(alwaysRun=false)
) means that the dependent test will be skipped if any tests it depends on fails. - A soft dependency (
@Test(alwaysRun=true)
) means that a test will run regardless of the outcome of the tests it depends on. However, it will always execute after all the tests it depends on.
In this example, test depTest
depends on methods pre1
and pre2
. depTest
always runs after pre1
and pre2
, and will be skipped if either pre1
or pre2
fail. Note that pre1
and pre2
can be executed in any order.
- public class DependsOnMethods {
- @Test(dependsOnMethods={"pre1", "pre2"})
- public void depTest() {}
-
- @Test
- public void pre1() {}
-
- @Test
- public void pre2() {}
- }
Here's an example where a number of tests depend on an external server and some environment configuration. We want to avoid executing the server-dependent tests if the server is down. One way to do this is do create test methods which verify that the server is working and the environment is configured correctly. For convenience, these methods are placed in an init
group, and the server-dependent tests depend on this group. Then, if the server is down, the server-dependent tests will be skipped. This has the benefit of potentially saving a lot of time (no waiting for the server to time-out for each test), plus the error report is more accurate, allowing one to focus more quickly on the actual failures. In this example, the init
group methods are placed in their own class. Alternatively, all the methods could be placed into one class, though this would require annotations on each method.
- @Test(groups={"init"})
- public class InitTests {
- public void checkEnvironment() {}
-
- public void checkServer() {}
- }
-
- @Test(dependsOnGroups={"init"})
- public class ServerDependentTests {
- public void sdtest1() {}
-
- public void sdtest2() {}
- }
Adding Flexibility with Method Parameters
Test and configuration methods can have parameters. Parameter values can be assigned in two ways - by using the @Parameters
annotation, or by using DataProviders
.
@Parameters
Here's an example of using the @Parameters
annotation to assign a server name and port number.
- @Parameters({"server-name", "port"})
- @BeforeTest
- public void setupServer(String serverName, int port) {
- this.serverName = serverName;
- this.port = port;
- }
The parameter values are assigned in the suite XML file (note that the name attributes must match the names listed in the @Parameters
annotation):
- <test name="ParametersTest">
- <parameter name="server-name" value="test-server "/>
- <parameter name="port" value="1234"/>
- ...
- </test>
Parameter elements can be placed under the or
elements. Suite parameters apply to all tests unless they are overridden by a parameter of the same name under a
element.
While the @Parameters
approach is easy to use, it has some limitations. Parameter types are limited to simple types such as String
, int
, and double
. Also, the parameter values are only assigned once, so it is not possible to invoke the same method with multiple sets of parameter values. DataProviders
overcome these limitations.
DataProviders
A DataProvider is a method annotated with @DataProvider
which returns either an Object[][]
or Iterator
. In either case, the inner Object[]
contains the parameters for a single invocation of a test method. The number and types of elements in each row must match the number and types of parameters in the test method. Here's a simple example:
- @DataProvider(name = "indexOfProvider")
- public Object[][] indexOfTestGenerator() {
- return new Object[][] {
- { -1, "something", "x" },
- { 4, "something", "thing" }, };
- }
-
- @Test(dataProvider="indexOfProvider")
- public void testStringIndexOf(int expect, String s1, String s2) {
- assertEquals(expect, s1.indexOf(s2));
- }
In this example, the test method will be invoked twice, first with parameters {-1, "something", "x"}
, then with parameters {4, "something", "thing"}
.
DataProviders can be particularly useful when implementing data-driven tests - tests where it is necessary to perform the same checks with a variety of input data sets. Typical ways of handling these types of tests include:
- Iterate over the input data sets within a single test method
- Create a separate test method for each input data set
DataProviders can provide a more elegant solution by helping keep your test methods small, and by separating the test data generation from the actual tests.
Parallel Execution
TestNG supports running tests in multiple threads. Use this feature to speed up execution of thread-safe tests. By default, all tests run in a single thread. To use multiple threads, use the parallel
and thread-count
attributes of the and
tags. The
parallel
attribute can be set to the following values:
"tests"
: All test methods in a giventag are executed in the same thread, but different
tags may execute in different threads. This value is only valid for
tags.
"classes"
: All test methods in the same class execute in a single thread, but methods in different classes may execute in different threads."methods"
: All test methods may potentially execute in different threads."false"
: Don't use multiple threads.
The thread-count
attribute is used to set the number of threads in the thread pool. If parallel
or thread-count
attributes are specified on a tag, their values take precedence over the
values.
Here's one way to define a suite where parallel
is "methods" by default, but is overridden by a test which contains methods which should not be run in parallel:
<suite name="Suite" parallel="methods" thread-count="10">
...
<test name= "not-parallel" parallel="false">
Reports
By default, TestNG produces an HTML report and a TestNG specific XML file. In addition, XML files are produced which are compatible with the Ant JUnitReport task. This is handy for integrating into existing build frameworks, but lacks any TestNG-specific data. It is also possible to produce custom reports by implementing an IReporter
reporter class. This class is called when all the test suites have completed, and it is passed the test results for all tests in the suites.
Summary
TestNG provides good support for managing large test suites, and provides solutions to common problems which arise particularly when writing functional and integration tests. This makes it a natural fit for many projects, where it can make it easier to write tests, more efficient to run the tests, and easier to interpret the results.
References
- [1] The TestNG web site: This is a good place to start.
http://testng.org. - [2] The testng-users discussion group:
http://groups.google.com/group/testng-users. - [3] Next-Generation Testing with TestNG, An Interview with Cédric Beust by Frank Sommers: A good interview with the creator of TestNG.
http://www.artima.com/lejava/articles/testng.html. - [4] Next Generation Java Testing by Cédric Beust and Hani Suleiman:The definitive TestNG book, coauthored by the creator.
http://testng.org/doc/book.html.