junit mockito
JUnit 5是用于用Java开发单元测试的新的事实上的标准。 这个最新版本摆脱了Java 5的限制,并集成了Java 8的许多功能,其中最著名的是对lambda表达式的支持。
在由两部分组成的JUnit 5简介的前半部分中,您将开始使用JUnit 5进行测试。我将向您展示如何配置Maven项目以使用JUnit 5,如何使用@Test
和@ParameterizedTest
批注以及如何在JUnit 5中使用新的生命周期批注。您还将看到一个使用过滤器标签的简短示例,并且我将向您展示如何将JUnit 5与第三方断言库集成在一起。这种情况,哈姆克雷斯特。 最后,您将获得一个快速的教程介绍,以将JUnit 5与Mockito集成在一起,以便您可以为复杂的实际系统编写更强大的单元测试。
如果您已经开发Java代码已有一段时间,那么您可能对测试驱动的开发非常熟悉,因此我将在本节中保持简短。 重要的是要理解为什么我们编写单元测试,以及开发人员在设计单元测试时采用的策略。
测试驱动开发(TDD)是将编码,测试和设计交织在一起的软件开发过程。 这是一种测试优先的方法,旨在提高您的应用程序的质量。 测试驱动的开发由以下生命周期定义:
图1显示了该TDD生命周期。
史蒂文·海恩斯图1.测试驱动的开发生命周期
在编写代码之前编写测试有两个目的。 首先,它迫使您考虑要解决的业务问题。 例如,成功方案应如何表现? 什么条件应该失败? 他们应该如何失败? 其次,测试首先使您对测试更有信心。 每当我在编写代码之后编写测试时,我总是必须破坏它们以确保它们确实捕获了错误。 首先编写测试可以避免此额外步骤。
为幸福的道路编写测试通常很容易:给定好的输入,该类应返回确定性的响应。 但是编写负面(或失败)的测试用例,尤其是对于复杂的组件,可能会更加复杂。
例如,考虑为数据库存储库编写测试。 在快乐的道路上,我们将一条记录插入数据库,并接收回创建的对象,包括任何生成的键。 实际上,我们还必须考虑发生冲突的可能性,例如插入一条记录,该记录具有另一个记录已经拥有的唯一列值。 此外,当存储库无法连接到数据库(可能是因为用户名或密码已更改)时,会发生什么? 如果在传输过程中出现网络错误怎么办? 如果请求未在您定义的超时限制内完成,该怎么办?
为了构建一个健壮的组件,您需要考虑所有可能和不太可能的场景,为它们开发测试,并编写代码来满足这些测试。 在本文的后面,我们将研究创建不同故障场景的策略,以及JUnit 5中的一些新功能,这些功能可以帮助您测试这些场景。
如果您已经使用JUnit一段时间,则JUnit 5中的一些更改将是一种调整。 以下是这两个版本之间的区别的简要概述:
org.junit.jupiter
组中,该组更改了将其包含在Maven和Gradle项目中的方式。 @Before
, @BeforeClass
, @After
和@AfterClass
批注已被替换@BeforeEach
, @BeforeAll
, @AfterEach
和@AfterAll
,分别。 @Ignore
注解已被取代的@Disabled
注释。 @Category
注释已被@Tag
注释替换。 我们将在本文中探索大多数这些新功能。
让我们从一个简单的端到端示例开始,配置一个项目以使用JUnit 5进行单元测试。 清单1显示了MathTools
类,该类的方法将分子和分母转换为double
。
package com.javaworld.geekcap.math;
public class MathTools {
public static double convertToDecimal(int numerator, int denominator) {
if (denominator == 0) {
throw new IllegalArgumentException("Denominator must not be 0");
}
return (double)numerator / (double)denominator;
}
}
我们有两种主要的场景来测试MathTools
类及其方法:
清单2显示了一个JUnit 5测试类来测试这两种情况。
package com.javaworld.geekcap.math;
import java.lang.IllegalArgumentException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class MathToolsTest {
@Test
void testConvertToDecimalSuccess() {
double result = MathTools.convertToDecimal(3, 4);
Assertions.assertEquals(0.75, result);
}
@Test
void testConvertToDecimalInvalidDenominator() {
Assertions.assertThrows(IllegalArgumentException.class, () -> MathTools.convertToDecimal(3, 0));
}
}
在清单2中, testConvertToDecimalInvalidDenominator
方法在assertThrows
调用内执行MathTools::convertToDecimal
方法。 第一个参数是要引发的异常的预期类型。 第二个参数是将引发该异常的函数。 assertThrows
方法执行该函数并验证是否抛出了预期的异常类型。
org.junit.jupiter.api.Test
注释表示一种测试方法。 请注意,@ @Test
注释现在来自JUnit 5 Jupiter API包,而不是JUnit 4的org.junit
包。 testConvertToDecimalSuccess
方法首先执行分子为3且分母为4的MathTools::convertToDecimal
方法,然后断言结果等于0.75。 org.junit.jupiter.api.Assertions
类提供了一组用于比较实际结果和预期结果的static
方法。 Assertions
类具有以下方法,这些方法涵盖了大多数原始数据类型:
assertArrayEquals
将实际数组的内容与预期数组进行比较。 assertEquals
将实际值与期望值进行比较。 assertNotEquals
比较两个值以验证它们不相等。 assertTrue
验证提供的值为true。 assertFalse
验证提供的值为false。 assertLinesMatch
比较两个String
列表。 assertNull
验证提供的值为null。 assertNotNull
验证提供的值不为null。 assertSame
验证两个值引用相同的对象。 assertNotSame
验证两个值未引用同一对象。 assertThrows
验证方法的执行将引发预期的异常(您可以在上面的testConvertToDecimalInvalidDenominator
示例中看到此异常)。 assertTimeout
验证提供的函数在指定的超时内完成。 assertTimeoutPreemptively
验证提供的函数是否在指定的超时内完成,但是一旦达到超时, assertTimeoutPreemptively
该函数的执行。 如果这些声明方法中的任何一个失败,则将单元测试标记为失败。 运行测试时,该故障通知将写入屏幕,然后保存在报告文件中。
在assertEquals
使用float
和double
值时,您还可以指定一个代表两者之间差异阈值的delta
。 在我们的示例中,我们可以添加0.001的增量,以防0.75实际上返回为0.750001。
assert
方法除了验证值或行为外,还可以接受错误的文本描述,这可以帮助您诊断故障。 例如:
Assertions.assertEquals(0.75, result, "The MathTools::convertToDecimal value did not return the correct value of 0.75 for 3/4");
Assertions.assertEquals(0.75, result, () -> "The MathTools::convertToDecimal value did not return the correct value of 0.75 for 3/4");
输出将显示期望值0.75和实际值。 它还将显示指定的消息,这可以帮助您了解错误的上下文。 两种变体之间的区别是,第一个变体始终创建消息,即使未显示该消息,而第二个变体仅在断言失败时构造消息。 在这种情况下,消息的构造是微不足道的,因此并不重要。 尽管如此,仍不需要为通过的测试构造错误消息,因此通常最好的方法是使用第二种样式。
最后,如果您使用的是像IntelliJ这样的IDE来运行测试,则每个测试方法将按其方法名称显示。 如果您的方法名称可读,那么可以,但是您也可以在测试方法中添加@DisplayName
批注,以更好地识别测试:
@Test
@DisplayName("Test successful decimal conversion")
void testConvertToDecimalSuccess() {
double result = MathTools.convertToDecimal(3, 4);
Assertions.assertEquals(0.751, result);
}
为了从Maven项目运行JUnit 5测试,您需要在Maven pom.xml
文件中包括maven-surefire-plugin
并添加新的依赖项。 清单3显示了此项目的pom.xml
文件。
4.0.0
com.javaworld.geekcap
junit5
jar
1.0-SNAPSHOT
org.apache.maven.plugins
maven-compiler-plugin
3.8.1
8
org.apache.maven.plugins
maven-surefire-plugin
3.0.0-M4
junit5
http://maven.apache.org
org.junit.jupiter
junit-jupiter
5.6.0
test
JUnit 5将其组件打包在org.junit.jupiter
组中,我们需要添加junit-jupiter
工件,这是一个聚合器工件,它会导入以下依赖项:
junit-jupiter-api
定义用于编写测试和扩展的API。 junit-jupiter-engine
是运行单元测试的测试引擎实现。 junit-jupiter-params
为参数化测试提供支持。 接下来,我们需要添加maven-surefire-plugin
构建插件来运行测试。
最后,确保将maven-compiler-plugin
包含在Java 8或更高版本中,以便您能够使用Java 8功能(例如lambda)。
使用以下命令从您的IDE或Maven运行测试类:
mvn clean test
如果成功,则应该看到类似以下的输出:
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.javaworld.geekcap.math.MathToolsTest
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.04 s - in com.javaworld.geekcap.math.MathToolsTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.832 s
[INFO] Finished at: 2020-02-16T08:21:15-05:00
[INFO] ------------------------------------------------------------------------
翻译自: https://www.infoworld.com/article/3537563/junit-5-tutorial-part-1-unit-testing-with-junit-5-mockito-and-hamcrest.html
junit mockito