JUnit 5 单元测试实践
基本用法
- 每个测试用例要有断言 assert
- 每个用例只专注一件事情
Example
/**
* @author 骚铭
*/
public interface TagFormatter {
/**
* 格式化标签值
* @param value 标签值
* @return 格式化结果
*/
String format(Object value);
/**
* 如果 value 不合法,则返回空字符串,不抛出异常
*/
default String safeFormat(Object value) {
if (value == null) {
return "";
}
try {
return format(value);
}
catch (Exception e) {
return "";
}
}
}
public class TagFormatterTest {
@Test
void testPercentage() {
TagFormatter formatter = TagFormatterFactory.percentage();
String result = formatter.format(99.3333);
assertEquals(result, "99.33%");
result = formatter.format(99.0);
assertEquals(result, "99.00%");
}
@Test
void testDecimal() {
TagFormatter formatter = TagFormatterFactory.decimal();
String result = formatter.format(99.3333);
assertEquals(result, "99.33");
result = formatter.format(99.3);
assertEquals(result, "99.30");
result = formatter.format(0.0);
assertEquals(result, "0.00");
}
@Test
void testDate() {
TagFormatter formatter = TagFormatterFactory.date();
String result = formatter.format(DateUtil.parseDate("2022-01-01"));
assertEquals(result, "2022-01-01");
}
}
Mock
使用 MockitoExtension
,即可以使用 @Mock
注解。
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class MyTest {
@Mock
private TaggingService taggingService;
@Test
public void test() {
when(taggingService.tagging(eq("A"), any())).thenReturn(2);
Assertions.assertEquals(taggingService.tagging("A"), 2);
}
}
Spring
使用 SpringExtension
,配合@MockBean
,可以在需要注入的地方使用 mock bean。
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = { NodeMapperImpl.class })
public class UnitSerializeTest {
@Autowired
NodeMapper nodeMapper;
@MockBean
TaggingService taggingService;
@Test
void test() {
when(taggingService.tagging(any())).thenReturn(2);
NodeDo nodeDo = new NodeDo();
Node node = nodeMapper.toEntity(nodeDo);
}
Embedded Redis
如果依赖了中间件或数据库,做单元测试时,有两种思路:
- embedded service (基于内存)
- TestsContainers(基于容器)
这里以 redis 为例,先探讨 embedded 的方式。需要添加如下测试依赖。
<dependency>
<groupId>it.ozimov</groupId>
<artifactId>embedded-redis</artifactId>
<version>0.7.2</version>
<scope>test</scope>
</dependency>
然后定义一个 RedisServer
,并用TestConfiguration
注解,仅作用于测试。
@TestConfiguration
public class TestRedisConfig {
private final RedisServer redisServer = new RedisServer(6379);
@PostConstruct
public void postConstruct() {
redisServer.start();
}
@PreDestroy
public void preDestroy() {
redisServer.stop();
}
}
单元测试如下,classes 注意要添加上述类 TestRedisConfig
。
@Import(RedisConfig.class)
@SpringBootTest(classes = {
ProductFieldServiceImpl.class,
ProductFieldConverterImpl.class,
LettuceConnectionFactory.class,
TestRedisConfig.class
})
@ExtendWith(SpringExtension.class)
public class ProductFieldTest {
@Autowired
ProductFieldService productFieldService;
@Test
void getValue() {
String value = productFieldService.getFormattedValue(ProductFieldValueQuery.of("WWW3037", "XZ_MF10"));
System.out.println(value);
}
}
Feign
对Feign接口进行单元测试时,需要 import 两个 configuration,因此可以抽一个基类出来,如下所示:
@ExtendWith(SpringExtension.class)
@Import({FeignAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class})
public class BaseFeignTest {
}
然后继承这个基类,并 enable 要测试的 client 即可。
@EnableFeignClients(clients = {ExampleFeign.class})
public class ProductFieldTest extends BaseFeignTest {
@Autowired
ExampleFeign feign;
@Test
void testFeign() {
TagValuesDto result = feign.getProductFieldValue("WWW3037", "XZ_MF10").getData();
System.out.println(result);
}
}
DevOps
有了单元测试之后,我们希望每次编译部署都执行一次。此时只需将 maven -DskipTest=true 去掉,则会在每个发布的生命周期内执行单元测试。
一旦有测试用例不通过,就会阻断在自动化编译阶段。
对于屎山代码而言,可能会有些乱七八糟的test。此时如果我们只希望执行部分单元测试,要怎么做呢?
这个时候就需要在 pom.xml
的build
里面添加 surefire plugin,然后补充 excludes,即可跳过不希望执行的单元测试。示例如下。
<!-- Surefire Test-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<!--配置是否跳过测试用例执行-->
<!--<skipTests>true</skipTests>-->
<!-- 包含 -->
<!--<includes>-->
<!--<include>com.cmb.lr20.zxb.dialog.domain.*Test</include>-->
<!--</includes>-->
<!-- 排除 -->
<excludes>
<exclude>com/cmb/lr20/zxb/dialog/infrastructure/**/*.java</exclude>
<exclude>com/cmb/lr20/zxb/dialog/infrastructure/*.java</exclude>
<exclude>com/cmb/lr20/zxb/dialog/application/*.java</exclude>
<exclude>com/cmb/lr20/zxb/dialog/external/**/*.java</exclude>
<exclude>com/cmb/lr20/zxb/dialog/utils/*.java</exclude>
<exclude>com/cmb/lr20/zxb/dialog/*ApplicationTest.java</exclude>
</excludes>
</configuration>
</plugin>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。