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模块的测试用例
}
总结
争做一个优秀的测试开发,加油!有疑问联系博主或者留言,欢迎大家有更好的想法和意见!