计算@Category在JUnit的一组测试中出现的次数

时间:2018-10-17 20:35:18

标签: java junit

我已经使用Java,Selenium,Junit,Maven开发了一整套的自动化测试。

对于每个测试,它们都有一个或多个@Category注释,用于描述每个测试涵盖的软件区域。例如:

@Test
@Category({com.example.core.categories.Priority1.class,
           com.example.core.categories.Export.class,
           com.example.core.categories.MemberData.class})


@Test
@Category({com.example.core.categories.Priority1.class,
           com.example.core.categories.Import.class,
           com.example.core.categories.MemberData.class})


@Test
@Ignore
@Category({com.example.core.categories.Priority2.class,
           com.example.core.categories.Import.class,
           com.example.core.categories.MemberData.class})

我想做的是找到一种方法来计算包含给定类别的测试数量。所有可能的类别都是//com/example/core/categories文件夹中的文件名作为源列表。

我已经尝试构建一个shell脚本来进行字数统计,这似乎还可以,但是我认为会有更多的“内置”来处理@Category。

我最大的问题是,即使我得到了正确的计数,也很有可能将一个或多个测试标记为@Ignore,这应该使该测试@Category无效,但无需大量使用标志并读取每个文件行-按顺序排列,以排除正确的计数。

是否有一种很好的方法可以逐项列出@Category的内容,而这也是@Ignore的因素呢?

示例输出

| Category                                     | Count |
|----------------------------------------------|------:|
| com.example.core.categories.Export.class     | 1     |
| com.example.core.categories.Import.class     | 1     |
| com.example.core.categories.MemberData.class | 2     |
| com.example.core.categories.Priority1.class  | 2     |
| com.example.core.categories.Priority2.class  | 0     |
| com.example.core.categories.Priority3.class  | 0     |

2 个答案:

答案 0 :(得分:6)

动态“按类别进行测试”计算机

(推荐方法)

我尝试了一种使用抽象层中的计数器执行此操作的方法,但是这很痛苦,必须在每个Test方法的开头添加源代码。

最后,这是我为满足您的需求而编写的源代码。它相当沉重(反射...),但是它对现有源代码的干扰较小,可以完全满足您的需求。

首先,您必须创建一个Testsuite(包含各种其他套件,或者直接包含您想要的所有Test类),以确保最后已加载了您想要统计的所有Test。

在此套件中,您必须实现一个名为“ @AfterClass”的“最终挂钩”,当整个测试套件已由 JUnit 完全管理时,它将被一次调用。

这是我为您编写的测试套件实现:

package misc.category;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicInteger;

import org.junit.AfterClass;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;

@RunWith(Suite.class)
@Suite.SuiteClasses({ UnitTestWithCategory.class })
public class TestSuiteCountComputer {

    public static final String MAIN_TEST_PACKAGES = "misc.category";

    private static final Class<?>[] getClasses(final ClassLoader classLoader)
            throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
        Class<?> CL_class = classLoader.getClass();
        while (CL_class != java.lang.ClassLoader.class) {
            CL_class = CL_class.getSuperclass();
        }
        java.lang.reflect.Field ClassLoader_classes_field = CL_class.getDeclaredField("classes");
        ClassLoader_classes_field.setAccessible(true);
        Vector<?> classVector = (Vector<?>) ClassLoader_classes_field.get(classLoader);

        Class<?>[] classes = new Class[classVector.size()]; // Creates an array to avoid concurrent modification
                                                            // exception.
        return classVector.toArray(classes);
    }

    // Registers the information.
    private static final void registerTest(Map<String, AtomicInteger> testByCategoryMap, String category) {
        AtomicInteger count;
        if (testByCategoryMap.containsKey(category)) {
            count = testByCategoryMap.get(category);
        } else {
            count = new AtomicInteger(0);
            testByCategoryMap.put(category, count);
        }

        count.incrementAndGet();
    }

    @AfterClass
    public static void tearDownAfterClass() throws Exception {
        Map<String, AtomicInteger> testByCategoryMap = new HashMap<>();

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        while (classLoader != null) {
            for (Class<?> classToCheck : getClasses(classLoader)) {
                String packageName = classToCheck.getPackage() != null ? classToCheck.getPackage().getName() : "";
                if (!packageName.startsWith(MAIN_TEST_PACKAGES))
                    continue;

                // For each methods of the class.
                for (Method method : classToCheck.getDeclaredMethods()) {
                    Class<?>[] categoryClassToRegister = null;
                    boolean ignored = false;
                    for (Annotation annotation : method.getAnnotations()) {
                        if (annotation instanceof org.junit.experimental.categories.Category) {
                            categoryClassToRegister = ((org.junit.experimental.categories.Category) annotation).value();
                        } else if (annotation instanceof org.junit.Ignore) {
                            ignored = true;

                        } else {
                            // Ignore this annotation.
                            continue;
                        }
                    }

                    if (ignored) {
                        // If you want to compute count of ignored test.
                        registerTest(testByCategoryMap, "(Ignored Tests)");
                    } else if (categoryClassToRegister != null) {
                        for (Class<?> categoryClass : categoryClassToRegister) {
                            registerTest(testByCategoryMap, categoryClass.getCanonicalName());
                        }
                    }

                }

            }
            classLoader = classLoader.getParent();
        }

        System.out.println("\nFinal Statistics:");
        System.out.println("Count of Tests\t\tCategory");
        for (Entry<String, AtomicInteger> info : testByCategoryMap.entrySet()) {
            System.out.println("\t" + info.getValue() + "\t\t" + info.getKey());
        }

    }

}

您可以适应您的需求,尤其是我一开始创建的常量,以过滤要考虑的软件包。

然后,您无所事事。

例如,这是我很小的测试类:

package misc.category;

import org.junit.Test;
import org.junit.experimental.categories.Category;

public class UnitTestWithCategory {

    @Category({CategoryA.class, CategoryB.class})
    @Test
    public final void Test() {
        System.out.println("In Test 1");
    }

    @Category(CategoryA.class)
    @Test
    public final void Test2() {
        System.out.println("In Test 2");
    }

}

在这种情况下,输出为:

In Test 1
In Test 2

Final Statistics:
Count of Tests      Category
    1       misc.category.CategoryB
    2       misc.category.CategoryA

并带有包含@Ignore批注的测试用例:

package misc.category;

import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.categories.Category;

public class UnitTestWithCategory {

    @Category({CategoryA.class, CategoryB.class})
    @Test
    public final void Test() {
        System.out.println("In Test 1");
    }

    @Category(CategoryA.class)
    @Test
    public final void Test2() {
        System.out.println("In Test 2");
    }

    @Category(CategoryA.class)
    @Ignore
    @Test
    public final void Test3() {
        System.out.println("In Test 3");
    }   
}

您将获得输出:

In Test 1
In Test 2

Final Statistics:
Count of Tests      Category
    1       (Ignored Tests)
    1       misc.category.CategoryB
    2       misc.category.CategoryA

您可以根据需要轻松删除“(已忽略的测试)”注册,并且当然可以根据需要调整输出。

这个最终版本非常棒的一点是,它将处理已经真正加载/执行的测试类,因此您将获得关于已执行内容的真实统计信息,而不是像您这样的静态统计信息到目前为止。

静态“按类别进行测试”计算机

如果您希望像您所要求的那样与现有源代码无关,那么这是一种静态执行按类别进行测试计算的方法。

这是我为您写的StaticTestWithCategoryCounter

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicInteger;

public class StaticTestWithCategoryCounter {

    public static final String ROOT_DIR_TO_SCAN = "bin";
    public static final String MAIN_TEST_PACKAGES = "misc.category";

    private static final Class<?>[] getClasses(final ClassLoader classLoader)
            throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
        Class<?> CL_class = classLoader.getClass();
        while (CL_class != java.lang.ClassLoader.class) {
            CL_class = CL_class.getSuperclass();
        }
        java.lang.reflect.Field ClassLoader_classes_field = CL_class.getDeclaredField("classes");
        ClassLoader_classes_field.setAccessible(true);
        Vector<?> classVector = (Vector<?>) ClassLoader_classes_field.get(classLoader);

        Class<?>[] classes = new Class[classVector.size()]; // Creates an array to avoid concurrent modification
                                                            // exception.
        return classVector.toArray(classes);
    }

    // Registers the information.
    private static final void registerTest(Map<String, AtomicInteger> testByCategoryMap, String category) {
        AtomicInteger count;
        if (testByCategoryMap.containsKey(category)) {
            count = testByCategoryMap.get(category);
        } else {
            count = new AtomicInteger(0);
            testByCategoryMap.put(category, count);
        }

        count.incrementAndGet();
    }


    public static void computeCategoryCounters() throws Exception {
        Map<String, AtomicInteger> testByCategoryMap = new HashMap<>();

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        while (classLoader != null) {
            for (Class<?> classToCheck : getClasses(classLoader)) {
                String packageName = classToCheck.getPackage() != null ? classToCheck.getPackage().getName() : "";
                if (!packageName.startsWith(MAIN_TEST_PACKAGES))
                    continue;

                // For each methods of the class.
                for (Method method : classToCheck.getDeclaredMethods()) {
                    Class<?>[] categoryClassToRegister = null;
                    boolean ignored = false;
                    for (Annotation annotation : method.getAnnotations()) {
                        if (annotation instanceof org.junit.experimental.categories.Category) {
                            categoryClassToRegister = ((org.junit.experimental.categories.Category) annotation).value();
                        } else if (annotation instanceof org.junit.Ignore) {
                            ignored = true;

                        } else {
                            // Ignore this annotation.
                            continue;
                        }
                    }

                    if (ignored) {
                        // If you want to compute count of ignored test.
                        registerTest(testByCategoryMap, "(Ignored Tests)");
                    } else if (categoryClassToRegister != null) {
                        for (Class<?> categoryClass : categoryClassToRegister) {
                            registerTest(testByCategoryMap, categoryClass.getCanonicalName());
                        }
                    }

                }

            }
            classLoader = classLoader.getParent();
        }

        System.out.println("\nFinal Statistics:");
        System.out.println("Count of Tests\t\tCategory");
        for (Entry<String, AtomicInteger> info : testByCategoryMap.entrySet()) {
            System.out.println("\t" + info.getValue() + "\t\t" + info.getKey());
        }
    }

    public static List<String> listNameOfAvailableClasses(String rootDirectory, File directory, String packageName) throws ClassNotFoundException {
        List<String> classeNameList = new ArrayList<>();

        if (!directory.exists()) {
            return classeNameList;
        }

        File[] files = directory.listFiles();
        for (File file : files) {           

            if (file.isDirectory()) {
                if (file.getName().contains("."))
                    continue;

                classeNameList.addAll(listNameOfAvailableClasses(rootDirectory, file, packageName));
            } else if (file.getName().endsWith(".class")) {
                String qualifiedName = file.getPath().substring(rootDirectory.length() + 1);
                qualifiedName = qualifiedName.substring(0, qualifiedName.length() - 6).replaceAll(File.separator, ".");

                if (packageName ==null || qualifiedName.startsWith(packageName))
                    classeNameList.add(qualifiedName);
            }
        }

        return classeNameList;
    }

    public static List<Class<?>> loadAllAvailableClasses(String rootDirectory, String packageName) throws ClassNotFoundException {
        List<String> classeNameList = listNameOfAvailableClasses(rootDirectory, new File(rootDirectory), packageName);
        List<Class<?>> classes = new ArrayList<>();

        for (final String className: classeNameList) {
            classes.add(Class.forName(className));
        }

        return classes;
    }

    public static void main(String[] args) {
        try {           
            loadAllAvailableClasses(ROOT_DIR_TO_SCAN, MAIN_TEST_PACKAGES);
            computeCategoryCounters();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

您只需要在开始时修改两个常量即可指定:

  • (字节码)类在哪里
  • 您对哪个主软件包感兴趣(可以将其设置为null来考虑100%可用的软件包)

这个新版本的想法:

  • 列出所有与您的2个常量匹配的类文件
  • 加载所有对应的类
  • 使用未经修改的动态版本源代码(现在已经加载了类)

如果您需要更多信息,请告诉我。

答案 1 :(得分:0)

使用番石榴ClassPath,您可以执行以下操作:

首先加载类别:

private static List<Class<?>> getCategories(ClassPath classPath) {
  return classPath.getAllClasses()
      .stream()
      .filter(classInfo -> classInfo.getPackageName().startsWith(CATEGORIES_PACKAGE))
      .map(ClassPath.ClassInfo::load)
      .collect(Collectors.toList());
}

然后计数频率。

此方法从类别Map返回一个Class<?>到它的频率:

private static Map<Class<?>, Long> getCategoryFrequency(ClassPath classPath) {
  return classPath.getAllClasses()
    .stream()
    .filter(classInfo -> classInfo.getPackageName().startsWith(APPLICATION_PACKAGE))
    .map(ClassPath.ClassInfo::load)
    .map(Class::getMethods)
    .flatMap(Arrays::stream)
    .filter(method -> method.getAnnotation(Test.class) != null)// Only tests
    .filter(method -> method.getAnnotation(Ignore.class) == null) // Without @Ignore
    .map(method -> method.getAnnotation(Category.class))
    .filter(Objects::nonNull)
    .map(Category::value)
    .flatMap(Arrays::stream)
    .collect(groupingBy(Function.identity(), Collectors.counting()));
}

最后打印结果:

System.out.println("Category | Frequency");
for (Class<?> category : categories) {
  System.out.println(category.getSimpleName() + " | " + categoryFrequency.getOrDefault(category, 0L));
}

完整的课程清单:

public class CategoriesCounter {
  private static final String CATEGORIES_PACKAGE = "com.example.core.categories";
  private static final String APPLICATION_PACKAGE = "com.example.core";


  public static void main(String[] args) throws Throwable {
    ClassPath classPath = ClassPath.from(CategoriesCounter.class.getClassLoader());
    List<Class<?>> categories = getCategories(classPath);
    Map<Class<?>, Long> categoryFrequency = getCategoryFrequency(classPath);
    System.out.println("Category | Frequency");
    for (Class<?> category : categories) {
      System.out.println(category.getSimpleName() + " | " + categoryFrequency.getOrDefault(category, 0L));
    }
  }

  private static List<Class<?>> getCategories(ClassPath classPath) {
    return classPath.getAllClasses()
        .stream()
        .filter(classInfo -> classInfo.getPackageName().startsWith(CATEGORIES_PACKAGE))
        .map(ClassPath.ClassInfo::load)
        .collect(Collectors.toList());
  }

  private static Map<Class<?>, Long> getCategoryFrequency(ClassPath classPath) {
    return classPath.getAllClasses()
        .stream()
        .filter(classInfo -> classInfo.getPackageName().startsWith(APPLICATION_PACKAGE))
        .map(ClassPath.ClassInfo::load)
        .map(Class::getMethods)
        .flatMap(Arrays::stream)
        .filter(method -> method.getAnnotation(Test.class) != null)// Only tests
        .filter(method -> method.getAnnotation(Ignore.class) == null) // Without @Ignore
        .map(method -> method.getAnnotation(Category.class))
        .filter(Objects::nonNull)
        .map(Category::value)
        .flatMap(Arrays::stream)
        .collect(groupingBy(Function.identity(), Collectors.counting()));
  }
}

将这个测试类放在类路径中:

public class Test1 {
  @FastTest
  @Category(value = FastTest.class)
  @Test
  public void a() {
  }

  @FastTest
  @Category(value = FastTest.class)
  @Test
  public void d() {
  }

  @Category(value = SlowTest.class)
  @Test
  public void b() {
  }

  @Category(value = SlowTest.class)
  @Test
  @Ignore
  public void c() {
  }
}

CategoriesCounter产生:

Category | Frequency
SlowTest | 1
FastTest | 2