Skip to main content

Mapping tests from TestNG to JUnit

In the summer of 2020, J2ObjC's JRE emulation library (a fork of Android's libcore library) was updated from Android Nougat to Android 10. The update consisted of adding new APIs, and updating the existing code. Apart from general functionality changes, one of the main goals of the update was to port new test cases to better verify that the JRE emulation library works correctly.

While updating the java.time package, I came across a tck.java.time package containing approximately 15,000 new test cases written in TestNG.

The problem? J2ObjC only supports JUnit.

Instead of supporting both TestNG and JUnit, we decided to build testng2junit, a tool that converts TestNG tests to JUnit.

How Can These Frameworks be Mapped?

To convert from TestNG to JUnit, we needed to determine where the two testing frameworks differed. Attempting to run the TestNG tests through the compiler gave us plenty of compile-time errors to sift through. These syntax errors, coupled with documentation from both frameworks, helped us identify five areas that needed translation: imports, annotations, data providers, expected exceptions, and assertions.

The following are the changes needed to translate between frameworks. In red the TestNG version (to be removed) and in green the JUnit version (to be added)

1. Package of imports

 org.testng.Test  
 org.junit.Test 
2. Package of imported annotations
 org.testng.annotations.BeforeClass  
 org.junit.BeforeClass  
3. Name of annotations
 @BeforeMethod  
 @Before  
4. JUnit data providers have to be public and static and they do not support aliasing
 @DataProvider(name=”alias”)  
 Object[][] longName  
 ...  
 @Test(dataProvider=”alias”)   
   
 @DataProvider  
 public static Object[][] longName  
 ...  
 @Test  
 @UseDataProvider(“longName”)     
5. Different parameter name
 @Test(expectedExceptions=Exception.class)  
 @Test(expected=Exception.class)  
 6. Different parameter order in assert overloads
 assertEquals(obj1, obj2, msg)  
 assertEquals(msg, obj1, obj2)  

How Can The Test Cases be Translated?

Now that we knew what to change, we created several regular expressions (Regex) to find and convert all instances of TestNG syntax into their JUnit counterparts. The key step here was ensuring the Regex expressions were precise and thorough. Hit all the syntax listed above, and don’t mess with anything that was already okay.

I used regex101 to help me visualize and build regular expressions until they matched only the desired lines. First I copied in several examples of the pattern that needs changing into the test strings section, next I updated the regular expression until it correctly selects the parameters that need to be switched. Now each parameter is now in a separate Regex group so that we can find all entries of assertEquals(a, b, c) and convert them into assertEquals(c, a, b).

Takeaways

I hope this tool can help if you need to convert tests from TestNG to JUnit. The project is open source so feel free to create new functionalities to make the tool better.

Below you can find some examples of the tool in action as well as links to the tool, the original test file used in the examples (taken from real libcore test class), and the translated version.


Comments

Popular posts from this blog

Catching Java exceptions in Swift via j2objc

TL;DR : it’s possible to handle Java-originating exceptions in Swift for j2objc -based projects. Scroll to the end for example code. It’s getting more common to call j2objc -generated Objective-C code from Swift as iOS development shifts to this modern language. At a high level, we can imagine this means calling Java code from Swift. But Objective-C is an important link in this chain and it shapes the way Swift interacts with the code that started its life as Java. j2objc  does a great job of supporting Swift’s features when called with the --swift-friendly  flag. This feature is particularly useful when the Java code is annotated with @Nonnull , @Nullable , and @ParametersAreNonnullByDefault  to enforce Swift’s optionality at compile time and as you type in Xcode. But there’s one important Java feature that gets lost in translation on the way to Swift: exceptions. You might expect to catch Java exceptions from Swift like: do {     // original Java code throw