0

Lets say i have a method in some class in my application's package NetBeans project:

package wuzzle.woozle;

import org.contoso.frobber.grob.Whiztactular;

@Whiztactular
public void testFizBuzz() {
    if (1 != 0) 
       throw new Exception("Whiztactular failed");
}
package frob;

import org.contoso.frobber.grob.Whiztactular;

@Whiztactular
public void testfrobFizBuzz() {
    if (1 != 0) 
       throw new Exception("Whiztactular failed");
}
package grob;

import org.contoso.frobber.grob.Whiztactular;

@Whiztactular
public void testGrobZoom() {
    if (1 != 0) 

       throw new Exception("Whiztactular failed");
}
package contoso.gurundy;

import org.contoso.frobber.grob.Whiztactular;

@Whiztactular
public void testDingbatWoozle() {
    if (1 != 0) 
       throw new Exception("Whiztactular failed");
       throw new Exception("Whiztactular failed");
}

I want to:

  • enumerate all classes/methods
  • find methods tagged with a specified @Annotation
  • construct the class
  • call the (parameterless) method

How can i do this in Java?

In .NET it's easy

Here's how you do it in .NET (in pseudo-Java):

//Find all methods in all classes tagged with @Test annotation, 
//and add them to a list.
List<MethodInfo> whiztactularMethods = new ArrayList<>();

//Enumerate all assemblies in the current application domain
for (Assembly a : AppDomain.currentDomain.getAssemblies()) {
   //Look at each type (i.e. class) in the assembly
   for (Type t : a.getTypes()) {
      //Look at all methods in the class. 
      for (MethodInfo m : t.getMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly)) {
         //If the method has our @Whiztactular annotation defined: add it
         if (m.IsDefined(typeof(org.contoso.frobber.grob.Whiztactular), true)) 
            whiztactularMethods .add(m);
      }
   }
}

And now that we have a List of all methods with the @Whiztactular annotation, it's just a matter of calling them:

//Call every test method found above
for (MethodInfo m : whiztactularMethods) {
   Object o = Activator.CreateInstance(m.DeclaringType); //Construct the test object
   m.Invoke(o, null); //call the parameterless Whiztactular method
}

What is the JRE equivalent of the above?

In Delphi it's easy to

When a Delphi application starts, the initializer of each unit is called:

initialization
   WhiztactularRunner.registerWhiztactularClass(TWuzzleWoozle);

So then i can have all my test code register itself.

But Java doesn't have .java file initialization; nor does it have static constructors.

The Journey

I want JUnit to run tests

JUnit requires tests to be in a special separate project

Use reflection to find the test methods

Reflection requires you to know the name of the packages that all developers have put their tests in

Use Reflections library

Reflections requires you to know the name of the packages that all developers have put their tests in

Create my own Test Annotation, and use reflections to find all methods that are tagged with it

Reflections requires you to know the name of the packages that all developers have put their tests in

Create my own TestCase annotation, and use reflections to find all classes that are tagged with it

Reflections requires you to know the name of the packages that all developers have put their tests in

Create my own TestCase interface, and use reflections to find all classes that implement it

Reflections requires you to know the name of the packages that all developers have put their tests in

Create my own TestCase class, and use reflections to find all classes that extend it

Reflections requires you to know the name of the packages that all developers have put their tests in

Create a static list, and use a static class constructor to register the class with the my TestRunner

Java doesn't have static class constructors

Create a static list, and use the package initializer to register the class with the my TestRunner

Java doesn't have package initializers

Create a static list, and use the events to listen for when a package is loaded, and then register the package with my static list

Java doesn't have package load events

Enumerate all packages

Reflection has no way to enumerate all packages

Ask the class loader that loaded my current class for any other classes it has loaded

Class loader won't know about classes until someone has actually needed them, and you might not even be using the same class loader instance

Enumerate all packages in the current class path ⇐ in progress

Enumerate all jar files on the local PC, use a custom class loader to load each one, then get a list of all packages in each one ⇐ in progress

Spent 4 days so far trying to solve this problem that was solvable in .NET with 5 lines of code, and in Delphi with 3 lines of code

Investigate converting 409 jsp, and 498 java code files to ASP.net and C# ⇐ in progress

Give up on having automated unit, functional, and integration tests ⇐ in progress

Research Effort


20
  • 1
    Since you specifically refer to the JUnit 5 test annotation, you may just use JUnit 5’s Launcher to execute the tests. But if you really want to go low level, it’s ReflectionSupport might have the right methods for you. Commented Jul 22, 2022 at 12:26
  • 1
    ServiceLoader is a nice thing when you use modules, as then, you don’t use configuration files anymore; it’s part of the language. E.g. you declare provides service.Type with implementation.Type; in the module-info and the compiler will verify that all constraints are met. But it wouldn’t help you with your specific case, as there is no service type (interface or abstract base class) extended by your test classes. Commented Jul 22, 2022 at 18:09
  • 1
    I don’t see that the ReflectionSupport I have linked would require you to know the package, e.g. this method only requires you to know the class path entry. Commented Jul 22, 2022 at 18:13
  • 2
    E.g., the following code scans the class path for a class containing a method annotated with @Test: for(String cpEntry: System.getProperty("java.class.path").split(File.pathSeparator)) { for(var cl: ReflectionSupport.findAllClassesInClasspathRoot(Paths.get(cpEntry) .toUri(), cl -> Arrays.stream(cl.getMethods()).anyMatch(m -> m.isAnnotationPresent(Test.class)), str -> true)) { System.out.println(cl); } } Commented Jul 22, 2022 at 18:15
  • 2
    Well, as your own comment already says, “Enumerate all assemblies in the current application domain”, so it’s not the magic tool that scans the entire hard drive. Replace “application domain” with “class path” and take the one-liner I posted in this comment two days ago. So, what’s the problem? Commented Jul 25, 2022 at 15:23

3 Answers 3

1

Java and .Net are fundamentally different. The reason this task is not supported in the JVM out of the box is because of JVM's intrinsic lazy class loading; the JVM is not fully aware of every possible class on the classpath and loading all of them would be extraordinarily intensive CPU and Memory wise.

Essentially: What you are asking for is not possible without extensive writing of code that can scan avaialable classes on the classpath without loading them and examine their contents. You could implement this yourself, but it will be quite intensive. If you do wish to go that route, the answer you mentioned How to find annotated methods in a given package? is likely the easiest method, but will be non-optimal.

All is not lost though: I think the easiest way to accomplish the task you're wanting is to use a library like the following: https://github.com/ronmamo/reflections

The Reflections library is able to scan the classpath without actually performing a classload and is quite quick. I use this lib personally for writing frameworks and scanning for developer extensions on startup.

Example:

// MethodsAnnotated with @GetMapping
Set<Method> resources = reflections.get(MethodsAnnotated.with(GetMapping.class).as(Method.class));
Sign up to request clarification or add additional context in comments.

7 Comments

What would be the Reflections way to do it
Reflections works internally by examining bytecode without performing an actual classload operation. It's a well written library. If you add the dependency to your project, you can use my code snippet above, but I would take a look at the documentation on the project page. Good luck!
Is it supposed to be Reflections; with a capital R? And where is MethodsAnnotated declared? Day three. I just want to run test cases; Java makes things so painful at every turn.
Oh, wait, from the homepage, "Scanner must be configured in order to be queried, otherwise an empty result is returned" I was hoping Reflections had solved all these problems. Back to trying to figure out how get JUnit to find its tests.
If Junit can't find all of it's tests, that's a configuration problem with your pom.xml! You can easily setup the surefire plugin to exclude or include certain tests
|
0

Use ClassGraph. Unlike Reflections, it's (exceptionally) actively maintained and works in many more scenarios (e.g. Reflections will keel over if you try to use it with modules).

try (ScanResult scanResult = new ClassGraph()
                               .enableAllInfo()  // Scan classes, methods, fields, annotations
                               .scan()) {        // Start the scan
      for (ClassInfo clazz : scanResult.getClassesWithMethodAnnotation(annotation)) {
          //Just your run-of-the-mill reflection from here
          Class<?> loaded = clazz.loadClass(); //SEE THE NOTE
          //Assumes the default constructor exists. Do what's appropariate if it doesn't.
          Object instance = loaded.getConstructor().newInstance(...);
          //Use the usual clazz.getMethods() and filter by annotation, or use ClassInfo to get closer
          Method annotatedMethod = ...;
          annotatedMethod.invoke(instance);
      }
}

NOTE: ClassGraph tries its best to figure out what classloader to use when loading classes, but you can still end up in bizarre situations. So, if you know already the correct classloader to use, I'd recommned going with the regular Java reflection here instead, e.g. Class.forName(clazz.getName(), loader).

Comments

0

The solution to the problem is pretty difficult; but at least it works.

Create a base TestCase class:

public class TestCase
{
   ...
}

For every test case, create a class that descends from TestCase:

public class DateHelperTests extends TestCase
{
    public void testNow() throws Exception {
        LocalDateTime dt = DateHelper.now();
        checkTrue(dt != null);
    }
}

Then, since Java doesn't support reflection, you have to enumerate all the classes yourself:

    private static void registerTests() {
        registerTestClass(DateHelperTests.class);
        registerTestClass(ToolkitTests.class);
        registerTestClass(TurboencabulatorTests.class);
    }

And now that you have them all registered, you can find them in a common list of your choosing.

Reflection in Java is slightly different:

  • rather than being able to discover methods at runtime
  • you discover them at code-writing time, and hard-code them in a list

It's all very obvious once you realize that reflection in .NET and Java are fundamentally different.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.