[Java] Try JUnit test-launcher

4 minute read

JUnit test Try something like a start-up

About @Test annotation used in Junit The method that implements this annotation is invoked as a test method, I was wondering how the principle works Consider the contents of its implementation

Junit test method startup

In the first place, is the Java file started by Junit the only Java file specified under a specific directory? For example, if you put a Java file under a setting folder such as “src/test” Start the test from that src/test.

In other words, since it is considered that the subordinate of src/test is set as the entry point, Traverse Java files (class files) under that directory I think that only the method with @Test annotation is executed (I haven’t seen the implementation of Junit, so I can’t say for sure)

In this way, setting a specific folder or lower as an entry point is Load the Java files in order from the root path of the class loader by setting the class path Find the method that has the @Test annotation set, If annotated, instantiate the target class I thought I was executing the method.

Based on this consideration, I tried to see if a similar implementation could be made using reflection.

‥ ‥

Creating an annotation

Create an annotation to be added to the method like @Test annotation

package anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TestAnno {
  boolean testFlg() default false;
}

This time only the method set to true in testFlg Implemented to be the target of method invocation ‥ ‥

Creating a boot entry point

I searched for the class and method to which @TestAnno is assigned and created a class that could be invoked.

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

import anno.TestAnno;


public class Entry {

  public static void main(String[] args) throws Exception {

    // *1
    final String resourceName = "test";
    final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    final URL testRoot = classLoader.getResource(resourceName);
    final String appRootStr = classLoader.getResource(".").getFile().toString();



// *2
    try (Stream<Path> paths = Files.walk(Paths.get(testRoot.toURI()))) {

      paths.filter(Files::isRegularFile).forEach(path -> {


        // *3
        String targertAbsolutePath = path.normalize().toFile().getAbsolutePath().toString();
        String targetClassName = targertAbsolutePath.substring(appRootStr.length(), targertAbsolutePath.length()).replace(".class", "").replace("/", ".");

        // Four
        Class<?> testClass;
        Object testClassInstance;
        try {
          testClass = Class.forName(targetClassName);
          testClassInstance = testClass.newInstance();
        } catch (Exception e) {
          throw new RuntimeException(e);
        }

     // *Five
        for( Method method: testClass.getMethods()) {

          TestAnno testAnnotation = method.getAnnotation(TestAnno.class);
          if (testAnnotation == null) continue;

          try {
            if (testAnnotation.testFlg())
              method.invoke(testClassInstance);
          } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            e.printStackTrace();
          }

        }


      });


      System.out.println("Test End");
    }



  }

}

*1 In this process, the package that drives test is specified This time, target java files under package name [test]. → → testRoot

*2 Using the walk function to extract the java files under testRoot Execute a directory traverse. If it is a regular file (that is, if it is a class file), filter it to be processed Only class files are subject to Stream processing

*3 Here, the class file to be extracted is acquired as a character string including the package name. Reflection can be acquired with *4 Class.forName

*Four Obtain the class to be tested as reflection, Obtain an instance

*Five Extract the method object from the acquired class object, If testAnnotation is added and testFlg is true Invoke.

With the above procedure, the method with @testAnnotation can be extracted.

Implementation of the method to be tested

Here, define the class that has the method that is actually started by test.

package test;

import anno.TestAnno;

public class Test {

  @TestAnno(testFlg=true)
  public void testMethod() {


    String className = this.getClass().getName();
    String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
    System.out.println("invoked "+ className + ":" + methodName);

  }

  @TestAnno(testFlg=false)
  public void testMethod2() {


    String className = this.getClass().getName();
    String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
    System.out.println("invoked "+ className + ":" + methodName);

  }

  @TestAnno(testFlg=true)
  public void testMethod3() {


    String className = this.getClass().getName();
    String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
    System.out.println("invoked "+ className + ":" + methodName);

  }

}

Make settings like @TestAnno(testFlg=true), If you check whether only the method with testFlg true is invoked It worked fine.

‥ ‥ That’s all, I do not understand the settings and contents of the context class loader, so I’ll try to check it again in my spare time.

The difficulty with this implementation was getting the package name. Such an implementation may not be in demand, as there weren’t many sites available for reference.

This time I was thinking about the implementation contents of the point I was wondering about the movement of Junit, For classes and methods that are started by annotation settings I wonder if a similar implementation is made.

For example, automatic wiring of the spring framework and component scanning I think it’s not the same way of thinking.

If I have the opportunity, I’ll try to find out more about reflection.

Regarding the implementation of class loader and reflection in this implementation I realized that I didn’t understand at all When writing an article about Java, I think I should investigate the details of that area.

Most of the above code is cut off, so If you are interested, please refer to Github. https://github.com/ktkt11122334/qiita/tree/master/test

Tags:

Updated: