上周,我们设置了JUnit 5以能够编写测试。 让我们开始吧!
总览
这篇文章是有关JUnit 5的系列文章的一部分:
- 设定
- 基本
- 建筑
- 条件
- 注射
- …
在新兴的《 JUnit 5用户指南》中可以找到您将在此处阅读的更多内容以及更多内容。 请注意,它基于Alpha版本,因此可能会发生变化。
确实,我们鼓励我们提出问题或提出请求,以便JUnit 5可以进一步改进。 请利用这个机会! 这是我们帮助JUnit帮助我们的机会,因此,如果您能在这里看到一些改善,请确保将其上游 。
如有必要,此帖子将得到更新。 我在这里显示的代码示例可以在GitHub上找到 。
哲学
我们将在另一篇文章中讨论的新体系结构旨在扩展性。 将来有可能使用JUnit 5进行非常陌生的测试技术(至少对我们熟悉的Java开发人员而言)。
但是到目前为止,其基础知识与当前版本4非常相似。JUnit5的表面经过了有意识的逐步改进,开发人员应该有宾至如归的感觉。 至少我愿意,我想你也会:
基本生命周期和功能
class Lifecycle {
@BeforeAll
static void initializeExternalResources() {
System.out.println("Initializing external resources...");
}
@BeforeEach
void initializeMockObjects() {
System.out.println("Initializing mock objects...");
}
@Test
void someTest() {
System.out.println("Running some test...");
assertTrue(true);
}
@Test
void otherTest() {
assumeTrue(true);
System.out.println("Running another test...");
assertNotEquals(1, 42, "Why wouldn't these be the same?");
}
@Test
@Disabled
void disabledTest() {
System.exit(1);
}
@AfterEach
void tearDown() {
System.out.println("Tearing down...");
}
@AfterAll
static void freeExternalResources() {
System.out.println("Freeing external resources...");
}
}
看到? 没什么大惊喜。
JUnit 5的基础
能见度
最明显的变化是测试类和方法不再必须公开。 包可见性足以满足要求,而私有可见性则无法满足。 我认为这是一个明智的选择,符合我们理解不同可见性修饰符的方式。
大! 我想说的是,输入的字母更少,但是您还是没有手动进行操作,对吗? 滚动测试类时要忽略的样板更少。
测试生命周期
@测试
最基本的JUnit批注是@Test
,它标记要作为测试运行的方法。
尽管不再使用可选参数,但实际上没有改变。 现在可以通过断言来验证预期的异常,但是据我所知,还没有替代超时的方法 。
JUnit 5为每个测试方法创建一个新的测试实例(与JUnit 4相同)。
之前和之后
您可能需要运行代码来设置和拆除测试。 有四个方法注释可帮助您实现此目的:
-
@BeforeAll
:执行一次; 在标有@BeforeEach的测试和方法之前运行。 -
@BeforeEach
:在每次测试之前执行。 -
@AfterEach
:在每次测试后执行。 -
@AfterAll
:执行一次; 在标有@AfterEach的所有测试和方法之后运行。
因为为每个测试都创建了一个新实例,所以没有明显的实例可以在其上调用@ BeforeAll
/ @AfterAll
方法,因此它们必须是静态的。
带有相同注释的不同方法的执行顺序是不确定的。 据我所知,继承方法也是如此。 当前正在讨论是否应该定义订单。
除了名称外,这些批注的工作方式与JUnit 4中的完全相同。尽管并不 罕见 ,但我并不相信这些名称。 有关详细信息,请参见此问题 。
禁用测试
今天是星期五下午,您只想回家? 没问题,只需在测试@Disabled
打@Disabled
(可以选择给出一个原因)并运行。
禁用测试
@Test
@Disabled("Y U No Pass?!")
void failingTest() {
assertTrue(false);
}
测试课程生命周期
与原型相比,有趣的是注意到测试类的生命周期没有进入alpha版本。 它将在测试类的同一实例上运行所有测试,从而允许测试通过改变状态相互交互。
正如我在讨论原型时所写的那样:我认为这是功能的典型案例,在99%的案例中有害,而在另外1%的案例中必不可少。 考虑到可怕的测试间依赖的真正风险,我想将它以原始形式删除是一件好事。
但是JUnit团队正在讨论使用其他名称和添加的语义将其重新引入。 这将使其使用非常刻意。 你怎么看?
断言
如果@Test
, @Before...
,和@After...
是一个测试套件的骨架,断言是它的心脏。 在准备好要测试的实例并在其上执行要测试的功能之后,断言可确保所需的属性成立。 如果没有,则它们将无法通过运行测试。
经典
经典断言要么检查单个实例的属性(例如,它不为null),要么进行某种比较(例如,两个实例相等)。 在这两种情况下,它们都可以选择将消息作为最后一个参数,当断言失败时显示该消息。 如果构造消息很昂贵,则可以将其指定为lambda表达式,因此构造将延迟到实际需要消息之前。
经典断言
@Test
void assertWithBoolean() {
assertTrue(true);
assertTrue(this::truism);
assertFalse(false, () -> "Really " + "expensive " + "message" + ".");
}
boolean truism() {
return true;
}
@Test
void assertWithComparison() {
List<String> expected = asList("element");
List<String> actual = new LinkedList<>(expected);
assertEquals(expected, actual);
assertEquals(expected, actual, "Should be equal.");
assertEquals(expected, actual, () -> "Should " + "be " + "equal.");
assertNotSame(expected, actual, "Obviously not the same instance.");
}
如您所见,JUnit 5在这里没有太大变化。 名称与以前相同,比较断言仍然采用一对期望值和实际值(按此顺序)。
期望-实际顺序对于理解测试的失败消息和意图至关重要,但是很容易混淆,这是一个大盲点。 但是,除了创建一个新的断言框架之外,没有什么可做的了。 考虑到像Hamcrest ( ugh !)或AssertJ (yeah!)这样的大公司,这不是投资有限时间的明智方法。 因此,目标是保持声明的重点和精力。
新的是失败消息排在最后。 我喜欢它,因为它始终关注着球,即所主张的财产。 作为对Java 8的致敬,布尔断言现在接受了provider ,这是一个很好的细节。
扩展的
除了检查特定属性的经典断言之外,还有其他一些断言。
第一个甚至不是真正的断言,它只是通过失败消息使测试失败。
'失败'
@Test
void failTheTest() {
fail("epicly");
}
然后,我们有了assertAll
,它接受可变数量的断言并在报告失败(如果有的话)之前测试所有这些断言。
'assertAll'
@Test
void assertAllProperties() {
Address address = new Address("New City", "Some Street", "No");
assertAll("address",
() -> assertEquals("Neustadt", address.city),
() -> assertEquals("Irgendeinestraße", address.street),
() -> assertEquals("Nr", address.number)
);
}
“ AssertAll”的失败消息
org.opentest4j.MultipleFailuresError: address (3 failures)
expected: <Neustadt> but was: <New City>
expected: <Irgendeinestraße> but was: <Some Street>
expected: <Nr> but was: <No>
检查多个相关属性并获取所有相关属性的值非常好,这与常规行为相反,在常规行为中,测试报告第一个失败的属性,而您永远不知道其他值。
最后,我们有了assertThrows
和expectThrows
。 如果给定的方法没有引发指定的异常,则两者都将通过测试。 后者还返回异常,因此可以将其用于进一步的验证,例如断言该消息包含某些信息。
“ assertThrows”和“ excpectThrows”
@Test
void assertExceptions() {
assertThrows(Exception.class, this::throwing);
Exception exception = expectThrows(Exception.class, this::throwing);
assertEquals("Because I can!", exception.getMessage());
}
假设条件
假设只允许在某些条件符合预期的情况下运行测试。 这可以用来减少测试套件的运行时间和冗长,尤其是在失败的情况下。
'assumeTrue','assumeFalse'和'assumingThat'
@Test
void exitIfFalseIsTrue() {
assumeTrue(false);
System.exit(1);
}
@Test
void exitIfTrueIsFalse() {
assumeFalse(this::truism);
System.exit(1);
}
private boolean truism() {
return true;
}
@Test
void exitIfNullEqualsString() {
assumingThat(
"null".equals(null),
() -> System.exit(1)
);
}
假设可以用于中止不满足先决条件的测试,或者仅在条件成立时才执行(部分测试)测试。 主要区别在于,中止的测试报告为已禁用,而由于条件不成立而为空的测试为纯绿色。
嵌套测试
JUnit 5使嵌套测试类变得毫不费力。 只需使用@Nested
注释内部类, @Nested
所有测试方法也将被执行:
'@嵌套'
package org.codefx.demo.junit5;// NOT_PUBLISHED
import org.junit.gen5.api.BeforeEach;
import org.junit.gen5.api.Nested;
import org.junit.gen5.api.Test;
import static org.junit.gen5.api.Assertions.assertEquals;
import static org.junit.gen5.api.Assertions.assertTrue;
class Nest {
int count = Integer.MIN_VALUE;
@BeforeEach
void setCountToZero() {
count = 0;
}
@Test
void countIsZero() {
assertEquals(0, count);
}
@Nested
class CountGreaterZero {
@BeforeEach
void increaseCount() {
count++;
}
@Test
void countIsGreaterZero() {
assertTrue(count > 0);
}
@Nested
class CountMuchGreaterZero {
@BeforeEach
void increaseCount() {
count += Integer.MAX_VALUE / 2;
}
@Test
void countIsLarge() {
assertTrue(count > Integer.MAX_VALUE / 2);
}
}
}
}
如您所见, @BeforeEach
(和@AfterEach
)也在这里工作。 尽管当前没有记录,但是初始化是从外而内执行的。 这允许为内部测试逐步构建上下文。
为了使嵌套测试能够访问外部测试类的字段,嵌套类不得为静态。 不幸的是,这禁止使用静态方法,因此在这种情况下不能使用@BeforeAll
和@AfterAll
。 ( 或者可以吗? )
也许您在问自己这有什么用。 我使用嵌套的测试类继承接口测试 ,而其他人则使他们的测试类小型化和集中化 。 JUnit团队通常会给出更复杂的示例来演示后者,该示例测试堆栈:
使用嵌套类测试堆栈
aclass TestingAStack {
Stack<Object> stack;
boolean isRun = false;
@Test
void isInstantiatedWithNew() {
new Stack<Object>();
}
@Nested
class WhenNew {
@BeforeEach
void init() {
stack = new Stack<Object>();
}
// some tests on 'stack', which is empty
@Nested
class AfterPushing {
String anElement = "an element";
@BeforeEach
void init() {
stack.push(anElement);
}
// some tests on 'stack', which has one element...
}
}
}
在此示例中,状态被连续更改,并且每种情况下都要执行许多测试。
命名测试
JUnit 5带有一个@DisplayName
注释,它使开发人员可以为他们的测试类和方法提供更易于@DisplayName
名称。
使用它,堆栈示例如下所示:
@DisplayName("A stack")
class TestingAStack {
@Test
@DisplayName("is instantiated with new Stack()")
void isInstantiatedWithNew() { /*...*/ }
@Nested
@DisplayName("when new")
class WhenNew {
@Test
@DisplayName("is empty")
void isEmpty() { /*...*/ }
@Test
@DisplayName("throws EmptyStackException when popped")
void throwsExceptionWhenPopped() { /*...*/ }
@Test
@DisplayName("throws EmptyStackException when peeked")
void throwsExceptionWhenPeeked() { /*...*/ }
@Nested
@DisplayName("after pushing an element")
class AfterPushing {
@Test
@DisplayName("it is no longer empty")
void isEmpty() { /*...*/ }
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() { /*...*/ }
@Test
@DisplayName(
"returns the element when peeked but remains not empty")
void returnElementWhenPeeked(){ /*...*/ }
}
}
}
这产生很好的可读的输出,并应带来欢乐的心脏BDD “呃!
反射
就是这样,您做到了! 我们快速了解了如何使用JUnit 5的基础知识,现在您知道编写简单测试所需的全部知识:如何注释生命周期方法(使用@[Before|After][All|Each]
)和测试方法本身( @Test
),如何嵌套( @Nested
)和名称( @DisplayName
)测试,以及断言和假设如何工作(与之前类似)。
但是,等等,还有更多! 我们还没有谈论测试方法的条件执行,非常酷的参数注入,扩展机制或项目的体系结构。 而且我们现在不会,因为我们将从JUnit 5短暂休息一下,然后在大约一个月内恢复到原来的状态。
敬请关注!
翻译自: https://www.javacodegeeks.com/2016/03/junit-5-basics.html