2 回答

TA貢獻(xiàn)1757條經(jīng)驗(yàn) 獲得超8個贊
動態(tài)“按類別測試”計(jì)算機(jī)
(推薦方法)
我嘗試了一種在抽象層中使用計(jì)數(shù)器執(zhí)行此操作的方法,但很痛苦,必須在每個測試方法的開頭添加源代碼。
最后,這是我為滿足您的需求而編寫的源代碼;它很重(反射......),但它對現(xiàn)有源代碼的干擾較小,并且完全滿足您的需求。
首先,您必須創(chuàng)建一個Testsuite(包含各種其他套件,或直接包含您想要的所有測試類),以確保最后您想要統(tǒng)計(jì)數(shù)據(jù)的所有測試都已加載。
在這個套件中,你必須實(shí)現(xiàn)一個“最終鉤子” @AfterClass,當(dāng)整個測試套件完全由JUnit管理時,它將被調(diào)用一次。
這是我為您編寫的測試套件實(shí)現(xiàn):
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());
}
}
}
你可以根據(jù)自己的需要,特別是我一開始創(chuàng)建的常量,來過濾包來考慮。
那么你就沒有比你已經(jīng)做的更多的事情了。
例如,這是我的小測試類:
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)”注冊,當(dāng)然還可以根據(jù)需要調(diào)整輸出。
這個最終版本的好處是,它將處理真正加載/執(zhí)行的測試類,因此您將獲得已執(zhí)行內(nèi)容的真實(shí)統(tǒng)計(jì)數(shù)據(jù),而不是像您目前獲得的靜態(tài)統(tǒng)計(jì)數(shù)據(jù).
靜態(tài)“按類別測試”計(jì)算機(jī)
如果您想像您問的那樣對現(xiàn)有源代碼無所事事,這是一種靜態(tài)執(zhí)行按類別計(jì)算的測試的方法。
這是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();
}
}
}
您只需要在開始時調(diào)整兩個常量即可指定:
(字節(jié)碼)類在哪里
您對哪個主包感興趣(您能否將其設(shè)置null為考慮 100% 可用包)
這個新版本的想法:
列出與您的 2 個常量匹配的所有類文件
加載所有對應(yīng)的類
使用未修改的動態(tài)版本源代碼(現(xiàn)在類已加載)
如果您需要更多信息,請告訴我。

TA貢獻(xiàn)1860條經(jīng)驗(yàn) 獲得超9個贊
使用番石榴,ClassPath您可以執(zhí)行以下操作:
首先加載類別:
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());
}
然后計(jì)算頻率。
這個方法返回一個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()));
}
最后打印結(jié)果:
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
添加回答
舉報(bào)