Guide

Dynamically naming your tests with TestNG

  • 2 March 2021
  • 0 replies
  • 17 views

Userlevel 1
Badge +2

Test Names Should Identify Tests

So here’s something that seems obvious but is often overlooked: test name should let you identify an individual test

You might have multiple tests in a Class, and they all might be testing the same Class as well... but the name of the test should still let you identify which test is which, eg, it should be unique. Ideally, they’ll also tell you what is being tested, eg, be descriptive.

Here’s an example of a unique names VS descriptive names. Both “TC1498245” and “assertAccountValid” are unique test case names… But only the latter also tells you what the test is doing.  That is, only the last one is descriptive.

 

Without unique test names, your Sauce Labs dashboard can end up looking like this:

5 Identically named tests, all testing different things

Not only is this confusing, it also stops you making full use of Sauce Labs’ failure analysis and insights tooling.

 

There’s three ways to change the names of a Sauce Labs test, but I prefer setting it in advance by using the name desired capability when setting up a new WebDriver session.

 

With TestNG, you might be setting up a new test in a BeforeMethod, and that introduces a problem; How can we know the name of a test before it runs?

Say our core looks like this:

  @BeforeMethod
public void setupTestMethod() throws MalformedURLException {
String sauceURL = "https://ondemand.saucelabs.com/wd/hub";

MutableCapabilities sauceOpts = new MutableCapabilities();

sauceOpts.setCapability("name", "4-best-practices");
/** The rest of your capabilities go here *//

driver = new RemoteWebDriver(new URL(sauceURL), capabilities);
}

This code works, but every test in this class will get named “4-best-practices”.  No Bueno.

 

Using Reflection to generate test names

We’re not defeated yet!  Each test method in our class has a name; What if we used that as a test name instead?  If our test methods are well named they should be both unique and descriptive, so they’re perfect.  But, how can we use the method name in our desired capabilities?  Those are set before we even call the test method!

 

Reflection to the rescue! TestNG allows you to pass parameters to methods, often so you can make use of a DataProvider. On @BeforeMethod and @AfterMethod, this parameter handling has a nifty trick; If you define a parameter of type java.lang.reflect.Method, TestNG populates it with a reference to the test method.  That reference has a getName() method in turn, so now, we can use that to set the name of our test!

@BeforeMethod
public void setupTestMethod(java.lang.reflect.Method testMethod) throws MalformedURLException {
String sauceURL = "https://ondemand.saucelabs.com/wd/hub";

MutableCapabilities sauceOpts = new MutableCapabilities();

sauceOpts.setCapability("name", testMethod.getName());
/** The rest of your capabilities go here *//

driver = new RemoteWebDriver(new URL(sauceURL), capabilities);
}

 

Making names more readable

Alrighty, so now our tests look like this:

Unique Test Case Names

Hmm, that’s better, but it’s still a bit awkward. It would be nice if they were more… Human….y.  Less CamelCase (No shade to camels).

 

Well, we can do that by processing the test name with the Apache Commons StringUtils library!  If your code doesn’t already include it, add the dependency to your pom.xml:

        <dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>

Add it to your imports:

import org.apache.commons.lang3.StringUtils;

And now, we can use the splitByCharacterTypeCamelCase method to give us a a set of individual strings, which we can join up again with spaces to make something suitable for actually reading.  While we’re at it, let’s make the first character a capital letter as well:

String testName = testMethod.getName();
String readableTestName = StringUtils.join(
StringUtils.splitByCharacterTypeCamelCase(
StringUtils.capitalize(testName)),
' ' //Use a blank space to separate each word
);

And now we have this:

Test names, readable by humans

Make it a Method

One more thing; Let’s pull the naming code out of our BeforeMethod and put it in a method of its own.  Not only will that nicely encapsulate the behaviour, it makes it easier to manage if we need to add more code later.

/** Our new method **/
public String generateTestName(Method testMethod) {
String testName = testMethod.getName();

String readableTestName = StringUtils.join(
StringUtils.splitByCharacterTypeCamelCase(
StringUtils.capitalize(testName)),
' '
);

return readableTestName;
}

@BeforeMethod
public void setupTestMethod(Method testMethod) throws MalformedURLException {
String sauceURL = "https://ondemand.saucelabs.com/wd/hub";

MutableCapabilities sauceOpts = new MutableCapabilities();

sauceOpts.setCapability("name", generateTestName(testMethod);
/** The rest of your capabilities go here **/

driver = new RemoteWebDriver(new URL(sauceURL), capabilities);
}

 

That’s it!  There are still a few issues we could manage like adding in the class name and dealing with data providers, but that’ll be another article.

 

Happy Testing!


0 replies

Be the first to reply!

Reply