Junit4 Rules 使用

Junit 4 Rules 介绍

一个 Junit Rule(规则) 就是一个实现了org.junit.rules.TestRule 接口的类,这些类的作用类似于@Before @After注解,通过在类的执行前后运行指定的代码逻辑来增强测试,此外,Junit Rule还能做一些@Before @After注解实现不了的功能,如动态获取测试类、测试方法的信息

假设我们的测试用例需要在运行的时候连接外部资源(如数据库),在测试结束的时候释放连接,如果我们想在多个测试类中使用该数据库,那么最终将在每个测试类中重复该代码

通过使用 Rule,我们可以将重复的逻辑隔离在一个地方,并方便的在多个测试类中重用该代码

Rule 使用

引入 maven 依赖

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

Rule 使用遵循以下几个步骤

  • 在测试类中添加一个 public 的字段,该字段类型需是org.junit.rules.TestRule接口的实现类
  • 使用@Rule注解标记该字段

junit4 内置规则介绍

Junit 默认提供了很多 Rule 供开发者使用,下面介绍几种常见的 Rule

TemporaryFolder Rule

测试的时候,我们经常需要创建一个临时文件/文件夹,但是在测试类中管理这些文件/文件夹有时候会很麻烦,这时使用 TemporaryFolderRule 就能很好的帮我们管理测试过程中的文件的创建与删除:

public class TemporaryFolderRuleTest {
	@Rule
	public TemporaryFolder tmpFolder = new TemporaryFolder();
	 
	@Test
	public void test() throws IOException {
	    File testFile = tmpFolder.newFile("test-file.txt");
	 
	    assertTrue("The file should have been created: ", testFile.isFile());
	    assertEquals("Temp folder and test file should match: ", 
	      tmpFolder.getRoot(), testFile.getParentFile());
	}
	
}

在代码中,首先在测试类中定义了TemporaryFolder规则,然后通过tmpFolder.newFile("test-file.txt")方法,创建了一个临时文件test-file.txt,当测试执行完的时候,TemporaryFolder会自动删除掉临时文件跟文件夹(但不校验删除是否成功)

从 junit 4.13 开始,TemporaryFolder支持配置校验是否删除成功,删除失败则抛出AssertionError异常
@Rule public TemporaryFolder folder = TemporaryFolder.builder().assureDeletion().build();

TemporaryFolder除了newFile(String fileName)方法外,还有几个常用的方法:

  • newFile()
    创建一个随机名称的文件
  • newFolder(String... folderNames)
    根据多级目录文件夹,如 newFolder("a","b") 创建文件夹 a/b
  • newFolder()
    创建一个随机名称的文件夹

ExpectedException Rule

ExpectedException 很好理解,通过名称就能大致猜出它是用来校验异常的,ExpectedException 可以用来校验代码即将发生的异常:

public class ExpectedExceptionRuleTest {
	@Rule
	public ExpectedException thrown = ExpectedException.none();
	 
	@Test
	public void test() {
	    thrown.expect(IllegalArgumentException.class);
	    thrown.expectCause(isA(NullPointerException.class));
	    thrown.expectMessage("This is illegal");
	 
	    throw new IllegalArgumentException("This is illegal", new NullPointerException());
	}
	
}

在测试方法中,我们断言了接下来的代码会抛出IllegalArgumentException,且caseNullPointerExceptionmessageThis is illegal

TestName Rule

TestName 可以很方便的让我们获取当前执行的测试方法的名称:

public class TestNameRuleTest {
	@Rule 
	public TestName name = new TestName();
	
	@Test
	public void test() {
	    System.out.println("method name = " + name.getMethodName());
	    assertEquals("test", name.getMethodName());
	}
	
}

执行输出:

method name = test

Timeout Rule

对于添加了 Timeout Rule 的测试类,当测试类中的测试方法执行超过 Timeout Rule 配置的时间时,测试方法执行就会被标记为失败:

public class TimeoutRuleTest {
	@Rule
	public Timeout globalTimeout = Timeout.seconds(5);
	 
	@Test
	public void timeout() throws InterruptedException {
	    TimeUnit.SECONDS.sleep(10);
	}

	@Test
	public void onTime() throws InterruptedException {
		TimeUnit.SECONDS.sleep(2);
	}
	
}

执行上面测试用例,onTime方法执行通过,timeout()方法则抛出TestTimedOutException

org.junit.runners.model.TestTimedOutException: test timed out after 5 seconds

ErrorCollector Rule

顾名思义,ErrorCollector Rule 就是用来收集 Error 的,正常情况下,当测试方法抛异常的时候,方法执行会中断。使用 ErrorCollector 可以把异常收集起来,让测试用例继续执行,待执行用例跑完之后再将异常信息报告出来

public class ErrorCollectorRuleTest {

    @Rule
    public final ErrorCollector errorCollector = new ErrorCollector();

    @Test
    public void test() {
        errorCollector.addError(new Throwable("First thing went wrong!"));
        errorCollector.addError(new Throwable("Second thing went wrong!"));
        errorCollector.checkThat("Hello World", not(containsString("ERROR!")));
        System.out.println("run finished");
    }

}

执行 test 方法程序输出如下:

run finished

java.lang.Throwable: First thing went wrong!
...
java.lang.Throwable: Second thing went wrong!
...
Process finished with exit code 255

从执行结果可以看出,程序先打印 run finished 再输出错误信息。说明 Errorcollector 暂时先讲错误信息给收集起来,待测试用例执行完再将报错信息抛出,整个测试用例的执行结果为 fail

Verifier Rule

Verifier 是一个抽象类,当我们需要在测试用例中验证一些额外的行为的时候,可以使用这个类并重写该类的 verify 方法。事实上,有些 Rule 就是通过继承 Verifier 类实现的
下面定义一个 Verifier

public class VerifierRuleTest {

    private List messageLog = new ArrayList<>();

    @Rule
    public Verifier verifier = new Verifier() {
        @Override
        protected void verify() throws Throwable {
            assertFalse("Message Log is not Empty!", messageLog.isEmpty());
        }
    };
    
}

Verifier 做的事情很简单,就是在每个测试用例执行完的时候额外验证一下 messageLog 是否为空。接下来写两个用例验证一下

@Test
public void successTest() {
    messageLog.add("I love java");
}

@Test
public void failTest() {
    System.out.println("I don't like java");
}

执行两个用例,第一个用例成功,但是第二个用例报错如下:

java.lang.AssertionError: Message Log is not Empty!

DisableOnDebug Rule

有时候我们希望在 debug 的时候 disable 掉某些 Rule,比如我们的测试代码中定义了一个Timeout Rule,但 debug 的时候往往会超出 Timeout Rule 设定的超时时间,这时候就可以使用 DisableOnDebug RuleTimeout Rule 给 disable 掉,这样当我们通过 debug 模式执行测试用例时,Timeout Rule 就不会生效

public class DisableOnDebugRuleTest {

    @Rule
    public DisableOnDebug disableTimeout = new DisableOnDebug(Timeout.seconds(5));

    @Test
    public void test() throws InterruptedException {
        TimeUnit.SECONDS.sleep(10);
    }

}

当使用 debug 模式执行测试用例的时候,用例能通过。直接 run 测试用例的时候会失败,并报TestTimedOutException

ExternalResource Rule

当做集成测试的时候,有可能需要在执行前准备一些数据(可以是一个文件/数据库连接),并在执行完将准备的数据进行删除。这时候就可以用 ExternalResource Rule 实现,前面所讲的 TemporaryFolder Rule 正式通过集成 ExternalResource 实现的,详情可以参考 TemporaryFolder 源码

@ClassRule 使用

上面介绍的都是使用 @Rule 注解,@Rule 作用于测试方法级别的。如果想让 Rule 作用于 class 级别,可以使用 @ClassRule 注解(类似于@Before、@After跟@BeforeClass、@AfterClass的区别)

@ClassRule
public TemporaryFolder tmpFolder = new TemporaryFolder();

自定义 Junit Rule

除了使用 junit 提供的默认 Rule 外,还可以自定义我们自己的 Rule, 自定义 Rule 需要继承 TestRule 接口,并实现其 apply 方法

public class CustomerRuleTest {

    @Rule
    public TestMethodNameLogger testMethodNameLogger = new TestMethodNameLogger();

    @Test
    public void test() {
        System.out.println("run case");
    }

    static class TestMethodNameLogger implements TestRule {

        @Override
        public Statement apply(Statement statement, Description description) {
            return new Statement() {
                @Override
                public void evaluate() throws Throwable {
                    System.out.println("before test,class name is " + description.getClassName());
                    statement.evaluate();
                    System.out.println("after test,class name is " + description.getClassName());
                }
            };

        }
    }

}

上面的例子,我们自定义了一个 TestMethodNameLogger RuleRule 的作用是在执行测试方法执行前后打印出 log
apply 方法中的 Statement 参数表示测试方法执行过程中的 runtime,当调用 statement.evaluate() 方法时,则执行测试方法中的代码
Description 参数可以获取到当前正在执行的测试用例的一些信息(类名,方法名等)

Rule 链

当有多个 Rule 的时候,可以通过 RuleChain 来控制多个 Rule 的执行顺序,类似于过滤器链(FilterChain)

public class RuleChainsTest {

    @Rule
    public RuleChain ruleChain = RuleChain.outerRule(new SimpleMsgLogger("First Rule"))
            .around(new SimpleMsgLogger("Second Rule"))
            .around(new SimpleMsgLogger("Third Rule"));

    @Test
    public void test() {
        System.out.println("run case");
    }

    static class SimpleMsgLogger implements TestRule {

        private String log;

        public SimpleMsgLogger(String log) {
            this.log = log;
        }

        @Override
        public Statement apply(Statement base, Description description) {
            return new Statement() {
                @Override
                public void evaluate() throws Throwable {
                    System.out.println("Starting:" + log);
                    base.evaluate();
                    System.out.println("Finishing:" + log);
                }
            };
        }
    }

}

执行结果如下:

Starting:First Rule
Starting:Second Rule
Starting:Third Rule
run case
Finishing:Third Rule
Finishing:Second Rule
Finishing:First Rule

Rule 使用场景示例

写过 spring 项目的都知道,当我们想要在测试类中使用 spring 容器,需要在测试类上添加 @RunWith(SpringRunner.class) 注解,但此时如果你又想用 junit 的参数化测试的话,就会有冲突产生,因为 junit 参数化测试需要在测试类上添加 @RunWith(Parameterized.class) 注解
我们知道,一个测试类不能同时添加两个 @RunWith 注解,对于这种情况,就可以使用 junit Rule 解决
可以使用 @SpringClassRule @SpringMethodRule 这两个注解来替代 @RunWith(SpringRunner.class) 启动 spring 容器

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = WiremockApplication.class)
public class RuleTest {
    @ClassRule
    public static SpringClassRule springClassRule = new SpringClassRule();
    @Rule
    public SpringMethodRule springMethodRule = new SpringMethodRule();
    @Autowired
    public SampleController sampleController;

    @Test
    public void test() {
        sampleController.hello();
    }

}

示例代码地址: https://github.com/HuangXiaoyu3331/test-study/tree/main/src/test/java/com/hxy/junit/rule
参考: https://www.baeldung.com/junit-4-rules

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值