写点什么

Junit4 Rules 使用

用户头像
hungxy
关注
发布于: 2021 年 01 月 08 日
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 是否为空。接下来写两个用例验证一下

@Testpublic void successTest() {    messageLog.add("I love java");}
@Testpublic 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 的区别)

@ClassRulepublic 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 RuleStarting:Second RuleStarting:Third Rulerun caseFinishing:Third RuleFinishing:Second RuleFinishing: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(); }
}
复制代码


参考: <a href="https://www.baeldung.com/junit-4-rules">https://www.baeldung.com/junit-4-rules</a>


发布于: 2021 年 01 月 08 日阅读数: 25
用户头像

hungxy

关注

还未添加个人签名 2018.07.19 加入

还未添加个人简介

评论

发布
暂无评论
Junit4 Rules 使用