TestNG

TestNG Basic Annotations Tutorial

In this post, we will take a look at TestNG annotations and how we can use them in unit tests for maximum benefit.

1. TestNG Annotations – Introduction

TestNG is a testing framework for the Java programming language created by Cedric Beust and inspired by JUnit and NUnit. The design goal of TestNG is to cover a wider range of test categories: unit, functional, end-to-end, integration, etc., with more powerful and easy-to-use functionalities. The main features of TestNG include

  • Annotations.
  • Run your tests in arbitrarily big thread pools with various policies available
  • Test that your code is multithread safe.
  • Flexible test configuration.
  • Support for data-driven testing (with @DataProvider).
  • Support for parameters.
  • Powerful execution model.
  • Supported by a variety of tools and plug-ins (Eclipse, IDEA, Maven, etc…).
  • Embeds Bean Shell for further flexibility.
  • Default JDK functions for runtime and logging (no dependencies).
  • Dependent methods for application server testing.

In this post, we will deep dive into annotations and discover how it helps for testing workflows. Before that, we will look at the steps involved in creating a TestNG project. We will use Gradle as the build tool of choice. The section below discusses the build file

pom.xml

plugins {
    id 'java'
}

group 'org.example'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    testCompile group: 'org.testng', name: 'testng', version: '7.3.0'
}

test {
    useTestNG() {
        useDefaultListeners = true
    }
    testLogging {
        events "PASSED", "FAILED", "SKIPPED"
    }
}
  • We have specified the repository for our dependencies as Maven central.
  • In the dependencies section, we specify TestNG as the dependency.
  • In the test task, we specify useTestNG to indicate the test task must use TestNG to run the tests.
  • We also specify logging status for each test rather than the overall status.

2. Test related annotations

We will cover three annotations in this section – @Test, @BeforeClass and @AfterClass. To illustrate the idea we will look at testing a simple calculator class.

Calculator.java

public class Calculator {

    public int add(int a, int b){
        return a+b;
    }

    public int subtract(int a, int b){
        return a-b;
    }
}
  • A simple class containing two operations – add and subtract

To test this class we will define a test class which asserts the working of these functions

CalculatorTest.java

import org.testng.Assert;
import org.testng.annotations.Test;

public class CalculatorTest {


    @Test
    public void addTest() {
        Calculator calculator = new Calculator();
        Assert.assertEquals(calculator.add(2,3),5);
    }

    @Test
    public void subtractTest() {
        Calculator calculator = new Calculator();
        Assert.assertEquals(calculator.subtract(4,3),1);
    }
}
  • We have specified Test annotation to indicate this is a test method
  • We are using Assert to verify the expected result and actual result.

The @Test annotation can be applied to the class level as well. When applied all public methods inside the class are executed as test cases.

In the above example, we notice that we are initializing the Calculator class in every test. A better way to do that would be to use @BeforeClass annotation.

CalculatorTest.java

import org.testng.Assert;
import org.testng.annotations.Test;

public class CalculatorTest {
Calculator calculator;

    @BeforeClass
    public void setUp() {
        calculator = new Calculator();
    }

...
  • We have initialized the Calculator class in setUp method which runs once before any of the test methods in the current class starts running.
  • This ensures that we need not initialize the class during each test.

Complementary of @BeforeClass is @AfterClass. This is generally used for closing the resources(IO) which are used in tests. For the above example, a scenario could be releasing the instance of Calculator class. This might not be necessary for our case with JVM doing the work but it is illustrated below to give the flavor.

CalculatorTest.java

import org.testng.Assert;
import org.testng.annotations.Test;

public class CalculatorTest {
Calculator calculator;

     @BeforeClass
    public void setUp() {
        System.out.println("initialize calculator");
        calculator = new Calculator();
    }

    @AfterClass
    public void tearDown() {
        System.out.println("teardown calculator");
        calculator = null;
    }

...

Running this produces the following output

initialize calculator
Gradle suite > Gradle test > com.jcg.testng.CalculatorTest > addTest PASSED
Gradle suite > Gradle test > com.jcg.testng.CalculatorTest > subtractTest PASSED
teardown calculator

In the above class, the class is initialized only once before any of the test is run. There might be cases where we want code to run for each test method. For this purpose there are annotations @BeforeMethod and @AfterMethod.

CalculatorTest.java

import org.testng.Assert;
import org.testng.annotations.Test;

public class CalculatorTest {
Calculator calculator;

     @BeforeMethod
    public void setUp() {
        System.out.println("initialize calculator");
        calculator = new Calculator();
    }

    @AfterMethod
    public void tearDown() {
        System.out.println("teardown calculator");
        calculator = null;
    }

...

The output below indicates the execution of the methods before each test method is being called.

initialize calculator
Gradle suite > Gradle test > com.jcg.testng.CalculatorTest > addTest PASSED
teardown calculator
initialize calculator
Gradle suite > Gradle test > com.jcg.testng.CalculatorTest > subtractTest PASSED
teardown calculator

3. Test Group related annotations

In this section, we will explore annotations that will act when using a group of tests. We will start with @BeforeSuite and @AfterSuite annotations. A suite is represented by one XML file. It can contain one or more tests and is defined by the <suite> tag.

testng.xml

<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd" >

<suite name="Suite1" verbose="1">
    <test name="add">
        <classes>
            <class name="com.jcg.testng.AddTest"/>
        </classes>
    </test>

    <test name="subtract">
        <classes>
            <class name="com.jcg.testng.SubtractTest"/>
        </classes>
    </test>
</suite>

This is an XML suite file which contains a suite of two tests containing AddTest and SubtractTest which are just broken down from the CalculatorTest class.

To execute something before the entire suite is executed i.e. initializing a heavy resource which would take time to initialize before each test class or method @BeforeSuite can be used.

AddTest.java

...
@BeforeSuite
    public void setUpSuite() {
        System.out.println("initialize before suite");
    }
...
@AfterSuite
    public void tearDown() {
        System.out.println("after suite");
    }
  • BeforeSuite can be present in any of the test classes
  • It will be executed once before the entire suite is started
  • It will be useful to initialize global variables which are needed by all executing tests(ObjectMother pattern)
initialize before suite
initialize calculator
initialize calculator
after suite
  • It first executes the BeforeSuite method
  • It executes the two BeforeClass methods located in each test class
  • Finally, it executes the AfterSuite method

We will explore one more grouping other than the suite. It is the basic grouping of a test. A test need not be condensed into a single test method or class. Test here refers to a group of test cases logically grouped to verify a particular behavior while a suite consists of many tests. The hierarchy in TestNG is Suite > Test > Test Class > Test Method.

To illustrate this scenario lets add another test to our application for the functionality multiply.

Calculator.java

 public int multiply(int a, int b) {
        return a * b;
    }

This is a simple function created to multiply two numbers as part of the calculator functionality. To assert this, we are creating another Test class.

MultiplyTest.java

...
public class MultiplyTest {

    Calculator calculator;

    @BeforeClass
    public void setUp() {
        System.out.println("initialize calculator");
        calculator = new Calculator();
    }

    @BeforeTest
    public void beforeTest() {
        System.out.println("Before Test");
    }

    @Test
    public void multiplyTest() {
        Assert.assertEquals(calculator.multiply(4, 3), 12);
    }

    @AfterTest
    public void afterTest() {
        System.out.println("After Test");
    }

}
  • This class is very similar to above tests.
  • We have added BeforeTest and AfterTest annotations to ensure these get executed before any test method runs in the test group.
  • To create a test group, refer the below testng.xml

testng.xml

<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd" >

<suite name="Suite1" verbose="1">
    <test name="addmul">
        <classes>
            <class name="com.jcg.testng.AddTest"/>
            <class name="com.jcg.testng.MultiplyTest"/>
        </classes>
    </test>

    <test name="subtract">
        <classes>
            <class name="com.jcg.testng.SubtractTest"/>
        </classes>
    </test>
</suite>

Here, We have added the MultiplyTest as part of the test containing AddTest. Now running the testng.xml produces the following result

initialize before suite
Before Test
initialize calculator
initialize calculator
After Test
initialize calculator
after suite
  • We can see the BeforeSuite runs first followed by before test
  • Then BeforeClass runs once for each test class
  • This is followed by the AfterTest
  • The before class runs as part of the SubtractTest class
  • Finally, the AfterSuite the method runs printing the message to console

The last aspect we are going to look at is annotations catering to a group of tests. Let’s look at changing CalculatorTest to include groups.

CalculatorTest.java

 @BeforeGroups({"addgrp"})
    public void beforeGroup() {
        System.out.println("Before Group");
    }

    @Test(groups = {"addgrp"})
    public void addTest() {
        Assert.assertEquals(calculator.add(2, 3), 5);
    }

    @Test(groups = {"subgrp"})
    public void subtractTest() {
        Assert.assertEquals(calculator.subtract(4, 3), 1);
    }

    @AfterGroups({"addgrp"})
    public void afterGroup() {
        System.out.println("After Group");
    }
  • We have added the groups attribute to test methods addTest and subtractTest to indicate the groups to which the test belongs
  • The complements BeforeGroups and AfterGroups were added to demonstrate the behaviour

testng.xml

<test name="calc_add">
        <groups>
            <run>
                <include name="addgrp"/>
            </run>
        </groups>
        <classes>
            <class name="com.jcg.testng.CalculatorTest"/>
        </classes>
    </test>

    <test name="calc_sub">
        <groups>
            <run>
                <include name="subgrp"/>
            </run>
        </groups>
        <classes>
            <class name="com.jcg.testng.CalculatorTest"/>
        </classes>
    </test>

In the class definition, we just indicated the group for each test method. In the XML, we specify the groups for each test. We declare two additional tests within the same suite but under each test associate a specific group. We need to specify the classes associated with the test but also specify the group we want to include. groups also has the option of exclude using which we can exclude tests belonging to a group. Running this produces the following output

initialize before suite
Before Test
initialize calculator
After Test
initialize calculator
Before Group
After Group
after suite

4. Annotation Attributes

In the previous section, we covered the basic annotations available as part of TestNG. We covered a few attributes available as part of the annotations. This section is about the attributes available for the annotations.

  • alwaysRun – Applicable for all except BeforeGroups. When it is set to true, it will run irrespective of any failures.
  • dependsOnGroups – This is used to indicate the test groups on which the annotated method depends on. If there is a failure in the group, this method is skipped.
  • dependsOnMethods – Very similar to above except here it provides flexibility to specify methods than groups
  • enabled – provides the capability to flexibility enable or disable annotated methods or classes
  • inheritGroups – This annotation indicates that annotated method should inherit the groups from the test class

CalculatorAttributeTest.java

@Test
public class CalculatorAttributeTest {

    Calculator calculator = new Calculator();


    @Test
    public void addTest() {
        Assert.assertEquals(calculator.add(4, 3), 6);
    }

    @Test(dependsOnMethods = {"addTest"})
    public void subtractTest() {
        Assert.assertEquals(calculator.subtract(4, 3), 1);
    }

    @Test(enabled = false)
    public void multiplyTest() {
        Assert.assertEquals(calculator.multiply(4, 3), 12);
    }


}
  • Running the above as it displays the output 1 skipped and 1 failed. subtractTest method never runs as addTest method fails
  • Now adding the attribute alwaysRun with the value true to subtractTest the method ensures that it is run even if addTest fails.
  • Now multiplyTest can be enabled by setting the enabled attribute to true or by removing the attribute itself.
  • The changes above result in 2 successful tests and 1 failing test.

The example has been illustrated with only the Test annotation but it is very similar for other annotations.

5. Download the Source code

That was a TestNG Basic Annotations Tutorial.

Download
You can download the full source code of this example here: TestNG Basic Annotations Tutorial

Rajagopal ParthaSarathi

Rajagopal works in software industry solving enterprise-scale problems for customers across geographies specializing in distributed platforms. He holds a masters in computer science with focus on cloud computing from Illinois Institute of Technology. His current interests include data science and distributed computing.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button