How to convert test classes from JUNIT to TESTNG

Everybody that I talk to says that they use TestNG. I am still using JUNIT.
Am I the only person still doing it?


I am using TestNG as well.
I started with JUNIT a while ago and switched to TestNG for a number of reasons.


So you think that TestNG is better than JUNIT?

TestNG is better than JUNIT version 4.

Let me tell you why.

It does not use static methods for setting up and cleaning the test environment.

I am referring to the methods that use the @BeforeClass and @AfterClass annotations.


Oh really? This is awesome. What else does TestNG do better?

You can have dependent test scripts.
Test scripts with priorities.
It is easier to run test scripts based on categories and suites.
It is possible to have multiple data providers with values for parameters.
And test scripts can have parameters.


So many good things. Is it difficult to migrate from JUNIT to TestNG?

It is not difficult.

I will show you how it is done.

We will start with simple JUNIT test class and discuss one by one the elements that need changes.


Great, lets start.

This is our sample test class:
 import static org.junit.Assert.*;
 import java.util.ArrayList;
 import java.util.Collection;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.FixMethodOrder;
 import org.junit.rules.Timeout;
 import org.junit.runners.MethodSorters;
 import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;

 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @RunWith(value = Parameterized.class)
 public class JUNITClass {
 
  @Rule
  public Timeout globalTimeout = new Timeout(1000); 
 
  @Rule
  public CustomTestRule screenshotTestRule = new CustomTestRule();
 
  private String keyword;
 
  public JUNITClass(String keyword) {
   this.keyword = keyword;
  }   
 
  @BeforeClass
  public static void setUp() {
  
  }
 
  @AfterClass
  public static void tearDown() {
  
  }
     
  @Parameters
  public static Collection< Object[] > data() {
   Collection< Object[] > params = new ArrayList<>(100);
   params.add(new Object[] { "java-author-10" });
   params.add(new Object[] { "oracle-date-20" });
   params.add(new Object[] { "microsoft-title-15" });     
   return params;
  }
 
  @Test
  public void test1()  {
  
  }
 
  @Test
  public void test2() {                 
   assertTrue(keyword.length() > 0);
   assertEquals(keyword.contains("-"), true);  
  } 
 
  @Ignore
  public void test3() {
  
  }
 
  @Test(timeout=1000)
  public void test4() {  
  
  }
 
  @Test(expected = IndexOutOfBoundsException.class)
  public void test5() {  
   throw new IndexOutOfBoundsException();
  }
   
 }
 


The test class uses many JUNIT elements such as

  • annotations (@Test, @Ignore, @BeforeClass, @AfterClass, @RunWith, @Rule, @Parameters)
  • annotation attributes (expected = , timeout = )
  • test fixtures (setUp() and tearDown() methods)
  • assertions


So we will change them one by one.

By the way, you can follow along on your computer while I make the changes.


What's the first thing to do?

First, we remove the JUNIT library from the project by
  • displaying the Navigator view in Eclipse
  • opening the project
  • right clicking the project name and selecting Properties
  • selecting the JAVA BUILD PATH option
  • selecting the Libraries tab
  • selecting JUNIT 4
  • clicking the REMOVE button


Next, remove all JUNIT packages from the beginning of the class:

  import static org.junit.Assert.*;
  import java.util.ArrayList;
  import java.util.Collection;
  import org.junit.AfterClass;
  import org.junit.BeforeClass;
  import org.junit.FixMethodOrder;
  import org.junit.rules.Timeout;
  import org.junit.runners.MethodSorters;
  import org.junit.Ignore;
  import org.junit.Rule;
  import org.junit.Test;
  import org.junit.runner.RunWith;
  import org.junit.runners.Parameterized;
  import org.junit.runners.Parameterized.Parameters;
  

Now, we have lots of errors in the test class because of missing packages.

Second, we add the TestNG library to the project by
  • displaying the Navigator view in Eclipse
  • opening the project
  • right clicking the project name and selecting Properties
  • selecting the JAVA BUILD PATH option
  • selecting the Libraries tab
  • clicking the ADD LIBRARY button
  • selecting TestNG
  • clicking the FINISH button

We also need to install the TestNG plugin for Eclipse:
  • click Help in the Eclipse menu
  • click Eclipse MarketPlace
  • search for Test NG
  • install the TestNg for Eclipse plugin

Done. We still have those errors in the code. Which one should we fix first?

Lets start with @Test.

Click on the error icon to the left of @Test and import the following package:
import org.testng.annotations.Test;

Is the error still there?


No. It is gone.

Excellent.

Now, about @Ignore.

In TestNG, you can ignore a test using the @Test annotation as well.


How do you do this?

By using the enabled attribute.

So replace @Ignore with @Test(enabled=false).

This should get rid of the error for @Ignore.


Done.

Very good.

Now, lets make the changes for the setUp() and tearDown() methods.

Click on the error messages and import the following 2 packages:

 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 
And then remove the static keyword from the setUp() and tearDown() signatures.


So these methods will not be static any longer in TestNG?

They won't.

How is your code looking so far?


I did all changes. What's next?

Lets continue a bit more with the @Test annotation.

In our JUNIT code, we have a test that tests for exceptions and one that uses a timeout.

For the test with timeout, replace @Test(timeout=1000) with @Test(timeOut=1000)

For the test with exception, replace @Test(expected = IndexOutOfBoundsException.class) with @Test(expectedExceptions = IndexOutOfBoundsException.class).

Do these changes work for you?


They sure do. Everything is simple and smooth so far.


Awesome.

We can work on the assertions now.

In the JUNIT code, we use 2 assertions: assertTrue and assertEquals.

Import the following package:

  import org.testng.AssertJUnit;
 

And prefix both assertions with the name of the package:
  AssertJUnit.assertTrue(keyword.length() > 0);
  AssertJUnit.assertEquals(keyword.contains("-"), true); 
  

The assertions should be good now as well.


The assertions have no more errors. We are making lots of progress.


Yes.

Now, lets deal with the execution order for the test scripts.

In JUNIT, there is not a lot of support for it.

You can run the test scripts alphabetically by name with the @FixMethodOrder(MethodSorters.NAME_ASCENDING).

But if you need the test scripts run in other orders, I am not sure how you can do that.

In TestNG, this is done with the dependsOnMethods attribute of the @Test annotation.

For example,

 @Test(dependsOnMethods = { "test2" })
 public void test1()  {
 
 }
 
 @Test(dependsOnMethods = { "test4" })
 public void test2(String keyword) {
 
 }
 
In this case, test1() depends on test2() so test2() executes before test1.

test2() depends on test4() so test4() executes before test2.


Wow. This is so much better than @FixMethodOrder().


It is much simpler.

Make sure that you update your code before we move on.


My code is changed.

Good.

Now, lets change the code related to parameters.

In TestNG, similar with JUNIT, you need a method that provides the data for the parameter.

This method is annotated with @DataProvider and has a name attribute for the data provider name:

 @DataProvider(name = "data")
 public Object[][] createData1() {
  return new Object[][] {
    {"java-author-10"},
    {"oracle-date-20"},
    {"microsoft-title-15"}    
  };
 }
 
The package org.testng.annotations.DataProvider; needs to be added as well.

Then, we need to connect the data with the parameter.

We do not need any longer to pass the parameter to the class constructor.
And we dont need a class field for the parameter, either.

The test script uses the parameter in the same way a method uses a parameter.

The test script also uses the dataProvider annotation to specify which dataProvider has the data for the parameter:

 @DataProvider(name = "data")
 public Object[][] createData1() {
  return new Object[][] {
    {"java-author-10"},
    {"oracle-date-20"},
    {"microsoft-title-15"}    
  };
 }
 
 @Test(dataProvider="data")
 public void test2(String keyword) {
 
 } 
 
Still following?


Yes.

What else is there to change?


JUNIT Rules.

Right.

In TestNG, you use listeners instead of rules.

They work in a similar way.

Have a look at this sample listener:

 package TestNG;

 import org.testng.ITestContext;
 import org.testng.ITestListener;
 import org.testng.ITestResult;
 import org.testng.Reporter;

 public class CustomTestListener implements ITestListener {
 
 @Override
 public void onTestStart(ITestResult result) { 
 
 }
 
 @Override
 public void onTestSuccess(ITestResult result) {
  System.out.println("PASSED TEST");
 }

 @Override
 public void onTestFailure(ITestResult testResult) {        
 
  System.out.println("FAILED TEST");                 
 }
  
 @Override
 public void onTestSkipped(ITestResult result) {
        
 }
 
 @Override
 public void onTestFailedButWithinSuccessPercentage(ITestResult result) {
        
 }
 
 @Override
 public void onStart(ITestContext context) {
        
 }
 
 @Override
 public void onFinish(ITestContext context) {
       
 }
    
 }
 
The custom listener class implements the ITestListener interface and overrides all its methods.

The listener can then be attached to a test class using the @Listeners annotation:

 @Listeners(CustomTestListener.class)
 public class TestNGClass {
 
 ...............
 
 }
 

This is very clear and similar to the JUNIT rules. With the exception that we dont need to create a class field for the new listener object.

Exactly.

Because the test class know what listener to use through the @Listeners annotation.

We missed something in the JUNIT code: categories.

In JUNIT, you can specify a category for a test script or for the whole test class.

This is done through the @Category annotation as follows:

 @Category({SlowTests.class})
 public class JUNITClass2 { 
 
 ...................
 
 }
 
In TestNG, groups are used instead of categories.

And they are just another attribute of the @Test annotation.

For example,

 @Test(groups="slowTests")
 public void test1()  {
  
 }
  
 @Test(groups="slowTests")
 public void test2() {                 
  
 } 
 
test1() and test2() methods belong to the slowTests group.


OK. So use groups instead of categories.


Yes.

A good side effect is that in TestNg you do not need to create an empty class for each group.

Remember that in JUNIT, before using a category, you had to create an empty class for it.


Yes, that's right. So we will have less classes now.

Yes.

The last change that we will discuss is about JUNIT suites.

In JUNIT, you need test suites to run test scripts from multiple test classes based on categories.

For example:

  import org.junit.experimental.categories.Categories;
  import org.junit.experimental.categories.Categories.IncludeCategory;
  import org.junit.runner.RunWith;
  import org.junit.runners.Suite.SuiteClasses;

  @RunWith(Categories.class)
  @IncludeCategory(SlowTests.class)
  @SuiteClasses({
    JUNITClass.class, 
    JUNITClass2.class     
  })
  
This test suite runs all test scripts from the 2 classes that are part of the specified category.


And how does TestNG solve this?

TestNG uses an xml file called testng.xml for organizing test classes in test suites.

When running the tests, everything happens as per the structure of this file.

In our case, testng.xml could look like this:

 < suite name="My suite" > 
 
  < listeners >
   < listener class-name="TestNG.CustomTestListener" />
  < / listeners >
   
  < test name="test1" >
  
   < groups >
    < run >      
     < include name="slowTests"  />
    < / run >
   < / groups >
    
   < classes >
    < class name="TestNG.TestNGClass" />               
    < class name="TestNG.TestNGClass2" />
   < / classes >
  < / test > 
   
 < / suite >
 
One thing to notice in the xml file is that a suite is made of tests which are made of test classes.

In a test class, you can specify the groups to be used.

In the xml file, you can also mention the listener to be used for all test classes.


So no more suite classes?


No more.

Everything is controlled by the testng.xml file.

Let have a look at the final code that uses TestNG:
 package TestNG;

 import static org.testng.AssertJUnit.assertTrue;
 import org.testng.annotations.Test;
 import org.testng.AssertJUnit;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Listeners;

 @Listeners(CustomTestListener.class)
 public class TestNGClass {
 
  @BeforeClass
  public void setUp() {
  
  }
 
  @AfterClass
  public void tearDown() {
  
  }
 
  @DataProvider(name = "data")
  public Object[][] createData1() {
    return new Object[][] {
    {"java-author-10"},
    {"oracle-date-20"},
    {"microsoft-title-15"}    
   };
  }
 
  @Test(dependsOnMethods = { "test2" })
  public void test1()  {
   assertTrue(false);
  }
 
  @Test(dataProvider="data", 
    dependsOnMethods = { "test4" })
  public void test2(String keyword) {
   AssertJUnit.assertTrue(keyword.length() > 0);
   AssertJUnit.assertEquals(keyword.contains("-"), true);  
  } 
 
  @Test(enabled=false)
  public void test3() {
  
  }
 
  @Test(timeOut=1000, 
    groups="slowTests")
  public void test4() {  
  
  }
 
  @Test(expectedExceptions = IndexOutOfBoundsException.class)
  public void test5() {  
   throw new IndexOutOfBoundsException();
  }
   
 }
 
We are almost at the end of the conversion process.

Lets run the TestNG tests.

They run correctly.

What do you think about converting a test class from JUNIT to TestNG?

Was it too complicated?


With all these details, I felt that it was very easy. I am looking forward to starting using TestNG in my test automation project.


Good luck!

Share this

5 Responses to "How to convert test classes from JUNIT to TESTNG"

  1. Awesome!. Thanks For Sharing.

    ReplyDelete
  2. I do not get this bit at all.

    In my existing code, I had the following:

    import org.junit.runner.RunWith;
    import org.junit.runners.Parameterized;
    import org.junit.runners.Parameterized.Parameter;
    import org.junit.runners.Parameterized.Parameters;
    @RunWith(Parameterized.class)
    @Parameter
    public static String browserName;

    @Parameters
    public static Collection data () {
    Common.printLine();

    debug.print(thisClass + " set up browser parameters: " + global.variables.browsers);

    switch(global.variables.browsers) {
    case "all":
    Object[][] data = { {"firefox"}, {"chrome"}, {"edge"} };
    return Arrays.asList(data);
    case "firefox":
    Object[][] data1 = { {"firefox"}};
    return Arrays.asList(data1);
    case "chrome":
    Object[][] data2 = { {"chrome"}};
    return Arrays.asList(data2);
    case "edge":
    Object[][] data3 = { {"edge"}};
    return Arrays.asList(data3);
    default:
    Object[][] data4 = { {"firefox"}};
    return Arrays.asList(data4);
    }
    }
    The replacement code for testNG looks fine for the Parameters bit:

    @DataProvider(name = "data")
    public static Collection data () {
    Common.printLine();

    debug.print(thisClass + " set up browser parameters: " + global.variables.browsers);

    switch(global.variables.browsers) {
    case "all":
    Object[][] data = { {"firefox"}, {"chrome"}, {"edge"} };
    return Arrays.asList(data);
    case "firefox":
    Object[][] data1 = { {"firefox"}};
    return Arrays.asList(data1);
    case "chrome":
    Object[][] data2 = { {"chrome"}};
    return Arrays.asList(data2);
    case "edge":
    Object[][] data3 = { {"edge"}};
    return Arrays.asList(data3);
    default:
    Object[][] data4 = { {"firefox"}};
    return Arrays.asList(data4);
    }
    }
    This is what is suggested as equivalent for parameter, but I don't understand how to apply it?

    @Test(dataProvider="data")
    public void test2(String keyword) {

    }
    Can anyone explain how to adapt my parameter shown above, browserName?

    I think browserName previously held the output from the Collection object, so that when, for instance, the input parameter (global.variables.browsers) was set to 'all', the test would be performed for each of the browsers in order

    ReplyDelete
  3. I do not get this bit at all.

    In my existing code, I had the following:

    import org.junit.runner.RunWith;
    import org.junit.runners.Parameterized;
    import org.junit.runners.Parameterized.Parameter;
    import org.junit.runners.Parameterized.Parameters;
    @RunWith(Parameterized.class)
    @Parameter
    public static String browserName;

    @Parameters
    public static Collection data () {
    Common.printLine();

    debug.print(thisClass + " set up browser parameters: " + global.variables.browsers);

    switch(global.variables.browsers) {
    case "all":
    Object[][] data = { {"firefox"}, {"chrome"}, {"edge"} };
    return Arrays.asList(data);
    case "firefox":
    Object[][] data1 = { {"firefox"}};
    return Arrays.asList(data1);
    case "chrome":
    Object[][] data2 = { {"chrome"}};
    return Arrays.asList(data2);
    case "edge":
    Object[][] data3 = { {"edge"}};
    return Arrays.asList(data3);
    default:
    Object[][] data4 = { {"firefox"}};
    return Arrays.asList(data4);
    }
    }
    The replacement code for testNG looks fine for the Parameters bit:

    @DataProvider(name = "data")
    public static Collection data () {
    Common.printLine();

    debug.print(thisClass + " set up browser parameters: " + global.variables.browsers);

    switch(global.variables.browsers) {
    case "all":
    Object[][] data = { {"firefox"}, {"chrome"}, {"edge"} };
    return Arrays.asList(data);
    case "firefox":
    Object[][] data1 = { {"firefox"}};
    return Arrays.asList(data1);
    case "chrome":
    Object[][] data2 = { {"chrome"}};
    return Arrays.asList(data2);
    case "edge":
    Object[][] data3 = { {"edge"}};
    return Arrays.asList(data3);
    default:
    Object[][] data4 = { {"firefox"}};
    return Arrays.asList(data4);
    }
    }
    This is what is suggested as equivalent for parameter, but I don't understand how to apply it?

    @Test(dataProvider="data")
    public void test2(String keyword) {

    }
    Can anyone explain how to adapt my parameter shown above, browserName?

    I think browserName previously held the output from the Collection object, so that when, for instance, the input parameter (global.variables.browsers) was set to 'all', the test would be performed for each of the browsers in order

    ReplyDelete