规则在测试,测试用例或测试套件周围增加了特殊处理。 他们可以对类中的所有测试执行通用的其他验证,并发运行多个测试实例,在每个测试或测试用例之前设置资源,然后将其拆除。
该规则可以完全控制将要应用到的测试方法,测试用例或测试套件。 完全控制意味着规则决定运行它之前和之后要做什么以及如何处理引发的异常。
第一章介绍了如何使用规则,第二章介绍了内置规则可以做什么。 第三章介绍了我发现的第三方规则库,最后一章介绍了如何创建新规则。
使用规则
本章说明如何在测试用例中声明和使用规则。 大多数规则可以分别应用于每种测试方法,一次应用于整个测试用例,一次应用于整个测试套件。 为每个测试单独运行的规则称为测试规则,应用于整个测试用例或套件的规则称为类规则。
我们将以临时文件夹规则为例,因此第一个子章节将说明它的作用。 第二小节将其声明为测试规则,第三小节将其声明为类规则。 最后一个子章节显示了如何从测试内部访问该文件夹。
规则示例–临时文件夹
临时文件夹规则创建一个新的空文件夹,运行测试或测试用例,然后删除该文件夹。 您可以指定在哪里创建新文件夹,也可以在系统临时文件目录中创建它。
临时文件夹既可以用作测试规则,也可以用作类规则。
声明测试规则
测试规则(例如,针对每种测试方法分别运行的规则)必须在带有@Rule
注释的公共字段中声明。
声明测试规则:
public class SomeTestCase {@Rulepublic TemporaryFolder folder = new TemporaryFolder();
}
上面的folder
规则会在每种测试方法之前创建一个新文件夹,然后将其销毁。 所有测试都可以使用该目录,但不能通过该目录共享文件。 由于我们使用的构造函数没有参数,因此该文件夹将在系统临时文件目录中创建。
测试规则在使用@Before
注释的方法之前和之后使用@After
注释的方法之前进行工作。 因此,他们也将有权访问临时文件夹。
宣布班级规则
类规则(例如,针对整个测试用例或测试套件运行一次的规则)必须在公共静态字段中声明,并使用@ClassRule
注释进行注释。
声明测试用例规则:
public class SomeTestCase {@ClassRulepublic static TemporaryFolder folder = new TemporaryFolder();
}
上面的folder
规则在运行第一个测试方法之前创建一个新文件夹,并在最后一个测试方法之后销毁它。 所有测试都可以使用该目录,并且可以查看以前运行的测试所创建的文件。
类规则在该类内部的任何内容之前运行。 例如,用@BeforeClass
或@AfterClass
注释的方法也可以访问临时文件夹。 该规则在它们之前和之后运行。
在测试中使用规则
规则与其他任何规则一样都是类,测试可以自由调用其公共方法并使用其公共字段。 这些调用用于将测试特定的配置添加到规则或从中读取数据。
例如,可以使用newFile
, newFolder
或getRoot
方法访问临时文件夹。 前两个在临时文件夹中创建新文件或文件夹,而getRoot
方法返回临时文件夹本身。
创建临时文件和文件夹:
@Test
public void test1() {// Create new folder inside temporary directory. Depending on how you // declared the folder rule, the directory will be deleted either // right after this test or when the last test in test case finishes.File file = folder.newFolder("folder");
}@Test
public void test2() {// Create new file inside temporary folder. Depending on how you // declared the folder rule, the file will be deleted either // right after this test or when the last test in test case finishes.File file = folder.newFile("file.png");
}
默认规则
JUnit带有五个直接可用的规则 :临时文件夹,预期的异常,超时,错误收集器和测试名称。 临时文件夹已在上一章中进行了说明,因此我们将仅简要介绍其余四个规则。
预期异常
预期异常将运行测试并捕获其引发的所有异常。 该规则能够检查异常是否包含正确的消息,正确的原因以及是否由正确的行抛出。
预期的异常具有私有构造函数,必须使用静态none
方法进行初始化。 每个异常引发测试都必须配置预期的异常参数,然后调用规则的expect
方法。 该规则在以下情况下失败:
- 测试在
expect
方法调用之前引发任何异常, - 在
expect
方法调用之后,测试不会引发异常, - 引发的异常没有正确的消息,类或原因。
最后一条测试行引发异常。 在导致异常之前立即配置了预期的异常规则:
@Rule
public ExpectedException thrown= ExpectedException.none();@Test
public void testException() {// Any exception thrown here causes failuredoTheStuff();// From now on, the rule expects NullPointerException exception// to be thrown. If the test finishes without exception or if it // throws wrong one, the rule will fail.thrown.expect(NullPointerException.class);// We well check also messagethrown.expectMessage("Expected Message.");// this line is supposed to throw exceptiontheCodeThatThrowsTheException();
}
奖励:期望的消息方法也接受hamcrest匹配器参数。 这样,您就可以测试消息前缀后缀,无论它是否与某些正则表达式匹配或与其他任何正则表达式匹配。
超时
超时规则可以同时用作测试规则和类规则。 如果将其声明为测试规则,则它将相同的超时限制应用于该类中的每个测试。 如果将其声明为类规则,则它将超时限制应用于整个测试用例或测试套件。
错误收集器
通过错误收集器,您可以在测试内部运行多个检查,然后在测试结束后立即报告所有失败。
期望值与实际值的断言使用规则公开的checkThat
方法进行评估。 它接受hamcrest匹配器作为参数,因此可以用来检查任何内容。
可以使用addError(Throwable error)
方法直接报告意外异常。 或者,如果有要运行的Callable
实例,则可以通过checkSucceeds
方法调用它, checkSucceeds
方法将所有抛出的异常添加到错误列表中。
测试名称
测试名称规则在测试内部公开测试名称。 当您需要创建自定义错误报告时,它可能会很有用。
第三方规则库
规则与测试类分离,因此很容易编写通用规则库并在项目之间共享它们。 本章介绍了三个这样的库。
系统规则是用于测试使用java.lang.System的代码的规则集合。 它有充分的文档证明,可在maven中使用,并根据Common Public License 1.0(与jUnit相同)发布。 系统规则使您可以轻松:
- 测试
System.err
和System.out
内容, - 模拟
System.in
输入, - 配置系统属性并将其值还原回
- 测试
System.exit()
调用–是否被调用以及返回的值是什么, - 自定义Java
SecurityManager
并将其还原。
一个大集的有用的规则是可以在GitHub上aisrael帐户。 它的文档有所限制,但是您可以随时查看代码 。 所有规则均根据MIT许可发布:
- 启动和停止内存中的derby数据库 ,
- 启动和停止默认的Java HttpServer ,
- 启动和停止Jetty服务器,
- 运行存根jndi ,
- 对dbUnit测试的一些支持。
github上另一套未公开的规则。 我不会在这里列出它们,因为它们的名称是不言自明的,并且它们没有指定的许可证。 查看规则目录以查看其列表。
自订规则
本章介绍如何创建新规则。 可以通过实现TestRule
接口或扩展TestRule
提供的两个便捷类ExternalResource
和Verifier
之一来从头实现它们。
我们将从头开始创建一个新规则,然后使用ExternalResource
类重写它。
新规则
新规则可确保在每个测试完成工作后,正确删除由测试创建的所有文件。 测试本身仅具有一项责任:使用规则公开的ensureRemoval(file)
方法报告所有新文件。
如何声明和使用DeleteFilesRule
规则:
@Rule
public DeleteFilesRule toDelete = new DeleteFilesRule();@Test
public void example() throws IOException {// output.css will be deleted whether the test passes, fails or throws an exceptiontoDelete.ensureRemoval("output.css");// the compiler is configured to create output.css filecompileFile("input.less");checkCorrectess("output.css");
}
从头开始
每个规则(包括类规则)必须实现@TestRule
接口。 该接口只有一种方法:
public interface TestRule {Statement apply(Statement base, Description description);
}
我们的工作是获取base
参数中提供的语句,然后将其转换为另一个语句。 该语句表示一组要运行的动作,例如测试,测试用例或测试套件。 它可能已经被其他声明的规则修改,并且包括在测试或类方法之前和之后。
第二个description
参数描述输入语句。 它可以告诉测试类名称,测试名称,放置在其上的注释,它知道我们是在处理测试还是在测试套件等。我们将不需要它。
我们需要创建一个新的语句,它将执行三件事:
- 清空要删除的文件列表。
- 运行底层测试,测试案例或测试套件由所表示的
base
参数。 - 删除先前运行的语句中测试报告的所有文件。
该语句是具有一个抽象方法的类:
public abstract class Statement {public abstract void evaluate() throws Throwable;
}
由于基础语句可能会引发异常,因此删除所有文件的代码必须在finally块中运行:
public class DeleteFilesRule implements TestRule {public Statement apply(final Statement base, final Description description) {return new Statement() {@Overridepublic void evaluate() throws Throwable {emptyFilesList(); // clean the list of filestry {base.evaluate(); // run underlying statement} finally {removeAll(); // delete all new files}}};}
}
引用的两个方法emptyFilesList
和removeAll
都在new语句外部,直接在DeleteFilesRule
类内部声明:
public class DeleteFilesRule implements TestRule {private List<File> toDelete;private void emptyFilesList() {toDelete = new ArrayList<File>();}private void removeAll() {for (File file : toDelete) {if (file.exists())file.delete();}}/* ... the apply method ... */
}
我们需要的最后一件事是能够添加要删除的文件的公共方法:
public void ensureRemoval(String... filenames) {for (String filename : filenames) {toDelete.add(new File(filename));}
}
全班
public class DeleteFilesRule implements TestRule {private List<File> toDelete;public void ensureRemoval(String... filenames) {for (String filename : filenames) {toDelete.add(new File(filename));}}private void emptyFilesList() {toDelete = new ArrayList<File>();}private void removeAll() {for (File file : toDelete) {if (file.exists())file.delete();}}public Statement apply(final Statement base, final Description description) {return new Statement() {@Overridepublic void evaluate() throws Throwable {emptyFilesList(); // clean the list of filestry {base.evaluate(); // run underlying statement} finally {removeAll(); // delete all new files}}};}
}
扩展内置类
JUnit包含两个便捷类ExternalResource
和Verifier
旨在进一步简化上述过程。
外部资源
当您需要围绕基础测试语句进行某种预处理和后处理时, ExternalResource
帮助。 如果需要预处理,请覆盖before
方法。 如果需要后处理,请覆盖after
方法。 的
从finally块调用after
,无论如何它将运行。
我们的DeleteFilesRule
可以这样重写:
public class DeleteFilesRule2 extends ExternalResource {/* ... list, ensureRemoval and removeAll methods ... */@Overrideprotected void before() throws Throwable {toDelete = new ArrayList<File>();}@Overrideprotected void after() {removeAll();}}
验证者
Verifier
者只有一种verify
要覆盖的方法。 只有在包装测试未引发异常后,该方法才会运行。 顾名思义,如果您想在测试后进行其他检查,则验证器是不错的选择。
有关jUnit的更多信息
关于jUnit 4功能的上一篇文章:
- jUnit:动态测试生成
翻译自: https://www.javacodegeeks.com/2014/09/junit-rules-2.html