为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

慕容3067478 2021-11-24 15:18:13
我已经使用 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 无效,但没有大量使用标志并逐行读取每个文件为了它抛出正确的计数。有没有一种很好的方法来逐项列出@Ignore 中的@Category?示例输出| 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 回答

?
陪伴而非守候

TA贡献1757条经验 获得超8个赞

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

(推荐方法)


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


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


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


在这个套件中,你必须实现一个“最终钩子” @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

如果需要,您可以轻松删除“(Ignored Tests)”注册,当然还可以根据需要调整输出。


这个最终版本的好处是,它将处理真正加载/执行的测试类,因此您将获得已执行内容的真实统计数据,而不是像您目前获得的静态统计数据.


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

如果您想像您问的那样对现有源代码无所事事,这是一种静态执行按类别计算的测试的方法。


这是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 个常量匹配的所有类文件

加载所有对应的类

使用未修改的动态版本源代码(现在类已加载)

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


查看完整回答
反对 回复 2021-11-24
?
慕码人2483693

TA贡献1860条经验 获得超9个赞

使用番石榴,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());

}

然后计算频率。


这个方法返回一个Mapfrom 类别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


查看完整回答
反对 回复 2021-11-24
  • 2 回答
  • 0 关注
  • 120 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信