JUnit实现测试用例分类执行


前言

由于JUnit原生的测试套件Categories,仅仅支持原生已有的筛选规则去进行分类,不满足大多数项目中实际需要的分类场景,所以我们需要自己定义筛选规则。且使用Categories时,我们需要把每个测试类传入到指定的测试套件中,不能扫描所有的测试用例,使用起来相当不便,所以我们需要自己实现测试用例的搜索器。

1.我们需要自定义筛选规则
2.我们需要定义测试用例的搜索器

一、Categories是什么?如何使用?优点缺点?

我们可以先看一下GitHub上官方的介绍和案例,不了解的同学可以先看一下
https://github.com/junit-team/junit4/wiki/Categories

public interface FastTests {
    
     /* category marker */ }
public interface SlowTests {
    
     /* category marker */ }

public class A {
    
    
  @Test
  public void a() {
    
    
    fail();
  }

  @Category(SlowTests.class)
  @Test
  public void b() {
    
    
  }
}

@Category({
    
    SlowTests.class, FastTests.class})
public class B {
    
    
  @Test
  public void c() {
    
    

  }
}

@RunWith(Categories.class)
@IncludeCategory(SlowTests.class)
@SuiteClasses( {
    
     A.class, B.class }) // Note that Categories is a kind of Suite
public class SlowTestSuite {
    
    
  // Will run A.b and B.c, but not A.a
}

@RunWith(Categories.class)
@IncludeCategory(SlowTests.class)
@ExcludeCategory(FastTests.class)
@SuiteClasses( {
    
     A.class, B.class }) // Note that Categories is a kind of Suite
public class SlowTestSuite {
    
    
  // Will run A.b, but not A.a or B.c
}

简单而言就是三个步骤
1.定义需要过滤测试用例的注解SlowTests.class, FastTests.class 等等
2. 在测试用例上添加注解
3. 定义测试套件,并添加SuiteClasses

这样最终的效果就是执行A B 类中的加了注解的测试用例
但是原生的Categories很不满足我们的实际需求,存在两点问题:
1.只能是IncludeCategory或者ExcludeCategory的关系,筛选规则是固定的
2.必须通过SuiteClasses将测试用例的所在类添加进去,而且只能扫描已添加的测试类中的测试用例,这样不满足扫描所有测试用例的场景,非常不实用。

由于这两点诉求,我们可以自己去实现自定义的Categories

二、自定义测试套件

1.什么是Suite类

首先需要了解一下Suite类是用来干嘛的,简单一点就是用来选择哪些测试类去进行测试的。
Junit - 套件测试(Suite Test)

2.定义测试注解

(示例):

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Module {
    
    
    String module() default "please set right module again";

    int runOrder();
}

3.定义注解对应的过滤器(重写Filter)

Filter父类,需要先定义一个过滤规则filterRule,用来给其他注解扩展的

//Filter父类,需要先定义一个过滤规则filterRule
public abstract class MyFilter extends Filter {
    
    
    public abstract boolean filterRule();
}

实现自己的Filter,自己定义的Suite中的module的名字如果和测试用例的中加了Module注解的名字相同,则在shouldRun中返回True,则代表就是会运行的用例

public class FilterModule extends MyFilter {
    
    
    private String moudle_name;
    private Module module;

    public FilterModule(String moudle_name) {
    
    
        this.moudle_name = moudle_name;
    }

    @Override
    public boolean filterRule() {
    
    
        if (moudle_name != null && (module == null || !moudle_name.equalsIgnoreCase(module.module())))
            return false;
        return true;
    }

    @Override
    public boolean shouldRun(Description description) {
    
    
        if (description.isTest()) {
    
    
            module = description.getAnnotation(Module.class);
            return filterRule();
        }
        return true;
    }

    @Override
    public String describe() {
    
    
        return null;
    }
}

4.定义Finder类(用来查找所有测试用例)

此处涉及两个知识点
1.通过class文件加载对象
2.寻找安卓应用中DexFile的所在路径,然后通过DexFile去查找class文件

该类处理完后可以活得测试路径下,对应包含@Test注解的所有类,也就是所有测试类,返回 List<Class<?>>

public class TestCaseFinder {
    
    
    private List<Class<?>> testClasses = null;
    private String packageName;

    public TestCaseFinder(Context context, String packageName) {
    
    
        try {
    
    
            if (testClasses != null) {
    
    
                testClasses.clear();
            }
            this.packageName = packageName;
            testClasses = (ArrayList<Class<?>>) getClassesFromPkg(context, packageName);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    public List<Class<?>> getAllTestClasses() {
    
    
        List<Class<?>> alltestClass = new ArrayList<>();
        for (Class<?> class1 : testClasses) {
    
    
            Method[] methods = class1.getDeclaredMethods();
         	boolean isHasTestCase = false;
            for (Method method : methods) {
    
    
                if (isHasTestCase) {
    
    
                    break;
                }
                for (Annotation annotation : method.getDeclaredAnnotations()) {
    
    
                    if (annotation instanceof Test) {
    
    
                        alltestClass.add(class1);
                        isHasTestCase = true;
                        break;
                    }
                }
            }
        }
        return alltestClass;
    }

    private Iterable<Class<?>> getClassesFromPkg(Context context, String packageName) {
    
    
        String apkpath = context.getPackageCodePath();
        List<Class<?>> classes = new ArrayList<Class<?>>();
        DexFile dexFile = null;
        Enumeration<String> classString = null;
        String classname = null;
        //Log.i("TestCaseFinder getClassesFromPkg", packageName);
        try {
    
    
            dexFile = new DexFile(apkpath);
            classString = dexFile.entries();
            while (classString.hasMoreElements()) {
    
    
                classname = classString.nextElement();
                if (classname.startsWith(packageName)) {
    
    
                    classes.add(Class.forName(classname));
                    // Log.i("TestCaseFinder getClassesFromPkg classname", classname);
                }
            }
        } catch (IOException | ClassNotFoundException e) {
    
    
            e.printStackTrace();
        }
        return classes;
    }
}

5.定义Sutie类

该类就是用来运行哪些测试用例
重写此类注意三点:
1、定义一个注解用来处理测试用例注解的
2、重写构造方法,将Finder中找到的测试类传入super的构造方法中
3、构造方法中,将Filter传入重写的filter方法中

这里就实现了,扫描哪些类的测试和对应加了测试注解的测试用例,并可以通过过滤规则去进行筛选

public class MyCategory extends Suite {
    
    

    @Retention(RetentionPolicy.RUNTIME)
    public @interface ExcludeModule {
    
    
        String value();
    }

    public MyCategory (Class<?> suiteClass, RunnerBuilder builder) throws InitializationError {
    
    
        super(builder, suiteClass, getTestclasses(new TestCaseFinder(InstrumentationRegistry.getInstrumentation().getContext(),
               InstrumentationRegistry.getInstrumentation().getContext().getPackageName()).getAllTestClasses()));
        try {
    
    
            filter(new AnnotationsFilterBuilder(
                    getIncludedModule(suiteClass));
        } catch (NoTestsRemainException e) {
    
    
            throw new InitializationError(e);
        }
    }
    
    private String getExcludedModule(Class<?> classes) {
    
    
        ExcludeModule annotation = classes.getAnnotation(ExcludeModule.class);
        return annotation == null ? null : annotation.value();
    }
    
    private static Class<?>[] getTestclasses(List<Class<?>> testclasses) {
    
    
        return testclasses.toArray(new Class[testclasses.size()]);
    }
}

6.使用案例

测试用例所在类,包括可以定义一些其他规则的注解,根据自己实际需要等待,用例描述,用例的一些需要的参数,执行顺序,次数等等,都可以通过一部分的注解去解决


public class TestCaseExamples {
    
    

    @Test
    @Module(module = "test111", runOrder = 1)
    @CaseDescription(description = "test1234", beforeTest = "需要插入sim卡", limitTimes = 2)
    @Args({
    
    
            @Arg(key = "1", value = "1"),
            @Arg(key = "2", value = "@@2")
    })
    @Arg(key = "3", value = "3")
    public void test123() {
    
    
    }

    @Test
    @Module(module = "test1112", runOrder = 2)
    @CaseDescription(description = "test1234", beforeTest = "需要插入sim卡", limitTimes = 2)
    @Args({
    
    
            @Arg(key = "1", value = "1"),
            @Arg(key = "2", value = "@@2")
    })
    @Arg(key = "3", value = "3")
    public void test1234() {
    
    
    }

    @Test
    @Module(module = "test111", runOrder = 3)
    @CaseDescription(description = "test1234", beforeTest = "需要插入sim卡", limitTimes = 2)
    @Args({
    
    
            @Arg(key = "1", value = "1"),
            @Arg(key = "2", value = "@@2")
    })
    @Arg(key = "3", value = "3")
    public void test1235() {
    
    
    }

    @Test
    @Module(module = "test1112", runOrder = 4)
    @CaseDescription(description = "test1234", beforeTest = "需要插入sim卡", limitTimes = 2)
    @Args({
    
    
            @Arg(key = "1", value = "1"),
            @Arg(key = "2", value = "@@2")
    })
    public void test1236() {
    
    
    }

    @Test
    @Module(module = "test111", runOrder = 5)
    @CaseDescription(description = "test1234", beforeTest = "需要插入sim卡", limitTimes = 2)
    @Args({
    
    
            @Arg(key = "1", value = "1"),
            @Arg(key = "2", value = "@@2")
    })
    public void test1237() {
    
    
    }

    @Test
    @Module(module = "test1112", runOrder = 6)
    @CaseDescription(description = "test1234", beforeTest = "需要插入sim卡", limitTimes = 2)
    @Args({
    
    
            @Arg(key = "1", value = "1"),
            @Arg(key = "2", value = "@@2")
    })
    @Arg(key = "3", value = "3")
    public void test1238() {
    
    
    }

    @Module(module = "test111", runOrder = 7)
    @CaseDescription(description = "test1234", beforeTest = "需要插入sim卡", limitTimes = 2)
    @Args({
    
    
            @Arg(key = "1", value = "1"),
            @Arg(key = "2", value = "@@2")
    })
    @Arg(key = "3", value = "3")
    @Test
    public void test1239() {
    
    
    }

}

使用测试套件运行

@RunWith(MyCategory .class)
@MyCategory.ExcludeModule("test111")
public class CategoryTest {
    
    
//此处就会运行所有包下加了Module注解,且Module是test111模块的测试用例
}

总结

争做一个优秀的测试开发,加油!有疑问联系博主或者留言,欢迎大家有更好的想法和意见!

猜你喜欢

转载自blog.csdn.net/weixin_42233460/article/details/108442309
今日推荐