首页 > 其他分享 >Junit执行器Runner探索之旅

Junit执行器Runner探索之旅

时间:2023-06-13 14:13:36浏览次数:76  
标签:执行器 Runner void class 探索之旅 test Suite public Categories

单元测试是每个程序员必备的技能,而Runner是每个单元测试类必有属性。本文通过解读Junit源码,介绍junit中每个执行器的使用方法,让读者在单元测试时,可以灵活的使用Runner执行器。

一、背景

在今年的敏捷团队建设中,京东物流通过Suite执行器实现了一键自动化单元测试。Juint除了Suite执行器还有哪些执行器呢?由此京东物流的Runner探索之旅开始了!

二、RunWith

RunWith的注释是当一个类用@RunWith注释或扩展一个用@RunWith注释的类时,JUnit将调用它引用的类来运行该类中的测试,而不是内置到JUnit中的运行器,就是测试类根据指定运行方式进行运行。

代码如下:

public @interface RunWith {
    Class<? extends Runner> value();
}


其中:Runner 就是指定的运行方式。

三、Runner

Runner的作用是告诉Junit如何运行一个测试类,它是一个抽象类。通过RunWith 指定具体的实现类,如果不指定默认使用BlockJUnit4ClassRunner,Runner的代码如下:

public abstract class Runner implements Describable {
    public abstract Description getDescription();
    public abstract void run(RunNotifier notifier);
    public int testCount() {
        return getDescription().testCount();
    }
}


3.1 ParentRunner

ParentRunner是一个抽象类,提供了大多数特定于运行器的功能,是经常使用运行器的父节点。实现了Filterable,Sortable接口,可以过滤和排序子对象。

提供了3个抽象方法:

protected abstract List<T> getChildren();
protected abstract Description describeChild(T child);
protected abstract void runChild(T child, RunNotifier notifier);


3.1.1 BlockJUnit4ClassRunner

BlockJUnit4ClassRunner是Juint4默认的运行器,具有与旧的测试类运行器(JUnit4ClassRunner)完全相同的行为。

ParentRunner3个抽象方法的实现如下:

@Override
protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
    Description description = describeChild(method);
    if (isIgnored(method)) {
        notifier.fireTestIgnored(description);
    } else {
        runLeaf(methodBlock(method), description, notifier);
    }
}
@Override
protected Description describeChild(FrameworkMethod method) {
    Description description = methodDescriptions.get(method);


    if (description == null) {
        description = Description.createTestDescription(getTestClass().getJavaClass(),
                testName(method), method.getAnnotations());
        methodDescriptions.putIfAbsent(method, description);
    }


    return description;
}
@Override
protected List<FrameworkMethod> getChildren() {
    return computeTestMethods();
}


runChild() :

  • 调用describeChild()

  • 判断方法是否包含@Ignore注解,有就触发TestIgnored事件通知

  • 构造Statement回调,通过methodBlock()构造并装饰测试方法

  • 执行测试方法调用statement.evaluate()

describeChild() : 对测试方法创建Description并进行缓存

getChildren():返回运行测试的方法。 默认实现返回该类和超类上所有用@Test标注的未重写的方法

3.1.2 BlockJUnit4ClassRunnerWithParameters

BlockJUnit4ClassRunnerWithParameters是一个支持参数的BlockJUnit4ClassRunner。参数可以通过构造函数注入或注入到带注释的字段中。参数包含名称、测试类和一组参数。

private final Object[] parameters;
private final String name;
public BlockJUnit4ClassRunnerWithParameters(TestWithParameters test)
        throws InitializationError {
    super(test.getTestClass().getJavaClass());
    parameters = test.getParameters().toArray(
            new Object[test.getParameters().size()]);
    name = test.getName();
}


参数代码如下:

public class TestWithParameters {
    private final String name;
    private final TestClass testClass;
    private final List<Object> parameters;
    public TestWithParameters(String name, TestClass testClass,
            List<Object> parameters) {
        notNull(name, "The name is missing.");
        notNull(testClass, "The test class is missing.");
        notNull(parameters, "The parameters are missing.");
        this.name = name;
        this.testClass = testClass;
        this.parameters = unmodifiableList(new ArrayList<Object>(parameters));
    }


BlockJUnit4ClassRunnerWithParameters一般结合Parameterized使用

3.1.3 Theories

Theories允许对无限数据点集的子集测试某种功能。提供一组参数的排列组合值作为待测方法的输入参数。同时注意到在使用Theories这个Runner的时候,待测方法可以拥有输入参数,可以使您的测试更加灵活。

测试代码如下:

@RunWith(Theories.class)
public class TheoriesTest {
    @DataPoints
    public static String[] tables = {"方桌子", "圆桌子"};
    @DataPoints
    public static int[] counts = {4,6,8};
    @Theory
    public void testMethod(String table, int count){
        System.out.println(String.format("一套桌椅有一个%s和%d个椅子", table, count));
    }
}


运行结果:

图2 Theories测试代码的执行结果

3.1.4 JUnit4

JUnit4是Junit4默认执行器的别名,想要显式地将一个类标记为JUnit4类,应该使用@RunWith(JUnit4.class),而不是,使用@RunWith(BlockJUnit4ClassRunner.class)

3.1.5 Suite

Suite允许您手动构建包含来自许多类的测试的套件.通过Suite.SuiteClasses定义要执行的测试类,一键执行所有的测试类。

测试代码如下:

@RunWith(Suite.class)
@Suite.SuiteClasses({Suite_test_a.class,Suite_test_b.class,Suite_test_c.class })
public class Suite_main {
}
public class Suite_test_a {
    @Test
    public void testRun(){
        System.out.println("Suite_test_a_running");
    }
}
public class Suite_test_b {
    @Test
    public void testRun(){
        System.out.println("Suite_test_b_running");
    }
}
public class Suite_test_c {
    @Test
    public void testRun(){
        System.out.println("Suite_test_c_running");
    }
}


执行结果:

图3 Suite测试代码的执行结果

如结果所示:执行MainSuit时依次执行了Suite_test_a,Suite_test_b,Suite_test_c 的方法,实现了一键执行。

3.1.6 Categories

Categories在给定的一组测试类中,只运行用带有@ inclecategory标注的类别或该类别的子类型标注的类和方法。通过ExcludeCategory过滤类型。

测试代码如下:

public interface BlackCategory {}
public interface WhiteCategory {}


public class Categories_test_a {
    @Test
    @Category(BlackCategory.class)
    public void testFirst(){
        System.out.println("Categories_test_a_testFirst_running");
    }
    @Test
    @Category(WhiteCategory.class)
    public void testSecond(){
        System.out.println("Categories_test_a_testSecond_running");
    }
}


public class Categories_test_b {
    @Test
    @Category(WhiteCategory.class)
    public void testFirst(){
        System.out.println("Categories_test_b_testFirst_running");
    }
    @Test
    @Category(BlackCategory.class)
    public void testSecond(){
        System.out.println("Categories_test_b_testSecond_running");
    }
}


执行带WhiteCategory的方法

@RunWith(Categories.class)
@Categories.IncludeCategory(WhiteCategory.class)
@Categories.ExcludeCategory(BlackCategory.class)
@Suite.SuiteClasses( { Categories_test_a.class, Categories_test_b.class })
public class Categories_main {
}


运行结果:

图4 Categories测试代码WhiteCategory分组执行结果

执行带BlackCategory的方法

@RunWith(Categories.class)
@Categories.IncludeCategory(BlackCategory.class)
@Categories.ExcludeCategory(WhiteCategory.class)
@Suite.SuiteClasses( { Categories_test_a.class, Categories_test_b.class })
public class Categories_main {
}


运行结果:

图5 Categories测试代码BlackCategory分组执行结果

如运行结果所示,通过IncludeCategory,ExcludeCategory可以灵活的运行具体的测试类和方法。

3.1.7 Enclosed

Enclosed使用Enclosed运行外部类,内部类中的测试将被运行。 您可以将测试放在内部类中,以便对它们进行分组或共享常量。

测试代码:

public class EnclosedTest {
   @Test
    public  void runOutMethou(){
        System.out.println("EnclosedTest_runOutMethou_running");
    }
    public static class EnclosedInnerTest {
        @Test
       public  void runInMethou(){
            System.out.println("EnclosedInnerTest_runInMethou_running");
        }
    }
}


运行结果:没有执行内部类的测试方法。

图6 Enclosed测试代码的执行结果

使用Enclosed执行器:

@RunWith(Enclosed.class)
public class EnclosedTest {
   @Test
    public  void runOutMethou(){
        System.out.println("EnclosedTest_runOutMethou_running");
    }
    public static class EnclosedInnerTest {
        @Test
       public  void runInMethou(){
            System.out.println("EnclosedInnerTest_runInMethou_running");
        }
    }
}


执行结果:执行了内部类的测试方法。

图7 Enclosed测试代码的执行结果

3.1.8 Parameterized

Parameterized实现参数化测试。 运行参数化的测试类时,会为测试方法和测试数据元素的交叉乘积创建实例。

Parameterized包含一个提供数据的方法,这个方法必须增加Parameters注解,并且这个方法必
须是静态static的,并且返回一个集合Collection,Collection中的值长度必须相等。

测试代码:

@RunWith(Parameterized.class)
public class ParameterizedTest {
    @Parameterized.Parameters
    public static Collection<Object[]> initData(){
       return Arrays.asList(new Object[][]{
               {"小白",1,"鸡腿"},{"小黑",2,"面包"},{"小红",1,"苹果"}
       });
    }
    private String name;
    private int  count;
    private String food;


    public ParameterizedTest(String name, int count, String food) {
        this.name = name;
        this.count = count;
        this.food = food;
    }
    @Test
    public void eated(){
       System.out.println(String.format("%s中午吃了%d个%s",name,count,food));
    }
}


运行结果:

图8 Parameterized测试代码的执行结果

3.2 JUnit38ClassRunner

JUnit38ClassRunner及其子类是Junit4的内部运行器,有一个内部类OldTestClassAdaptingListener

实现了TestListener接口。

3.3 ErrorReportingRunner

ErrorReportingRunner也是Junit4运行错误时抛出的异常,代码如下:

private final List<Throwable> causes;


public ErrorReportingRunner(Class<?> testClass, Throwable cause) {
    if (testClass == null) {
        throw new NullPointerException("Test class cannot be null");
    }
    this.testClass = testClass;
    causes = getCauses(cause);
}


    private List<Throwable> getCauses(Throwable cause) {
        if (cause instanceof InvocationTargetException) {
            return getCauses(cause.getCause());
        }
        if (cause instanceof InitializationError) {
            return ((InitializationError) cause).getCauses();
        }
        if (cause instanceof org.junit.internal.runners.InitializationError) {
            return ((org.junit.internal.runners.InitializationError) cause)
                    .getCauses();
        }
        return Arrays.asList(cause);
    }


当junit运行错误时,会抛出ErrorReportingRunner,例如:

public Runner getRunner() {
    try {
        Runner runner = request.getRunner();
        fFilter.apply(runner);
        return runner;
    } catch (NoTestsRemainException e) {
        return new ErrorReportingRunner(Filter.class, new Exception(String
                .format("No tests found matching %s from %s", fFilter
                        .describe(), request.toString())));
    }
}


3.4 IgnoredClassRunner

IgnoredClassRunner是当测试的方法包含Ignore注解时,会忽略该方法。

public class IgnoredClassRunner extends Runner {
    private final Class<?> clazz;
    public IgnoredClassRunner(Class<?> testClass) {
        clazz = testClass;
    }
    @Override
    public void run(RunNotifier notifier) {
        notifier.fireTestIgnored(getDescription());
    }
    @Override
    public Description getDescription() {
        return Description.createSuiteDescription(clazz);
    }
}


IgnoredClassRunner的使用

public class IgnoredBuilder extends RunnerBuilder {
    @Override
    public Runner runnerForClass(Class<?> testClass) {
        if (testClass.getAnnotation(Ignore.class) != null) {
            return new IgnoredClassRunner(testClass);
        }
        return null;
    }
}


当测试时想忽略某些方法时,可以通过继承IgnoredClassRunner增加特定注解实现。

四、小结

Runner探索之旅结束了,可是单元测试之路才刚刚开始。不同的Runner组合,让单元测试更加灵活,测试场景更加丰富,更好的实现了测试驱动开发,让系统更加牢固可靠。

作者:京东物流 陈昌浩

来源:京东云开发者社区

标签:执行器,Runner,void,class,探索之旅,test,Suite,public,Categories
From: https://www.cnblogs.com/Jcloud/p/17477330.html

相关文章

  • 如何在 Postman Runner 中运用参数自增提升接口测试效率
    什么是PostmanRunner?PostmanRunner是PostmanPostmanRunner如何实现参数自增?步骤一:设置全局参数在使用PostmanRunner运行测试脚本时,我们需要设置一个全局参数counter。接下来,我们将会通过脚本自动将这个参数进行自增。1、点击右上角的眼睛图标,找到Globals列的Edit按......
  • LoadRunner+Fiddler性能测试(避免脚本无法生成问题)
    在使用LoadRunner做性能测试的时候,无论配置是否有问题,经常出现一种情况那就是录制之后没有脚本生成解决这个问题的方法在之前也写过,但最近发现了可以一劳永逸的方法,那就是LoadRunner+Fiddler用fiddler对于网站运行情况进行抓包,然后再拿到loadrunner分析首先fidder上官网安装,免......
  • GitlabCI学习笔记之五:GitLabRunner pipeline语法之artifacts dependencies
    artifacts用于指定在作业成功或者失败时应附加到作业的文件或目录的列表。作业完成后,工件将被发送到GitLab,并可在GitLabUI中下载。artifacts:paths路径是相对于项目目录的,不能直接链接到项目目录之外。将制品设置为target目录artifacts:paths:-target/禁用工件......
  • GitlabCI学习笔记之五:GitLabRunner pipeline语法之cache
    cache缓存用来指定需要在job之间缓存的文件或目录。只能使用该项目工作空间内的路径。不要使用缓存在阶段之间传递工件,因为缓存旨在存储编译项目所需的运行时依赖项。如果在job范围之外定义了cache ,则意味着它是全局设置,所有job都将使用该定义。如果未全局定义或未按job定义则......
  • 性能测试-第01天-从理解到体验,最后能够对比说出Loadrunner和JMeter的优缺点
    更多功能测试以及全套学习路线图均在专栏,↑↑戳进去领取~系列文章目录......
  • 软测5班Loadrunner阶段性考试(2019-10-19)
    试题1:用你在Loadrunner中所学习的知识,将“欢迎来到然学科技”保存为一个变量,并且在日志中打印输出(10分)。答案:lr_save_string("欢迎来到科技","ranther");lr_output_message("你好:%s",lr_eval_string("{ran}"));试题2:Loadrunner中如何保持每次参数取值的唯一性(2分)?Unique+Once(保持......
  • Loadrunner考试题
    Loadrunner考试题试题1:用你在Loadrunner中所学习的知识,将“欢迎来到XXX科技”保存为一个变量,并且在日志中打印输出(10分)。答案:lr_save_string("欢迎来到XXX科技","ranther");lr_output_message("你好:%s",lr_eval_string("{ranther}"));试题2:Loadrunner中如何保持每次参数取值的唯一......
  • Loadrunner考核试卷分析
    XXX科技学院试卷20  /20  学年   第 学期        课程所属部门:                  课程名称:                        课程编号:               考试方式:(A、B、开、闭)卷使用班级:         ......
  • 在win2016服务器上安装Loadrunner 11版本
    背景:在自己的本机上安装LR12,去录制和调试脚本。在win2016(win2013、win2008都可以)上安装LR11并且破解。则可以做到使用Loadrunner工具进行压测。 在2年的压测过程中,装了2次LR11,记录一下过程,容易踩坑的是安装.net的补丁包!详细步骤:安装LR11  遇到问题(一般新申请的机器......
  • MyBatis之执行器(Excutor)
    MyBatis之执行器(Executor)前面的文章已经介绍了MyBatis的基本使用流程,但我们不能仅仅局限于使用,而是应该去横向扩展,往更深的方向研究。今天就先讲解一下MyBatis中的执行器Executor。我们在CURD的时候,每次都会去获取一个新的SqlSession对象,可以看出来这个接口主要定义类关于CRUD......