@Test(singleThreaded = true) on a class with non-test public methods

34 views
Skip to first unread message

Rich MacDonald

unread,
Apr 4, 2024, 5:34:09 PMApr 4
to testng-users
The background is that I have struggled to develop a manageable system for parallel testing in a multi-module maven environment. Typically, I would prefer all my classes to run in a single-thread. At least by default. But I have never satisfactorily got <parallel>classes</parallel> in the pom.xml to be accepted. This is a separate issue and not what this thread is concerned with.

So I thought about defaulting to parallel threads per method as the default. But I definitely need some classes to run single-threaded.

The documentation says I can use the annotation attribute "singleThreaded = true", and this has to be placed on the class.

But what Annotation do I need for that singleThreaded annotation. The only one I can see working is @Test. So annotate each class as @Test(singleThreaded = true).

Unfortunately, I have many public methods in my test classes that are not test methods. This is because my test classes implement other interfaces, and thus need public methods for those interface methods.

I can add @Test(enabled = false) to all these methods, but ugh.

Any other options? Thanks.

Rich MacDonald

Krishnan Mahadevan

unread,
Apr 5, 2024, 4:47:06 AMApr 5
to testng...@googlegroups.com
Rich,

Perhaps you could use a MethodInterceptor implementation that would do this for you. 

Basically the idea is to use the method interceptor to decide if a method should be included or not.

You would ONLY include those methods, that are NOT part of interface definitions. 

Below is a very basic implementation of how to do this [ This implementation does NOT deal with test class inheritances wherein your test class has a base class from which you get a bunch of interface definitions ]

import org.testng.IMethodInstance; import org.testng.IMethodInterceptor; import org.testng.ITestContext; import org.testng.ITestNGMethod; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; public class MyTransformer implements IMethodInterceptor { @Override public List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context) { //Lets first collect all the classes that are involved. List<Class<?>> classes = methods.stream() .map(it -> it.getInstance().getClass()) .distinct() .collect(Collectors.toList()); //We now create a map of Class to methods. //Here the methods are ONLY those methods that are coming from an interface that a class implements. Map<Class<?>, List<Method>> classToInterfaceImplsMapping = new ConcurrentHashMap<>(); for (Class<?> klass : classes) { //First lets extract out all the interfaces that this class implements. List<Class<?>> interfaces = Arrays.stream(klass.getInterfaces()).toList(); //Now let's extract all the methods that are part of all the interface definitions. List<Method> methodsInInterfaces = extractMethodsInInterfaces(interfaces); classToInterfaceImplsMapping.computeIfAbsent(klass, k -> new ArrayList<>()) .addAll(methodsInInterfaces); } return methods.stream() .filter(it -> include(it, classToInterfaceImplsMapping)) //Skip interface implementations .collect(Collectors.toList()); } private static List<Method> extractMethodsInInterfaces(List<Class<?>> interfaces) { return interfaces.stream() .flatMap(it -> Arrays.stream(it.getDeclaredMethods())) .collect(Collectors.toList()); } private static boolean include(IMethodInstance methodInstance, Map<Class<?>, List<Method>> classToInterfaceImplsMapping) { ITestNGMethod itm = methodInstance.getMethod(); Method m = itm.getConstructorOrMethod().getMethod(); Class<?> belongingClass = methodInstance.getInstance().getClass(); //First extract all the interface method definitions for our class. //Then stream it and ensure that none of those methods matches with our test method boolean result = classToInterfaceImplsMapping.get(belongingClass) .stream() .noneMatch(it -> bothMethodsSame(it, m)); System.err.println("Include " + itm.getQualifiedName() + "? " + result); return result; } private static boolean bothMethodsSame(Method first, Method second) { if (!first.getName().equals(second.getName())) { return false; } if (!first.getReturnType().equals(second.getReturnType())) { return false; } return equalParamTypes(first.getParameterTypes(), second.getParameterTypes()); } private static boolean equalParamTypes(Class<?>[] params1, Class<?>[] params2) { if (params1.length != params2.length) { return false; } for (int i = 0; i < params1.length; i++) { if (!params1[i].equals(params2[i])) { return false; } } return true; } }


Thanks & Regards
Krishnan Mahadevan

"All the desirable things in life are either illegal, expensive, fattening or in love with someone else!"
My Scribblings @ http://wakened-cognition.blogspot.com/
My Technical Scribblings @ https://rationaleemotions.com/

--
You received this message because you are subscribed to the Google Groups "testng-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to testng-users...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/testng-users/9105eef6-beeb-45d0-9507-225485d2596dn%40googlegroups.com.

Rich MacDonald

unread,
Apr 5, 2024, 3:53:53 PMApr 5
to testng-users
Krishnan,

Thanks for the suggestion and code. Yes, I can see this working. 

At the moment, it just seems a little more "obscure" than I'd like to subject the team to.
Especially compared with  "<parallel>classes</parallel>", which really should be perfectly fine.
And I think I finally have that working now. Put it in the pom and also in each testng.xml file

Rich MacDonald

Krishnan Mahadevan

unread,
Apr 5, 2024, 8:21:57 PMApr 5
to testng...@googlegroups.com
Rich

>> And I think I finally have that working now. Put it in the pom and also in each testng.xml file

You dont need to be doing that. There is a listener called IAlterSuiteListener. You should be able to alter your parallel execution strategy via this listener also. 

If you add this listener via ServiceLoading capabilities of TestNG then it gets automatically executed irrespective of how you run your tests and without it being even explicitly referenced. 

The earlier listener I shared also would behave the same way if you wire it in using service loading capabilities. 

You can also tweak that logic by:

1. Create a custom annotation 
2. Annotate all methods that you want TestNG to ignore 
3. Use the same method interceptor to skip these custom annotated methods. 

The above same approach can also be built by defining a parent class which implements the IHookable interface where you do the same filtering. 

So yeah there are more than one ways of doing it in TestNG. 



Thanks & Regards
Krishnan Mahadevan

"All the desirable things in life are either illegal, expensive, fattening or in love with someone else!"

From: testng...@googlegroups.com <testng...@googlegroups.com> on behalf of Rich MacDonald <macdona...@gmail.com>
Sent: Saturday, April 6, 2024 1:23:53 AM
To: testng-users <testng...@googlegroups.com>
Subject: Re: [testng-users] @Test(singleThreaded = true) on a class with non-test public methods
 
Reply all
Reply to author
Forward
0 new messages