一、前言
在我们日常开发中项目功能自测必不可少,但是又是很容易被忽视的一个环节,其实,个人认为测试才是一个项目良好运行的保障,好的测试习惯能帮我们避免很多问题,可以提高我们的思考和开发功能的效率,但是一个复杂的项目的测试往往是让人头痛不已,千丝万缕连在一起,除非还原用户使用场景,否则怎么测试都让程序员感到不安,往往写完代码提交之后寄希望于测试爸爸能测出漏洞,但是自己写的代码,薄弱点和漏洞出现的可能性也只有自己亲自知道该往那个方向测试重拳出击,测试又怎么会知道呢?
往往项目上线之后我们还如履薄冰,要怎么测试自己开发的功能一直以来都是每个开发工作者需要考虑的问题,但是测试又分很多种类,比如一个小小的逻辑模块不依赖于任何模块的测试,像我来说就一个main方向就搞定了,如果在依赖深一点的,需要配置文件,需要web容器,需要bean的嵌套,但是又没有完整的环境,或者说配置一套完整的环境实在是太麻烦的时候,我们应该怎么测试呢?
对了,如果你刚好是SpringBoot项目,那么你一定要了解一下
SpringBootTest
,在SpringBoot中集成Junit5来进行测试,这也是目前市场主流的测试框架。
二、快速使用SpringBootTest
1、Junit集成SpringBoot
SpringBoot2.2.x 之前使用的是Junit4,之后就使用的是Junit5。
maven引入依赖,仅需要引入starter依赖就足够了。
<dependency > <groupId > org.springframework.bootgroupId > <artifactId > spring-boot-starter-testartifactId > <version > 2.3.13.RELEASEversion > <scope > testscope >dependency >
2、一个简单的测试类
import org.junit.jupiter.api.Test;import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest public class DemoTest { @Test public void demoTest () { System.out.println("test success" ); } }
三、SpringTest 注解
Spring为了简化xml文件配置,使用了大量的注解,所以需要我们重点了解
SpringBootTest
的注解,以便了解
SpringBootTest
的运行机制。
@SpringBootTest
它会加载完整的Spring应用程序上下文,包括所有的bean定义、配置和组件,并且会自动启动嵌入式的服务器。
@SpringBootTest 参数配置
1、
value
指定"
my.application.property
"的值为"value"
@SpringBootTest(value = "my.application.property=value" )
2、
properties
在加载配置的时候修改
my.property my.otherProperty
@SpringBootTest(properties = {"my.property=value1" , "my.otherProperty=value2" })
3、
classes
指定两个配置类
MyConfig1.class
和
MyConfig2.class
@SpringBootTest(classes = {MyConfig1.class, MyConfig2.class})
4、
webEnvironment
指定使用随机端口的Web环境进行集成测试。
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@Test
用来标记这个方法是个测试方法。
@SpringBootTest public class DemoTest { /** * 表示这个方法是个测试方法 */ @Test public void demoTest () { System.out.println("test success" ); } }
@ParameterizedTest
用来标记一个参数化测试方法,和以下注解搭配使用。(列出常用的几种入参方式注解)
@ValueSource:
用于指定基本类型、String类型或Class类型的参数值。
@CsvSource:
用于指定CSV格式的参数值,逗号分隔每个参数。
@MethodSource:
用于指定一个或多个方法作为参数提供者。
@ArgumentsSource:
用于指定一个自定义的参数提供者类。
@NullSource:
用于指定null值作为参数。
@EmptySource:
用于指定空值作为参数。
@NullAndEmptySource:
用于指定null值和空值作为参数。
@DisplayNameGeneration:
用于指定一个自定义的显示名称生成器。
这些注解提供了不同的方式来为测试方法提供参数,并且可以根据实际情况选择合适的注解来进行参数化测试。
public class DemoTest { /** * @ParameterizedTest 标记名为testIsPositive的参数化测试方法 * @ValueSource 注解,我们将整数1、2和3作为参数传递给测试方法 * @param number */ @ParameterizedTest @ValueSource (ints = {1 , 2 , 3 }) public void testIsPositive (int number) { assertTrue(number > 0 ); } }
1、
@ValueSource
用于提供测试方法的参数化值。
public class CalculatorTest { @ParameterizedTest @ValueSource (ints = {1 , 2 , 3 , 4 , 5 }) public void testIsPositive (int number) { assertTrue(number > 0 ); } }
2、
@MethodSource
用于提供测试方法的参数化方法。
public class CalculatorTest { @ParameterizedTest @MethodSource ("provideAdditionTestData" ) public void testAddition (int a, int b, int expected) { Calculator calculator = new Calculator(); int result = calculator.add(a, b); assertEquals(expected, result); } private static Stream provideAdditionTestData () { return Stream.of( Arguments.of(1 , 2 , 3 ), Arguments.of(5 , -3 , 2 ), Arguments.of(10 , 10 , 20 ) ); } }
3、
@NullSource
用于指定测试方法的参数化测试中的一个参数为null。
public class StringUtilsTest { @ParameterizedTest @NullSource public void testIsEmpty_Null (String input) { assertTrue(StringUtils.isEmpty(input)); } }
4、
@EnumSource
用于指定测试方法的参数化测试中的一个参数为枚举类型的值。
public class CalculatorTest { enum Operation { ADD, SUBTRACT, MULTIPLY, DIVIDE } @ParameterizedTest @EnumSource (Operation.class ) public void testCalculate (Operation operation ) { Calculator calculator = new Calculator(); int result; switch (operation) { case ADD: result = calculator.add(2 , 3 ); assertEquals(5 , result); break ; case SUBTRACT: result = calculator.subtract(5 , 2 ); assertEquals(3 , result); break ; case MULTIPLY: result = calculator.multiply(4 , 6 ); assertEquals(24 , result); break ; case DIVIDE: result = calculator.divide(10 , 2 ); assertEquals(5 , result); break ; } } }
@RepeatedTest
用于重复运行相同的测试方法。可以使用该注解指定要重复运行的次数。
public class DemoTest { /** * @RepeatedTest (5) 标记名为testAddition的测试方法,并指定要重复运行它的次数为5次 */ @RepeatedTest (5 ) public void testAddition () { int result = 2 + 2 ; assertEquals(4 , result); } }
@DisplayName
用于为测试类或测试方法指定一个自定义的显示名称。
@DisplayName ("Calculator Tests" )public class CalculatorTest { @Test @DisplayName ("Addition Test" ) public void testAddition () { int result = Calculator.add(2 , 2 ); assertEquals(4 , result); } @Test @DisplayName ("Subtraction Test" ) public void testSubtraction () { int result = Calculator.subtract(5 , 3 ); assertEquals(2 , result); } }
@BeforeEach & @AfterEach & @BeforeAll & @AfterAll
@BeforeEach
在每个测试方法运行之前执行一次。
@AfterEach
在每个测试方法运行之后执行一次。
@BeforeAll
在所有测试方法之前执行一次。
public class DemoTest { @BeforeAll public static void init () { System.out.println("@BeforeAll" ); } @BeforeEach public void setUp () { System.out.println("@BeforeEach" ); } @Test public void testAddition () { System.out.println("Test success" ); } @AfterEach public void tearDown () { System.out.println("@AfterEach" ); } @AfterAll public static void cleanUp () { System.out.println("@AfterAll" ); } }
结果
@Disabled
用于禁用测试类或测试方法。
@Disabled
("This test is currently disabled" )public class CalculatorTest { @Test public void testAddition () { } }
@Timeout
它用于设置测试方法的超时时间。
public class CalculatorTest { @Test @Timeout (value = 5 , unit = TimeUnit.SECONDS) public void testAddition () { // 执行需要在5秒内完成的测试代码 } }
四、Junit5断言
JUnit Jupiter 断言都是
static org.junit.jupiter.Assertions
方法,方便使用。
assertEquals(expected, actual)
验证两个值是否相等。
public class DemoTest { @Test public void test () { int expected = 10 ; int actual = 5 + 5 ; assertEquals(expected, actual); // 通过 } }
assertNotEquals(unexpected, actual)
验证两个值是否不相等。
public class DemoTest { @Test public void test () { int unexpected = 10 ; int actual = 5 + 5 ; assertNotEquals(unexpected, actual); // 通过 } }
assertArrayEquals(expectedArray, actualArray)
验证两个数组是否相等。
public class DemoTest { @Test public void test () { int [] expectedArray = {1 , 2 , 3 }; int [] actualArray = {1 , 2 , 3 }; assertArrayEquals(expectedArray, actualArray); // 通过 } }
assertTrue(condition)
验证条件是否为true。
public class DemoTest { @Test public void test () { boolean condition = 5 > 3 ; assertTrue(condition); // 通过 } }
assertFalse(condition)
验证条件是否为false。
public class DemoTest { @Test public void test () { boolean condition = 5 3; assertFalse(condition); // 通过 } }
assertNull(actual)
验证值是否为null。
public class DemoTest { @Test public void test () { String actual = null ; assertNull(actual); // 通过 } }
assertNotNull(actual)
验证值是否不为null。
public class DemoTest { @Test public void test () { String actual = "Hello" ; assertNotNull(actual); // 通过 } }
assertSame(expected, actual)
验证两个引用是否指向同一个对象。
public class DemoTest { @Test public void test () { String expected = "Hello" ; String actual = "Hello" ; assertSame(expected, actual); // 通过 } }
assertNotSame(unexpected, actual)
验证两个引用是否指向不同的对象。
public class DemoTest { @Test public void test () { String unexpected = "Hello" ; String actual = "World" ; assertNotSame(unexpected, actual); // 通过 } }
assertThrows(expectedType, executable)
验证代码块是否抛出了指定的异常。
public class DemoTest { @Test public void test () { assertThrows(ArithmeticException.class , () -> { int result = 1 / 0 ; // 抛出ArithmeticException }); } }
assertDoesNotThrow(executable)
验证代码块是否没有抛出任何异常。
public class DemoTest { @Test public void test () { assertDoesNotThrow(() -> { int result = 1 / 1 ; // 不会抛出异常 }); } }
assertTimeout(duration, executable)
验证代码块是否在指定的时间内执行完毕。
public class DemoTest { @Test public void test () { assertTimeout(Duration.ofSeconds(5 ), () -> { // 执行耗时较长的代码块 }); } }
assertAll(executables)
验证多个断言是否都通过。
public class DemoTest { @Test public void test ()