Junit5单元测试

    • 配置

    • Maven配置

我用的spring版本是2.2.2。

其实引入一个就行:

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<scope>test</scope>

</dependency>

里面就会引入单元测试相关的一系列jar,包括junit4和jupiter(junit5)都都引入了。

比如

spring-boot-test、spring-boot-test-autoconfigure、spring-test,

还有

<dependency>

<groupId>org.junit.jupiter</groupId>

<artifactId>junit-jupiter</artifactId>

<version>5.5.2</version>

<scope>compile</scope>

</dependency>【里面包括jupiter一系列的jar包,可以看到junit是4,jupiter是5】

<dependency>

<groupId>org.mockito</groupId>

<artifactId>mockito-junit-jupiter</artifactId>

<version>3.1.0</version>

<scope>compile</scope>

</dependency>

<dependency>

<groupId>org.assertj</groupId>

<artifactId>assertj-core</artifactId>

<version>3.13.2</version>

<scope>compile</scope>

</dependency>

<dependency>

<groupId>org.mockito</groupId>

<artifactId>mockito-core</artifactId>

<version>3.1.0</version>

<scope>compile</scope>

</dependency>【mockito-core-3.1.0.jar,核心mock相关都在里面

等等。

另外看使用情况可能需要再单独引入:

<dependency>

<groupId>org.junit.platform</groupId>

<artifactId>junit-platform-launcher</artifactId>

<version>1.5.2</version>

<scope>test</scope>

</dependency>

<dependency>

<groupId>junit</groupId>

<artifactId>junit</artifactId>

<scope>test</scope>

</dependency>【junit-4.12.jar】

<dependency>

<groupId>io.gitee.ForteScarlet</groupId>

<artifactId>mock.java</artifactId>

<version>1.7.1</version>

</dependency>

引入的jar是mock.java-1.7.1.jar(MockObject、Mock等)

    • yml配置

test文件夹下是有自己独立的resources的:

bootstrap.yml

application-junit.yml

有了这些配置才能在test类上面加上注解:

@ExtendWith(SpringExtension.class)

@SpringBootTest(classes = RrmApplication.class, properties = {"system.data-auth.enabled=true"})

这样跑test时就会启动spring并支持spring的注解。

当然也可以不加这两个注解,那就跑普通test不启动spring相关。

    • 引入内置mongoDB数据库服务以支持单元测试

在pom.xml中加入:

<dependency>

<groupId>com.spring4all</groupId>

<artifactId>mongodb-plus-spring-boot-starter</artifactId>

<version>1.0.0.RELEASE</version>

</dependency>

另外还要在test下的application-junit.yml加上:

spring:

data:

mongodb:

uri: mongodb://localhost:27017/fms_rrm

    • 引入内嵌redis服务支持单元测试

在pom.xml中加入:

<dependency>

<groupId>it.ozimov</groupId>

<artifactId>embedded-redis</artifactId>

<version>0.7.3</version>

<scope>test</scope>

<exclusions>

<exclusion>

<groupId>com.google.guava</groupId>

<artifactId>guava</artifactId>

</exclusion>

<exclusion>

<groupId>org.slf4j</groupId>

<artifactId>slf4j-simple</artifactId>

</exclusion>

</exclusions>

</dependency>

在test下的application-junit.yml加上:

spring:

redis:

host: localhost

port: 6380

    • 注解

0.@Test 修饰在方法上

1.@DisplayName 说明,运行时会看到,修饰在类上和方法上都可以

2.@before 只跑一次

3. @beforeEach每个test方法前都会跑一次

@BeforeAll

@AfterEach

@AfterAll

一般:

@BeforeEach

void init() {mongodb构造数据等}

@AfterEach

void clear() {mongodb清理数据等}

4. JUnit 4到5迁移吗?从@RunWith到@ExtendWith的过渡(修饰在类上)

当涉及Spring时:

如果您想在测试中使用Spring测试框架功能(例如)@MockBean,则必须使用@ExtendWith(SpringExtension.class)。它取代了不推荐使用的JUnit4@RunWith(SpringJUnit4ClassRunner.class)

当不涉及Spring时:

例如,如果您只想涉及Mockito而不必涉及Spring,那么当您只想使用@Mock/ @InjectMocks批注时,可写成这样:@ExtendWith(MockitoExtension.class),因为它不会加载到很多不需要的Spring东西中。它替换了不推荐使用的JUnit4 @RunWith(MockitoJUnitRunner.class)。

5. @MockBean

@Mock

@InjectMocks

For1:

@InjectMocks

private SomeHandler someHandler;

@Mock

private OneDependency oneDependency; // 此mock将被注入到someHandler

InjectMocks,就好比是service层的对象。Mock好比是dao层对象。

当然也可以用代码的方式将私有属性注入对象中:

public static void setProperty(Object target, String name, Object value) throws NoSuchFieldException {

Field field = target.getClass().getDeclaredField(name);

ReflectionUtils.makeAccessible(field);

ReflectionUtils.setField(field, target, value);

}

6.@SpringBootTest(classes = RrmApplication.class, properties = {"system.data-auth.enabled=true"})

修饰在类上。

如果单元测试要启动spring相关,一个例子如下:

@ExtendWith({SpringExtension.class})

@SpringBootTest(classes = {RrmApplication.class})

@DisplayName("清除机器人位置坐标信息")

class ClearRobotLocationInfoTest {...}

7.@Ignore 修饰类,忽略该单元测试类(修饰方法貌似没用)

8.启用内置kafka: @EmbeddedKafka(topics = {"fms.rrm.clearPositionInfo"})

    • 测试方法

    • 规范

示例:

@Test

@DisplayName("查询数据服务日志审计-总条数为0")

void test_pageCallLog_countIsZero() {

PageCallLogReq req = buildPageCallLogReq(LocalDate.now(), LocalDate.now(), null);

Mockito.when(repository.countCallLog(req.getParams())).thenReturn(0L);

PageResp<List<PageCallLogDTO>> rest = dataServiceImpl.pageCallLog(req);

assertEquals(rest.getTotal(), 0L);

assertNull(rest.getData());

}

测试方法命名规范:test_[要测试的方法]_[要测试的分支情况]

    • 测试protected与private方法

一般是不建议单独针对protected与private方法去做单元测试,单元测试应该是只针对public方法,在public方法中调用的protected与private方法也会被同时测试到。

如果实在要测试protected与private方法,则要采用反射方式去调用。

For:

@InjectMocks

private IccidPosListener iccidPosListener = new IccidPosListener();

@Mock

private AsyncIccidPosDeal asyncIccidPosDeal;

@Test

@DisplayName("handleIccid方法正常执行")

void test_handleIccid_normal(){

try {

Method method = iccidPosListener.getClass().getDeclaredMethod("handleIccid", ConsumerRecord.class);

ConsumerRecord<String, String> record = new ConsumerRecord("topic", 1, 2, "key", "{'deviceCode': 'deviceCode2', 'iccid': 'iccid2'}");

Mockito.doNothing().when(asyncIccidPosDeal).handlePosRecord(Mockito.any(RobotIccidDto.class), Mockito.any(IccidUpdateResult.class));

method.setAccessible(true);

method.invoke(iccidPosListener, record);

Mockito.verify(asyncIccidPosDeal).handlePosRecord(Mockito.any(RobotIccidDto.class), Mockito.any(IccidUpdateResult.class));

} catch (Exception e) {

e.printStackTrace();

assertTrue(false);

}

}

    • 指定方法顺序

在类上加上注解:

@TestMethodOrder (MethodOrderer.Alphanumeric.class ) //字母数字自然顺序

然后单元测试方法命名如test1、test2.。。。

在运行该类单元测试时就会按方法命名顺序去执行各个方法。

当然还有其他方法指定顺序。

    • Mock

这个是单元测试的核心知识点。

    • mock对象

repository = Mockito.mock(DataServiceRepository.class);

或者直接用@InjectMocks 与 @Mock

    • mock行为(方法)

    • mock普通方法

1.方法有返回值:Mockito.when(repository.countCallLog(req.getParams())).thenReturn(12L);

还可以这样写:

Mockito.doReturn(future).when(kafkaTemplate).send(any(), any());

2.方法无返回值:

Mockito.doNothing().when(bIPMsgService.sendBip(Mockito.any()));

但在junit5这样写貌似会报错,改成如下:

Mockito.doNothing().when(repository).exportCallLog(req, Mockito.any(Consumer.class));

Ps:对象本身就是mock出来的,那这个doNothing语句可以不用写,因为mock出来的对象默认就是跳过其所有方法的。

3.如果无返回值的方法模拟抛出异常也要修改写法:

Mockito.when(fileConfigurationRepository.delete(GlobalConfiguration.CUSTOM_LOGO_ID)).thenThrow(new Exception());

改为:

Mockito.doThrow(new RuntimeException()).when(fileConfigurationRepository).delete(GlobalConfiguration.CUSTOM_LOGO_ID);

    • mock静态方法

Jdk8以前可以用PowerMockito来mock静态方法,但jdk8及以后就不能用了。

要用Mockito 3.4以上的版本就可直接支持。

所以需要额外引入:

<dependency>

<groupId>org.mockito</groupId>

<artifactId>mockito-core</artifactId>

<version>3.9.0</version>

<scope>test</scope>

</dependency>

<dependency>

<groupId>org.mockito</groupId>

<artifactId>mockito-inline</artifactId>

<version>4.1.0</version>

<scope>test</scope>

</dependency>

其实如果springboot本身的版本在2.5以上的话mockito也在3.4以上了(会使得spring-boot-starter-test也在2.5以上,可以直接单独指定spring-boot-starter-test版本为2.5.3以上),这样就不用引入mockito-core,但mockito-inline还是要引入的。

1.对于无返回值void的静态方法:

try(MockedStatic<ExcelExport> mock = Mockito.mockStatic(ExcelExport.class)){

dataServiceImpl.exportCallLog(req, response);//里面会调用ExcelExport.export静态方法

}

注意要test的方法要写在try作用域里面。

因为这里已经mock了静态方法所在的类,默认就会跳过其下所有静态方法,所有不用再mock静态方法。

Ps: 目前问题,对于返回void的静态方法如何模拟抛出异常???

2.对于有返回值但入参为空的静态方法:

try(MockedStatic<ExcelExport> mock = Mockito.mockStatic(ExcelExport.class)){

mock.when(ExcelExport::export).thenReturn(1);

dataServiceImpl.exportCallLog(req, response);

}

3.对于有返回值且有入参为空的静态方法:

try(MockedStatic<ExcelExport> mock = Mockito.mockStatic(ExcelExport.class)){

mock.when(() -> ExcelExport.export(PoiExcelInfo.builder().build(), response)).thenReturn(1);

dataServiceImpl.exportCallLog(req, response);

}

这里面也有个要点:静态方法export的入参是一个PoiExcelInfo对象,我写成Mockito.any(PoiExcelInfo.class)是会报错的,随意new一个对象反而不会报错,也能正常跳过该静态方法。

    • mock私有方法

待补充

    • 模拟方法抛异常

对于无返回值的:

Mockito.doThrow(new RuntimeException("抛出异常")).when(asyncIccidPosDeal).handlePosRecord(Mockito.any(RobotIccidDto.class), Mockito.any(IccidUpdateResult.class));

貌似不能直接抛new Exception()的,需要指定一种具体的异常类,如RuntimeException。

对于有返回值的:

Mockito.when(robotLocationService.queryDeviceLocation(Mockito.anyString())).thenThrow(new RuntimeException("异常哈哈"));

ps:抛的指定异常必须是这个方法真的有throws这种异常,否则只能模拟抛出运行时异常,要不就会报错:

Checked exception is invalid for this method!

    • mock方法指定入参

·任意字符串:Mockito.anyString()

·任意指定对象:Mockito.any(UserDTO.class)

·如果入参是一个函数式接口,比如Consumer,好像不能用Mockito.any(Consumer.class),要试下用doc -> {} 是否可以,比如:

Mockito.doNothing().when(repository).exportCallLog(req, doc -> {});这个不确定,待定。

当然还有很多Mockito.anyInt()之类的,但非必要统一用Mockito.any()就好。

    • mock redis

如果是启动spring的单元测试,但又想跳过redis的相关操作,可以这样配置:

在test下面创建类:

@Configuration

public class JunitTestConfig{

@Bean

public RedisTemplate redisTemplate() {

RedisTemplate redisTemplate = Mockito.mock(RedisTemplate.class);

ValueOperations valueOperations = Mockito.mock(ValueOperations.class);

SetOperations setOperations = Mockito.mock(SetOperations.class);

HashOperations hashOperations = redisTemplate.opsForHash();

ListOperations listOperations = redisTemplate.opsForList();

ZSetOperations zSetOperations = redisTemplate.opsForZSet();

when(redisTemplate.opsForSet()).thenReturn(setOperations);

when(redisTemplate.opsForValue()).thenReturn(valueOperations);

when(redisTemplate.opsForHash()).thenReturn(hashOperations);

when(redisTemplate.opsForList()).thenReturn(listOperations);

when(redisTemplate.opsForZSet()).thenReturn(zSetOperations);

RedisOperations redisOperations = Mockito.mock(RedisOperations.class);

RedisConnection redisConnection = Mockito.mock(RedisConnection.class);

RedisConnectionFactory redisConnectionFactory = Mockito.mock(RedisConnectionFactory.class);

when(redisTemplate.getConnectionFactory()).thenReturn(redisConnectionFactory);

when(valueOperations.getOperations()).thenReturn(redisOperations);

when(redisTemplate.getConnectionFactory().getConnection()).thenReturn(redisConnection);

return redisTemplate;

}

}

    • 断言

单元测试的规范之一就是必须要写断言,而且不能写成assertTrue(true);这样绝对正确的断言。针对各种情况的断言这里一一介绍。

Assert是junit4的,而Assertions是junit5(jupiter5)的。

    • 测试的方法有返回值的

就断言方法返回值等于预期值就好,如:

assertEquals(rest.getTotal(), 12L);

assertNull(rest.getData());

Assertions.assertTrue(robots1.size() > 0);

    • 测试的方法返回void的

断言某对象有执行某方法:

对于返回void函数,测试时可以这样断言:(这个例子不对,verify要求robotExceptionReq对象也要是mock对象才行,所以找个mock对象就行啦!)

RobotExceptionReq robotExceptionReq = RobotExceptionReq.builder().build();

exceptionMessageHelper.handleMessage(robotExceptionReq);

Mockito.verify(robotExceptionReq).setCategory(Mockito.anyString());

证明robotExceptionReq执行了setCategory方法。

还可以写成断言某对象调用了某方法几次:

Mockito.verify(studentRepository, Mockito.times(2)).save(studentEntity);

    • 测试的方法抛异常的

For1:

Assertions.assertThrows(ServiceException.class,() -> controller.getExceptionCodeCount(req));

For2:

MyException thrown =

assertThrows(MyException.class,

() -> myObject.doThing(),

"Expected doThing() to throw, but it didn't");

assertTrue(thrown.getMessage().contains("Stuff"));

    • 在idea中使用单元测试

    • 创建junit单元测试类

对着类Ctrl + Shift + T

或者:

在需要测试的类或接口(推荐基于接口创建)名称上使用Alt+Enter,然后选择创建测试。

    • 查看覆盖率

Idea: Ctrl+Alt+F6 查看测试覆盖率

或者直接在类上面运行覆盖率:

执行后:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JUnit 5 是 Java 编程语言的单元测试框架,它是 JUnit 团队开发的最新版本。JUnit 5 提供了一套强大的工具和功能,用于编写和执行单元测试。下面是 JUnit 5 单元测试的一些重要特性和用法: 1. 注解驱动:JUnit 5 使用注解来标记测试方法和测试类。常用的注解包括 `@Test`、`@BeforeEach`、`@AfterEach` 等。 2. 断言方法:JUnit 5 提供了丰富的断言方法,用于验证测试结果是否符合预期。例如,`assertEquals()`、`assertTrue()`、`assertNotNull()` 等。 3. 参数化测试:JUnit 5 支持参数化测试,可以通过 `@ParameterizedTest` 注解来定义参数化测试方法,并使用 `@ValueSource`、`@CsvSource` 等注解提供测试参数。 4. 嵌套测试:JUnit 5 允许在一个测试类中嵌套其他测试类,以更好地组织和管理测试代码。 5. 扩展模型:JUnit 5 引入了扩展模型,通过实现扩展接口可以自定义测试生命周期、测试报告、参数解析等行为。 6. 并发执行:JUnit 5 支持并发执行测试,可以通过 `@Execution` 注解来配置并发策略。 7. 动态测试:JUnit 5 允许在运行时动态生成测试用例,通过 `DynamicTest` 接口和 `@TestFactory` 注解实现。 8. 条件测试:JUnit 5 提供了条件测试的功能,可以根据条件来决定是否执行某个测试方法。 以上是 JUnit 5 单元测试的一些重要特性和用法。如果你还有其他问题,请继续提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值