实战模拟带你入门Junit+Mockito+Assert

测试分类

按不同分类可以有很多种,我这里简单说一下下面的几种,这里想要更深入了解的可以看这篇文章:https://zhuanlan.zhihu.com/p/81571007

img

单元测试目的:用于验证编码单元的正确性,比如测试某个方法逻辑正确性,属于白盒测试,即被测对象内部逻辑对测试者来说是透明的,一般由开发编写。

集成测试:用于验证详细设计,也叫组装测试、子系统测试,是在单元测试的基础上,将涉及到的上下游依赖、数据库、中间件、缓存等都访问真实内容,而不是单元测试的 mock 内容,将涉及到的模块都组装起来形成一个子系统,看这个子系统功能能不能正常服务,满足详细设计要求,属于黑盒测试。

系统测试目的:用于验证概要设计,测试每个系统功能的正确性,属于白盒测试,测试人员来做。

回归测试目的:验证缺陷得到了正确的修复,并且对系统的变更,没有影响以前的功能。一般是通过重新执行所有在前期测试阶段建立的测试用例,来确认问题修改的正确性。

Junit+Mockito+Assert实战

Mock场景

在实际开发中,为了完成我们的业务开发CRUD,我们的常操就是:

调用数据库获取数据,调用外部RPC获取数据,获取这些数据后做自己的业务逻辑开发返回给前端,这里我们依赖了数据库、外部RPC服务,所以我们在写单元测试的时候,经常做的操作就是需要用到Mock掉这些外部的依赖,保证这些外部依赖正常获取的的情况下我们的业务逻辑是正确的!

这里Mock的具体场景如下

  1. 最常用的 mock 场景是外部资源 RPC调用
  2. 拿数据库的连接,增删改查数据
  3. 下载文件
  4. 发邮件
  5. 调用打印机打印文件等

当测试涉及到以上内容的时候,没必要真正去调用这些资源,mockito 可以模拟这些外部资源调用。

实战模拟

这里我模拟一个日常开发中的业务场景带大家入门一下Mock是怎么使用的:

场景如下:我们需要调用UserService.getUserNameList获取用户列表返回给前端,我们想要验证的是getUserNameList这个方法的逻辑是否正确,getUserNameList主要逻辑如下:

1、调用数据库接口

2、业务逻辑计算:这里会涉及大量业务逻辑开发

3、调用第三方接口

这里我们的核心业务逻辑主要是在第2步,对userList进行了大量业务逻辑操作,所以我们想要验证的是在调用第3步调用第三方接口externalService.calculateUserInfo(userList)的时候,我这个userList的入参是否符合预期,也就是我第2步进行的业务逻辑开发是否正确

UserService:

public class UserService {

    private UserDao userDao;
    private ExternalService externalService;

    public List<String> getUserNameList(Long userId) {
        //模拟调用数据库接口   这里也需要mock
        List<String> userList = userDao.listUserName();
        userList.add("bb");

        //模拟业务复杂的逻辑计算:对userList进行了大量操作
        String uidStr = userId.toString();
        userList.add(uidStr);

        //模拟调用第三方接口   这里需要Mock掉
        externalService.calculateUserInfo(userList);
        userList.add("ccc");
        return userList;
    }
}

UserDao:

public class UserDao {

    public List<String> listUserName() {
        return new ArrayList<>(Arrays.asList("a", "b", "c"));
    }

}

外部RPC服务ExternalService:

public class ExternalService {

    public void calculateUserInfo(List<String> userList) {
        userList.remove(0);
        System.out.println("calculator" + userList);
    }
}

Mock测试解析

我们这里mock两个场景,一个是正常调用成功的情况,一个是调用外部依赖失败的情况

正常调用成功

这里涉及到一些操作:

1、对于有参的mock,when(userDao.listUserName()).thenReturn(new ArrayList<>(Arrays.asList("a")));

2、对于无参的mock,doNothing().when(externalService).calculateUserInfo(anyList());

3、捕获入参:verify+ArgumentCaptor

​ 1)定义入参捕获器

​ 2)进行入参捕获

​ 3)获取捕获的入参

1、定义入参捕获器
@Captor 
private ArgumentCaptor<List<String>> listArgumentCaptor;
2、进行入参捕获
//入参捕获
verify(externalService).calculateUserInfo(listArgumentCaptor.capture());
3、获取捕获的入参
//获取捕获到的入参
//验证externalService.calculateUserInfo这里的入参是否符合预期
List<String> resultList = listArgumentCaptor.getValue();

4、验证结果:

assertEquals(resultList.get(0), "a");
assertEquals(resultList.get(1), "bb");
assertEquals(resultList.get(2), "999");
assertEquals(listArgumentCaptor.getValue().size(), 4);

完整代码:

@RunWith(MockitoJUnitRunner.class)
public class UserMockTest {

    //要使用 injectMock注解进行注入
    @InjectMocks
    private UserService userService;
    //
    @Mock
    private UserDao userDao;

    @Mock
    private ExternalService externalService;

    //用于捕获入参
    @Captor
    private ArgumentCaptor<List<String>> listArgumentCaptor;

    /**
     * 这里service先调用userDao,然后还要调用外部服务externalService
     * 要mock调两个  userDao 和rpc
     * 最后是想要得到acm调用的时候的入参,因为这个入参在service里面有对应的复杂计算
     *
     * 1、当mock一个对象,且执行此对象中的方法有返回值时,使用下面的方法:  when里面带上方法
     * 类名 对象 = mock (类名.class);
     * when (对象.方法 (参数)).thenReturn (方法的返回值);
     * 2、 当mock一个对象,且执行此对象中的方法没有返回值时,使用下面的方法:when里面只能是对象
     * 类名 对象 = Mockito.mock(类名.class); .when(对象).方法名();
     * doNothing().when(acmService).calculateUserInfo(anyList());
     */
    @Test
    public void testGetUserNameListSucceed() {
        //mock  dao.listUserName()有参返回
        when(userDao.listUserName()).thenReturn(new ArrayList<>(Arrays.asList("a")));
        //mock externalService.calculateUserInfo 无参void方法 ,注意:这里anyList只是打桩  跟你下面获取入参没有关系
        doNothing().when(externalService).calculateUserInfo(anyList());

				//调用业务方法:这里内部会调用上面的mock
        userService.getUserNameList(999L);

        //入参捕获
        verify(externalService).calculateUserInfo(listArgumentCaptor.capture());

        //获取捕获到的入参
        //验证externalService.calculateUserInfo这里的入参是否符合预期
        List<String> resultList = listArgumentCaptor.getValue();
        assertEquals(resultList.get(0), "a");
        assertEquals(resultList.get(1), "bb");
        assertEquals(resultList.get(2), "999");
        assertEquals(listArgumentCaptor.getValue().size(), 4);

    }
    
}

调用外部依赖失败

1、对于有参的mock,when(dao.listUserName()).thenThrow(new RuntimeException("mock exception"));

2、对于无参的mock,doThrow(new RuntimeException("mock exception")).when(externalService).calculateUserInfo(anyList());

3、try catch+assert验证结果:assertEquals("mock exception", e.getMessage());

/**
 * Mock Annotation
 * 测试:适用@Mock注解:@Mock 注解可以理解为对 mock 方法的一个替代。
 * 使用该注解时,要使用MockitoAnnotations.initMocks 方法,让注解生效, 比如放在@Before方法中初始化。 比较优雅优雅的写法是用MockitoJUnitRunner,它可以自动执行MockitoAnnotations.initMocks 方法。
 */
@RunWith(MockitoJUnitRunner.class)
public class UserMockTest {

    //要使用 injectMock注解进行注入
    @InjectMocks
    private UserService userService;
    @Mock
    private UserDao userDao;
    @Mock
    private ExternalService externalService;
    //用于捕获入参
    @Captor
    private ArgumentCaptor<List<String>> listArgumentCaptor;

    @Test
    public void testGetUserNameListFailed() {

        when(userDao.listUserName()).thenReturn(new ArrayList<>(Arrays.asList("a", "b", "c")));

        //有参的模拟异常
        //when(dao.listUserName()).thenThrow(new RuntimeException("mock exception") );

        //无参的异常抛出
        doThrow(new RuntimeException("mock exception")).when(externalService).calculateUserInfo(anyList());

        try {
            userService.getUserNameList(1232L);
        } catch (Exception e) {
            assertEquals("mock exception", e.getMessage());
        }

    }
}

推荐文章系列:

如何编写单元测试和实践TDD

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,我来为您解答。 在使用 JunitMockito 进行单元测试时,若出现空指针错误,可以通过调试测试函数来定位问题。下面是一个示例: 假设我们要测试的类是 `UserService`,其中有一个方法 `getUserById(int id)`,返回类型为 `User`。我们使用 Mockito 模拟 `UserDao` 对象,并在测试方法中调用 `getUserById()` 方法。 ```java public class UserServiceTest { @Mock private UserDao userDao; private UserService userService; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); userService = new UserService(userDao); } @Test public void testGetUserById() { int id = 1; User user = new User(id, "Tom"); Mockito.when(userDao.getUserById(id)).thenReturn(user); User result = userService.getUserById(id); Assert.assertEquals(user, result); } } ``` 如果在运行测试方法时出现空指针错误,我们可以在测试方法上打上断点,然后使用调试工具进入测试方法中进行调试。 在调试时,可能会遇到无法进入框架方法的情况。这是因为框架代码中的方法已经被编译为字节码,无法直接进入。但我们可以在调试时使用“Step Into”或“Step Over”等命令,来控制程序的执行流程,逐步定位问题。 在 Eclipse 中,可以使用如下调试过程: 1. 在测试方法上打上断点。 2. 右键单击测试类,选择“Debug As” -> “JUnit Test”。 3. 程序会在测试方法处停止,此时可以使用调试工具进行调试。 4. 在调试过程中,可以使用“Step Into”或“Step Over”等命令,逐步定位问题。 在 IDEA 中,可以使用如下调试过程: 1. 在测试方法上打上断点。 2. 右键单击测试类,选择“Debug” -> “Run 'TestName'”。 3. 程序会在测试方法处停止,此时可以使用调试工具进行调试。 4. 在调试过程中,可以使用“Step Into”或“Step Over”等命令,逐步定位问题。 希望这能帮助到您。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Apple_Web

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值