JUnit的Rule的使用

Posted henryyao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUnit的Rule的使用相关的知识,希望对你有一定的参考价值。

我们在使用JUnit的时候,为了使测试结果更加清晰明确,会有以下的需求:

  • 一个类有多个测试方法,想知道日志是哪个测试方法

  • 想在所有测试方法的前后加上一些语句,用于初始化和销毁一些资源

  • 如果一个测试方法可能抛出多个异常,想知道运行时到底抛出哪些异常

  • ...

以上的需求可以通过为每个方法加上相似的语句来实现,但这样未免有点麻烦和冗余,好在JUnit4为我们提供了一劳永逸的方法,使用Rule。

Rule简介

Rule是JUnit4.7加入的新特性,有点类似于拦截器,用于在测试方法执行前后添加额外的处理。实际上是@Before,@After的另一种实现。使用时需要放在实现了TestRule的成员变量上或者返回TestRule的方法上,且修饰符为public。Rule会应用于该类每个测试方法。

内置Rule

JUnit提供了很多内置的TestRule的实现满足日常使用,具体有如下:
  • Verifier:所有测试结束后对测试执行结果添加额外的逻辑验证测试最终成功与否。该抽象类为子类提供一个接口方法verify()供扩展

        String result = "success";
        /**
         * Verifier:用于在测试方法执行结束后,进行一些结果校验
         */
        @Rule
        public Verifier verifier = new Verifier() {
            @Override
            protected void verify() throws Throwable {
                if (result.equals("fail")) {
                    throw new Exception("failed");
                }
            }
        };
    
        @Test
        public void verifierTest() {
            result = "fail";
        }

    运行结果:

    java.lang.Exception: failed
    ...
  • ErrorCollector:是Verifier类的一个子类实现,用于在测试执行过程中收集错误信息,不会中断测试,最后调用verify()方法处理。

        /**
         * ErrorCollector:可以收集多个多个异常,并在方法结束后一起打印出来
         */
        @Rule
        public ErrorCollector errorCollector = new ErrorCollector();
    
        @Test
        public void errorCollectorTest() {
            errorCollector.addError(new RuntimeException("error 1"));
            System.out.println("==================================");
            errorCollector.addError(new RuntimeException("error 2"));
        }

    运行结果:

    ==================================
    java.lang.RuntimeException: error 1
    ...
    java.lang.RuntimeException: error 2
    ...
  • TemporaryFolder:是抽象类ExternalResource的一个子类实现,用于在JUnit测试执行前后,创建和删除临时目录

        /**
    
         * TemporaryFolder:创建临时目录/文件,测试方法执行结束后自动删除
         * 可以在构造方法中传入使用的父目录,否则默认使用系统临时目录
         */
        @Rule
        public TemporaryFolder temporaryFolder = new TemporaryFolder(new File("C:\\Users\\test"));
  @Test
  public void temporaryFolderTest() throws IOException, InterruptedException {
      temporaryFolder.newFolder("test");
      temporaryFolder.newFile("hello.txt");
      Thread.sleep(10 * 1000);

  }

```

  • TestName:是抽象类TestWatcher的一个子类实现,用于在测试执行过程中获取测试方法名称。在starting()中记录测试方法名,在getMethodName()中返回

        /**
    
         * TestName:获取当前测试方法的方法名
         */
        @Rule
        public TestName testName = new TestName();
    
        @Test
        public void testNameTest() {
            System.out.println("method is " + testName.getMethodName());
        }

运行结果:

  method is testNameTest
  • TestWatcher:监视测试方法生命周期的各个阶段。该抽象类为子类提供了五个接口方法succeeded(), failed(), skipped(), starting()及finished()供扩展

        /**
    
         * TestWatcher:在测试方法开始,结束,成功,失败,跳过这些时间点调用相应方法
         */
        @Rule
        public TestWatcher testWatcher = new TestWatcher() {
            @Override
            protected void succeeded(Description description) {
                System.out.println("success");
            }
    
            @Override
            protected void failed(Throwable e, Description description) {
                System.out.println("failed");
            }
      @Override
      protected void starting(Description description) {
          System.out.println("starting");
      }

      @Override
      protected void finished(Description description) {
          System.out.println("finished");
      }

      @Override
      protected void skipped(AssumptionViolatedException e, Description description) {
          System.out.println("skipped");
      }
  };

  @Test
  public void testWatcherTest() {
      System.out.println("testWatcher");
  }

  运行结果:

starting

testWatcher

success
finished

```

  • Timeout:与@Test中的timeout相对应,@Test只能修饰待测试方法,Timeout可以修饰待测试类

        /**
    
         * Timeout:超时时间,方法运行超时则抛出TestTimedOutException异常
         */
        @Rule
        public Timeout timeout = new Timeout(5, TimeUnit.SECONDS);
        @Test
        public void timeoutTest() throws InterruptedException {
            System.out.println("timeout");
            Thread.sleep(6*1000);
        }

运行结果:

  org.junit.runners.model.TestTimedOutException: test timed out after 5 seconds
  ...
  • ExpectedException:与@Test中的expected相对应,提供更强大灵活的异常验证功能,@Test只能修饰待测试方法,ExpectedException可以修饰待测试类

        /**
         * ExpectedException:指定测试方法出现的异常,未出现或者出现其他类型的异常测试不通过
         */
        @Rule
        public ExpectedException expectedException=ExpectedException.none();
    
        @Test
        public void expectedExceptionTest(){
            expectedException.expect(NullPointerException.class);
    
            throw new RuntimeException();
        }

    运行结果:

    java.lang.AssertionError: 
    Expected: an instance of java.lang.NullPointerException
         but:  is a java.lang.RuntimeException

自定义Rule

当内置的Rule无法满足你的需求的时候,你还可以通过实现TestRule来自定义Rule。
  • 自定义RuleTest实现类

    /**
    
     * 自己实现TestRule
     */
    public class MyTestRule implements TestRule {
        /**
         * @param base 基础行为,要进行封装的行为
    
         * @param description test方法的描述,包括方法名,类名等
         * @return 可以使在基础行为上添加新动作,也可以是一个全新的动作
         */
        @Override
        public Statement apply(Statement base, Description description) {
            return new Statement() {
                @Override
                public void evaluate() throws Throwable {
                    String name = description.getMethodName();
                    System.out.println("开始调用" + name);
                    base.evaluate();
                    System.out.println("结束调用" + name);
                }
            };
        }
    }

    使用自定义的Rlue类:

      @Rule
        public MyTestRule myTestRule=new MyTestRule();
    
        @Test
        public void myTestRuleTest(){
            System.out.println("myTestRuleTest");
        }

    运行结果:

    开始调用myTestRuleTest
    myTestRuleTest
    结束调用myTestRuleTest
  • 使用匿名类自定义Rule

        @Rule
    
        public TestRule myRule(){
            TestRule rule = (base, description) -> new Statement() {
                @Override
                public void evaluate() throws Throwable {
                    String name = description.getMethodName();
                    System.out.println("start invoke " + name);
    
                    base.evaluate();
                    System.out.println("finished invoke " + name);
    
                }
            };
            return rule;
        }
        @Test
        public void myRuleTest(){
            System.out.println("myRule");
        }

    运行结果:

    start invoke myRuleTest
    
    myRule
    finished invoke myRuleTest

以上是关于JUnit的Rule的使用的主要内容,如果未能解决你的问题,请参考以下文章

如何在 JUnit 5 中替换 WireMock @Rule 注释?

JUnit的Rule的使用

Junit @Rule 和 @ClassRule

使用java.lang.Exception的错误:测试类应该只有一个公共构造函数

为啥 JUnit 中的 @Rule 注释字段必须是公开的?

如何将 JUnit TemporaryFolder @Rule 与 Spring @Value 属性一起使用?