质量工程流程(Quality Engineering Process)
- Quality Plan
- 质量目标(Quality goal)
- 质量策略 (Quality Strategy)
- Quality Assurance
- 校验 & 确认(Verification & Validation)
- 质量度量(Quality Metrics)
- 质量标准(Quality Standards)
- 配置管理(Configuration Management)
- 文档管理(Documentation Management)
- Post-QA
- 测量(Measurement)
- 分析(Analysis)
- 回访(Feedback)
- 跟进(Follow-Up)
单元测试(Unit Testing)
- The sooner we detect a bug in the code, the faster and cheaper it is to fix
- JUnit is a testing framework which allows to create automated tests. The development of JUnit was started by Kent Beck and Erich Gamma in late 1995.
Junit3
- JUnit3 is open source software, released under Common Public License (CPL) Version 1.0 and hosted on SourceForge (sourceforge.net/projects/ju…). The latest version of JUnit 3 was JUnit 3.8.2, released on May 14, 2007
Junit4
-
JUnit 4 is still an open source framework, though the license changed with respect to JUnit 3, from CPL to Eclipse Public License (EPL) Version 1.0. The source code of JUnit 4 is hosted on GitHub (github.com/junit-team/…). On February 18, 2006, JUnit 4.0 was released. It follows the same high-level guidelines than JUnit 3, that is, easily define test, the framework run tests independently, and the framework detects and report errors by the test.
-
One of the main differences of JUnit 4 with respect to JUnit 3 is the way that JUnit 4 allows to define tests. In JUnit 4, Java annotations are used to mark methods as tests. For this reason, JUnit 4 can only be used for Java 5 or later.
-
In JUnit 4, a test runner is a Java class used to manage a test’s life cycle: instantiation, calling setup and teardown methods, running the test, handling exceptions, sending notifications, and so on. The default JUnit 4 test runner is called BlockJUnit4ClassRunner, and it implements the JUnit 4 standard test case class model.
-
One of the most significant innovations introduced in JUnit 4 was the use of rules. Rules allow flexible addition or redefinition of the behavior of each test method in a test class. A rule should be included in a test case by annotating a class attribute with the annotation @Rule.
- JUnit 4 Framework Limitations
- monolithic
- only use a single runner at a time.
- The main inconvenience when using JUnit 4 rules for complex tests is that we are not able to use a single rule entity for method-level and class-level.
Junit3 vs Junit4
JUnit 5 Framework Design Principles
-
模块化Modularization: JUnit 4 was not modular, and this causes some problems. From its inception, JUnit 5 architecture is much completely modular, allowing developers to use the specific parts of the framework they require.
-
强大的扩展模型(Powerful extension model with focus on composability): Extensibility is a must for modern testing frameworks. Therefore, JUnit 5 should provide seamless integration (无缝集成) with third-party frameworks, such as Spring or Mockito, to name a few.
-
API隔离(API segregation): Decouple test discovery and execution from test definition.
-
与旧版本的兼容(Compatibility with older releases): Supporting the execution of legacy Java 3 and Java 4 in the new JUnit 5 platform.
-
Modern programming model for writing tests (Java 8): Nowadays, more and more developers write code with Java 8 new features, such as lambda expressions. JUnit 4 was built on Java 5, but JUnit 5 has been created from scratch using Java 8.
Junit5 Latest Version junit.org/junit5/
Junit5 Architecture
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
-
JUnit Platform. This component is aimed to become the foundation for any testing framework executed in the JVM. In other words, it provides mechanisms to run Jupiter tests, legacy JUnit 4, and also third-party tests (for example, Spock, FitNesse, and so on).
-
The first high-level component is called Jupiter. It provides the brand-new programming and extension model of the JUnit 5 framework.
-
Vintage: This component allows running legacy JUnit tests on the JUnit Platform out of the box.
-
Test Engines: Created by extending the general Platform Engine (junit-platform-engine).These modules allow to execute a kind of test (Jupiter tests, legacy JUnit 4, or other Java tests) within the JUnit Platform.
-
Test Launcher: These modules provide the ability of test discovery inside the JUnit platform for external build tools and IDEs. This API is consumed by tools such as Maven, Gradle, IntelliJ, and so on, using the junit-platform-launcher module.
Junit5 简单测试类
public class Junit5StandardTest {
@Test
public void addListCorrect(){
Assertions.assertTrue(new ArrayList<>().add("junit"));
}
}
- IDEA中执行单元测试
-
idea驱动Launcher => Launcher调用Engine => Engine执行单元测试代码
-
查看运行命令如下:实际运行的是com.intellij.rt.junit.JUnitStarter类,Idea实现了Junit的Launcher API。并且idea的-classpath中自动引入junit-platform-launcher-1.9.3.jar。
C:\dawn\jdk17\jdk-17.0.7\bin\java.exe -classpath "C:\Users\Administrator\.m2\repository\org\junit\platform\junit-platform-launcher\1.9.3\junit-platform-launcher-1.9.3.jar; " com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit5 com.dawn.foundation.Junit5StandardTest,addListCorrect -
- Maven中执行单元测试
-
结果如下图,未执行任何一个单元测试,默认mvn test并未实现Junit5的Launcher,必须添加实现Launcher的依赖。
-
添加依赖,再次运行
<build> <plugins> <plugin> <!--包含了jupiter的Launcher--> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.1.2</version> <dependencies> <!--指定运行单元测试的engine这个engine是运行jupiter的单元测试方法--> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.9.1</version> </dependency> </dependencies> </plugin> </plugins> </build> -
Jupiter自定义Launcher
- 添加依赖
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.9.3</version>
<scope>test</scope>
</dependency>
- 自定义Launcher
public class Junit5CustomLauncher {
/**
* 1. 创建Launcher时,通过SPI的方式加载engine到Launcher。
* @see LauncherFactory#collectTestEngines
* 2. 执行单元测试,通过发现请求找到要发现的单元测试方法,并将其封装为TestPlan对象。
* 3. TestPlan对象中封装了TestIdentifier其包含了TestSource而TestSource包含了MethodSource
* @see org.junit.platform.launcher.TestIdentifier
* @see org.junit.platform.engine.TestSource
* @see org.junit.platform.engine.support.descriptor.MethodSource
*
* @param args
*/
public static void main(String[] args) {
LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder
.request()
.selectors(
DiscoverySelectors.selectPackage("com.dawn"),
DiscoverySelectors.selectClass(Junit5StandardTest.class)
).build();
// 通过SPI的方式加载engine到Launcher对象中
Launcher launcher = LauncherFactory.create();
// 生成测试执行的摘要信息
SummaryGeneratingListener summaryGeneratingListener = new SummaryGeneratingListener();
launcher.registerTestExecutionListeners(summaryGeneratingListener);
// 执行发现的单元测试
launcher.execute(request);
// 输出单元测试执行后的摘要信息
summaryGeneratingListener.getSummary().printTo(new PrintWriter(System.out));
}
}
Jupiter Fundamental
- Jupiter Assertion Statement
- Assert Exception
- Assert All
- DisplayName
- Disabled
- Assert Timeout
- Assumptions
- Repeat
- Order
- Nested Test Class
- Test case lifecycle
- Tagging & Filtering
- Custom Tagging
- Integration With Hamcrest
- Callback API
- TestInstancePostProcessor
- TestExecutionExceptionHandler
- Conditional Execution
- Test Interfaces And Default Method
Condition Execution Method
跳过单元测试方法的方式
- Assumptions
- Tagging
- ExecutionCondition Extension
Test Method Order
@DisplayName("Junit 5 standard test")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class Junit5StandardTest {
@Test
@Order(2)
public void addListCorrect(){
Assertions.assertTrue(new ArrayList<>().add("junit"));
}
@Test
@Order(1)
public void disabledTestMethod(){
System.out.println("==>disabled test method");
}
}
Jupiter Nedsted Test Class
可对测试方法分类
public class Junit5NestedTest {
private ImmutableList<String> list = ImmutableList.of("java", "go", "python");
private static String env;
@BeforeAll
public static void setup() {
env = System.getenv("ENV");
env = env == null ? "unknown" : env;
}
@Nested
class NestedThrow {
@Test
@DisplayName("Test assert throw")
public void assertThrow() {
//given
//when
Executable executable = () -> list.remove(0);
//then
Assertions.assertThrows(UnsupportedOperationException.class, executable);
}
/**
* 针对多个断言语句,如果有运行出错的语句,其他断言语句依然可正常断言
*/
@Test
@DisplayName("Test multiple assert")
public void multipleAssert() {
Assertions.assertAll("assert all executable ",
() -> list.remove(0),
() -> list.get(0),
() -> list.get(1)
);
}
}
@DisplayName("Repeated test")
@Nested
class NestedRepeated{
@RepeatedTest(3)
public void repeatedExec() {
assertTrue(true, list.get(0));
}
@RepeatedTest(3)
public void repeatedWithIndex(RepetitionInfo repetitionInfo) {
var str = switch (repetitionInfo.getCurrentRepetition()) {
case 1 -> "java";
case 2 -> "go";
case 3 -> "python";
default -> "unknown";
};
assertEquals(list.get(repetitionInfo.getCurrentRepetition() - 1), str);
}
@DisplayName("Repeated with detail information=>")
@RepeatedTest(value = 3, name = "{displayName} total=>{totalRepetitions}, current=>{currentRepetition}")
public void repeatedWithDetailInformation(RepetitionInfo repetitionInfo) {
var str = switch (repetitionInfo.getCurrentRepetition()) {
case 1 -> "java";
case 2 -> "go";
case 3 -> "python";
default -> "unknown";
};
assertEquals(list.get(repetitionInfo.getCurrentRepetition() - 1), str);
}
@DisplayName("Repeated with long display name=>")
@RepeatedTest(value = 3, name = RepeatedTest.LONG_DISPLAY_NAME)
public void repeatedWithLongDisplayName(TestInfo testInfo, RepetitionInfo repetitionInfo) {
String displayName = String.format("%s :: repetition %s of %s", "Repeated with long display name=>", repetitionInfo.getCurrentRepetition(), repetitionInfo.getTotalRepetitions());
assertEquals(displayName, testInfo.getDisplayName());
}
}
/**
* assertTimeout:运行于当前线程,所以会一直阻塞当前线程
* assertTimeoutPreemptively:运行于线程池中,不会阻塞当前线程,可以退出
*/
@DisplayName("Assert timeout")
@Nested
class NestedAssertTimeout{
@Test
@DisplayName("timeout assertion=>")
// timeout注解可以让等待中断
@Timeout(value = 5, unit = TimeUnit.SECONDS)
public void timeoutAssertion() {
BlockingQueue blockingQueue = new ArrayBlockingQueue(14);
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
blockingQueue.offer("java");
}).start();
assertTimeout(Duration.ofSeconds(4), () -> assertEquals("java", blockingQueue.take()));
}
@Test
@DisplayName("Timeout preemptively assertion=>")
public void timeoutPreemptivelyAssertion() {
BlockingQueue blockingQueue = new ArrayBlockingQueue(14);
// 等待2秒自动退出
assertTimeoutPreemptively(Duration.ofSeconds(2), () -> blockingQueue.take());
}
}
@DisplayName("Assumptions test")
@Nested
class NestedAssumptions{
@Order(1)
@Test
public void executeInDev() {
BlockingQueue blockingQueue = new ArrayBlockingQueue(14);
// 来做一个前提假设(Assumption),确保代码运行在名为 "DEV" 的开发环境中。
// 如果前提假设不满足(即环境变量 env 不是 "DEV"),那么这个测试方法将会被忽略,不会执行后续的断言。
Assumptions.assumeTrue(env.equals("DEV"));
// 超时断言,尝试从 blockingQueue 中获取元素,但是会在两秒内超时。
// 如果在两秒内未能从队列中获取到元素,将会抛出断言错误。
assertTimeoutPreemptively(Duration.ofSeconds(2), () -> blockingQueue.take());
}
@Order(2)
@Test
public void multiExecuteInEnv() {
BlockingQueue blockingQueue = new ArrayBlockingQueue(14);
Assumptions.assumingThat(() -> env.equals("DEV"),
() -> assertTimeoutPreemptively(Duration.ofSeconds(2), () -> blockingQueue.take()));
Assumptions.assumingThat(() -> env.equals("PRE"),
() -> assertTimeout(Duration.ofSeconds(2), () -> blockingQueue.take()));
}
}
}
查看运行结构
Execution Condition
内置Conditional
public class JupiterConditionTest {
@BeforeAll
static void setup(){
System.getProperties().put("env", "dev");
}
@Test
@EnabledOnJre(JRE.JAVA_17)
void enableOnJDK17(){
}
@Test
@DisabledOnJre(JRE.JAVA_17)
void disableOnJDK17(){
}
@Test
@EnabledForJreRange(max = JRE.JAVA_17)
void enabledWhenJDKMatchRange() {
}
@Test
@DisabledForJreRange(min = JRE.JAVA_9, max = JRE.JAVA_17)
void disabledWhenJDKMatchRange() {
}
@Test
@EnabledOnOs(OS.MAC)
void enabledOnMac() {
}
@Test
@DisabledOnOs({OS.AIX, OS.FREEBSD})
void disabledOnOs() {
}
@Test
@EnabledIfSystemProperty(named = "env",matches = "dev")
void enabledIfSystemPropertyEnv(){
}
@Test
@DisabledIfSystemProperty(named = "env",matches = "dev")
void disabledIfSystemPropertyEnv(){
}
@Test
@EnabledIfEnvironmentVariable(named = "M2_HOME", matches = "*")
void enableHasEnvironmentContainsMavenHome() {
}
}
Custom Condition
@Test
@EnabledIf("customCondition")
void enabledIf() {
}
private boolean customCondition() {
return Boolean.TRUE;
}
Jupiter Lifecycle
public interface JupiterInstanceLifecycle {
class DawnTestInstancePostProcessor implements TestInstancePostProcessor {
@Override
public void postProcessTestInstance(Object o, ExtensionContext extensionContext) throws Exception {
System.out.println(">>TestInstancePostProcessor#postProcessTestInstance=>" + extensionContext.getTestInstanceLifecycle());
}
}
class DawnBeforeAllCallback implements BeforeAllCallback {
@Override
public void beforeAll(ExtensionContext extensionContext) throws Exception {
System.out.println(">>BeforeAllCallback#beforeAll=>" + extensionContext.getDisplayName());
}
}
class DawnBeforeEachCallback implements BeforeEachCallback {
@Override
public void beforeEach(ExtensionContext extensionContext) throws Exception {
System.out.println(">>BeforeEachCallback#beforeEach=>" + extensionContext.getDisplayName());
}
}
class DawnBeforeTestExecutionCallback implements BeforeTestExecutionCallback {
@Override
public void beforeTestExecution(ExtensionContext extensionContext) throws Exception {
System.out.println(">>BeforeTestExecutionCallback#beforeTestExecution=>" + extensionContext.getDisplayName());
}
}
class DawnTestExecutionExceptionHandler implements TestExecutionExceptionHandler {
@Override
public void handleTestExecutionException(ExtensionContext extensionContext, Throwable throwable) throws Throwable {
System.out.println(">>TestExecutionExceptionHandler#handleTestExecutionException" +
"=>extensionContext#" + extensionContext.getDisplayName() +
"=>throwable#" + throwable.getClass() + "#" + throwable.getMessage()
);
}
}
class DawnAfterEachCallback implements AfterEachCallback {
@Override
public void afterEach(ExtensionContext extensionContext) throws Exception {
System.out.println(">>AfterEachCallback#afterEach" +
"=>extensionContext#" + extensionContext.getDisplayName()
);
}
}
class DawnAfterTestExecutionCallback implements AfterTestExecutionCallback {
@Override
public void afterTestExecution(ExtensionContext extensionContext) throws Exception {
System.out.println(">>AfterTestExecutionCallback#afterTestExecution" +
"=>extensionContext#" + extensionContext.getDisplayName()
);
}
}
class DawnAfterAllCallback implements AfterAllCallback {
@Override
public void afterAll(ExtensionContext extensionContext) throws Exception {
System.out.println(">>AfterAllCallback#afterAll" +
"=>extensionContext#" + extensionContext.getDisplayName()
);
}
}
}
@ExtendWith({
JupiterInstanceLifecycle.DawnTestInstancePostProcessor.class,
JupiterInstanceLifecycle.DawnBeforeAllCallback.class,
JupiterInstanceLifecycle.DawnBeforeEachCallback.class,
JupiterInstanceLifecycle.DawnBeforeTestExecutionCallback.class,
JupiterInstanceLifecycle.DawnTestExecutionExceptionHandler.class,
JupiterInstanceLifecycle.DawnAfterTestExecutionCallback.class,
JupiterInstanceLifecycle.DawnAfterEachCallback.class,
JupiterInstanceLifecycle.DawnAfterAllCallback.class
})
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@DisplayName("jupiter test class instance life cycle test")
public class JupiterInstanceLifeCycleTest {
@BeforeAll
public static void init() {
System.out.println("@BeforeAll=>jupiter before all");
}
@BeforeEach
public void setup() {
System.out.println("@BeforeEach=>jupiter before each");
}
@RepeatedTest(2)
//@Disabled
public void execution() {
System.out.println("@RepeatedTest(2)=>jupiter execution test method");
}
@Test
public void executionWithEx() {
System.out.println("@Test=>jupiter execution with throw exception");
throw new IllegalArgumentException("@Test=>Throw exception=>only use to test");
}
@AfterEach
public void tearDown() {
System.out.println("@AfterEach=>jupiter after each");
}
@AfterAll
public static void destroy() {
System.out.println("@AfterAll=>jupiter after all");
}
}
执行结果:
>>TestInstancePostProcessor#postProcessTestInstance=>Optional[PER_CLASS]
>>BeforeAllCallback#beforeAll=>jupiter test class instance life cycle test
@BeforeAll=>jupiter before all
>>BeforeEachCallback#beforeEach=>repetition 1 of 2
@BeforeEach=>jupiter before each
>>BeforeTestExecutionCallback#beforeTestExecution=>repetition 1 of 2
@RepeatedTest(2)=>jupiter execution test method
>>AfterTestExecutionCallback#afterTestExecution=>extensionContext#repetition 1 of 2
@AfterEach=>jupiter after each
>>AfterEachCallback#afterEach=>extensionContext#repetition 1 of 2
>>BeforeEachCallback#beforeEach=>repetition 2 of 2
@BeforeEach=>jupiter before each
>>BeforeTestExecutionCallback#beforeTestExecution=>repetition 2 of 2
@RepeatedTest(2)=>jupiter execution test method
>>AfterTestExecutionCallback#afterTestExecution=>extensionContext#repetition 2 of 2
@AfterEach=>jupiter after each
>>AfterEachCallback#afterEach=>extensionContext#repetition 2 of 2
>>BeforeEachCallback#beforeEach=>executionWithEx()
@BeforeEach=>jupiter before each
>>BeforeTestExecutionCallback#beforeTestExecution=>executionWithEx()
@Test=>jupiter execution with throw exception
>>TestExecutionExceptionHandler#handleTestExecutionException=>extensionContext#executionWithEx()=>throwable#class java.lang.IllegalArgumentException#@Test=>Throw exception=>only use to test
>>AfterTestExecutionCallback#afterTestExecution=>extensionContext#executionWithEx()
@AfterEach=>jupiter after each
>>AfterEachCallback#afterEach=>extensionContext#executionWithEx()
@AfterAll=>jupiter after all
>>AfterAllCallback#afterAll=>extensionContext#jupiter test class instance life cycle test
Jupiter Interface Implements
- 定义接口类
@ExtendWith({
JupiterInstanceLifecycle.DawnTestInstancePostProcessor.class,
JupiterInstanceLifecycle.DawnBeforeAllCallback.class,
JupiterInstanceLifecycle.DawnBeforeEachCallback.class,
JupiterInstanceLifecycle.DawnBeforeTestExecutionCallback.class,
JupiterInstanceLifecycle.DawnTestExecutionExceptionHandler.class,
JupiterInstanceLifecycle.DawnAfterTestExecutionCallback.class,
JupiterInstanceLifecycle.DawnAfterEachCallback.class,
JupiterInstanceLifecycle.DawnAfterAllCallback.class
})
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@DisplayName("jupiter test class instance lifecycle")
public interface JupiterInterfaceTest {
List<String> LIST = List.of("HELLO", "JAVA", "JUNIT", "JUPITER");
@BeforeAll
static void init() {
System.out.println("@BeforeAll=>");
}
@BeforeEach
default void setUp(TestInfo testInfo) {
System.out.println("@BeforeEach=> " + testInfo.getTestMethod().get());
}
@AfterEach
default void tearDown(TestInfo testInfo) {
System.out.println("@AfterEach=> " + testInfo.getTestMethod().get());
}
@AfterAll
static void destroy() {
System.out.println("@AfterAll=> destroy");
}
@DisplayName("[List size should be four]")
@Test
default void listSizeShouldEquals4() {
assertThat(LIST, hasSize(4));
}
@DisplayName("[List contains 'JAVA' element]")
@Test
default void listContainsJavaElement() {
assertThat(LIST, hasItem("JAVA"));
}
@DisplayName("[Immutable list only support read operation]")
@Test
default void immutableListCouldNotUpdate() {
Executable executable = () ->
{
var firstElement = LIST.remove(0);
assertThat(firstElement, equalTo("HELLO"));
};
assertThrows(UnsupportedOperationException.class, executable);
}
@Disabled("disabled due to one of assertion failure.")
@DisplayName("[Immutable list only support read but update]")
@Test
default void immutableListCanReadButUpdate() {
assertAll("assert read and update mixed operation", Stream.of(
() -> assertThat(LIST.remove(0), equalTo("HELLO")
)));
}
class SimpleJupiterInterfaceTest implements JupiterInterfaceTest{
}
}
Jupiter Base On Interface Test
public interface JupiterInterfaceTest {
List<String> LIST = List.of("HELLO", "JAVA", "JUNIT", "JUPITER");
@BeforeAll
static void init() {
System.out.println("@BeforeAll=>");
}
@BeforeEach
default void setUp(TestInfo testInfo) {
System.out.println("@BeforeEach=> " + testInfo.getTestMethod().get());
}
@AfterEach
default void tearDown(TestInfo testInfo) {
System.out.println("@AfterEach=> " + testInfo.getTestMethod().get());
}
@AfterAll
static void destroy() {
System.out.println("@AfterAll=> destroy");
}
@DisplayName("[List size should be four]")
@Test
default void listSizeShouldEquals4() {
assertThat(LIST, hasSize(4));
}
}
// 单元测试类
public class SimpleJupiterInterfaceTest implements JupiterInterfaceTest {
}
Tagging
Example Of Tag
/**
* jdk1.8可以指定多tag
* @Tags 和 多tag的指定只能指定以一种
*/
/*@Tag("dev")
@Tag("local")
@Tag("pre")*/
@Tags({
@Tag("dev"),
@Tag("local"),
@Tag("pre")
})
public class JupiterTagTest {
/**
* 方法级别的tag和类级别的tag进行merge
*
* @param testInfo
*/
@Test
@Tag("dev")
public void tagMergeAssert(TestInfo testInfo) {
Assertions.assertTrue(testInfo.getTags().containsAll(List.of("dev", "test", "pre")));
}
@Test
@Tags({
@Tag("local"),
@Tag("prod")
})
public void tagAssert(TestInfo testInfo) {
Assertions.assertTrue(testInfo.getTags().containsAll(List.of("local", "dev", "test", "pre", "prod")));
}
}
Annotation Test Of Tag
public interface Env {
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Test
@Tag("dev")
@interface Dev {
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Test
@Tag("beta")
@interface Beta {
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Test
@Tag("pre")
@interface Pre {
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Test
@Tag("dev")
@Tag("pre")
@Tag("beta")
@interface Default {
}
}
@Env.Default
public class JupiterTagAnnotationTest {
@Env.Dev
public void tagMergeAssert(TestInfo testInfo) {
Assertions.assertTrue(testInfo.getTags().containsAll(List.of("beta", "pre")));
}
@Env.Pre
public void tagAssert(TestInfo testInfo) {
Assertions.assertTrue(testInfo.getTags().containsAll(List.of("dev", "beta")));
}
}
Hancrest Asserts
自然语言的代码编写风格利于理解和阅读并且与各种测试框架兼容
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>2.2</version>
<scope>test</scope>
</dependency>
@Test
public void assertThatTest() {
assertThat(5, Matchers.equalTo(5));
assertThat(list, Matchers.hasSize(3));
assertThat(list, Matchers.hasItem("java"));
assertThat(10, greaterThan(5));
assertThat(2, lessThan(5));
assertThat("Hello World", containsString("Hello"));
assertThat(10, both(greaterThan(5)).and(lessThan(15)));
assertThat(7, either(greaterThan(5)).or(lessThan(0)));
assertThat(new Integer[]{1, 2, 3}, hasItemInArray(2));
assertThat(new Integer[]{1, 2, 3}, Matchers.hasItemInArray(greaterThan(0)));
}
Mockito Extension
@ExtendWith(
MockitoExtension.class
)
public class MockitoExtensionTest {
@Test
public void mockitoParamTest(@Mock List list){
Mockito.when(list.size()).thenReturn(12);
MatcherAssert.assertThat(list.size(), Matchers.equalTo(12));
}
}