【单例测试】Mockito实战

3 篇文章 0 订阅

前面我们提到了《【单元测试】一文读懂java单元测试》
简单介绍了《【单元测试】单元测试之Mockito的使用》

今天一起来看看项目中mockito怎么用的

项目源码GitHub

一、项目介绍

在这里插入图片描述
这个案例比较简单,模拟了一个数据统计系统,由地推员输入客户的姓名和手机号,系统根据客户的手机号和归属地和所属的运营商将客户群体分组,分配给相应的销售组,最后构建用户对象存入数据表。

二、业务代码

2.1 导入依赖

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.8.2</version>
</dependency>
<!--        <dependency>-->
<!--            <groupId>org.mockito</groupId>-->
<!--            <artifactId>mockito-core</artifactId>-->
<!--            <version>4.3.1</version>-->
<!--        </dependency>-->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <version>4.3.1</version>
    <scope>test</scope>
</dependency>

注意:mockito-inlinemockito-core不能同时打开

2.2 entity

PhoneNumber :

public class PhoneNumber {
    private final String number;
    private final String pattern = "^0?[1-9]{2,3}-?\\d{8}$";

    public String getNumber() {
        return number;
    }

    //在含参构造器中进行参数校验
    public PhoneNumber (String number) throws ValidationException {
        if (number == null) {
            throw new ValidationException("number 不能为空");
        } else if (isValid(number)) {
            throw new ValidationException("number 格式错误");
        }
        this.number = number;
    }

    private boolean isValid(String number) {
        return number.matches(pattern);
    }

    private static String getAreaCode(String number) {
        //具体实现逻辑
        return "a";
    }

    private static String getOperatorCode(String number) {
        //具体实现逻辑
        return "b";
    }
}

SalesRep:

public class SalesRep {
    private String repId;

    public SalesRep(String repId) {
        this.repId = repId;
    }

    public String getRepId() {
        return repId;
    }

    public void setRepId(String repId) {
        this.repId = repId;
    }
}

User:

public class User {
    private String name;
    private String phone;
    private String repId;

    public User() {
    }

    public User(String name, String phone, String repId) {
        this.name = name;
        this.phone = phone;
        this.repId = repId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getRepId() {
        return repId;
    }

    public void setRepId(String repId) {
        this.repId = repId;
    }
}

2.3 Dao

UserDao :

public class UserDao {

    public User save(String name, String phone, String repId) throws SQLException {
        return new User(name, phone, repId);
    }
}

SalesDao:

public class SalesDao {

    public SalesRep findRep(String areaCode, String operatorCode) {
        if ("a".equals(areaCode) && "b".equals(operatorCode)) {
            return new SalesRep("Echo");
        }
        return null;
    }
}

2.4 业务代码

RegistrationServiceImpl:

public class RegistrationServiceImpl implements RegistrationService {

    SalesDao salesDao = new SalesDao();
    UserDao userDao = new UserDao();

    @Override
    public User register(String name, String phone) throws Exception {
        // 参数校验
        if (name == null || name.length() == 0) {
            throw new ValidationException("number 不能为空");
        }
        if (phone == null || !isValid(phone)) {
            throw new ValidationException("phone 格式错误");
        }
        // 获取手机号归属地编号和运营商编号 然后通过编号找到区域内是 SalesRep
        String areaCode = FindUtils.getAreaCode(phone);
        String operatorCode = FindUtils.getOperatorCode(phone);

        User user;
        try {
            SalesRep rep = salesDao.findRep(areaCode, operatorCode);

            // 最后创建用户,落盘,然后返回
            user = userDao.save(name, phone, rep.getRepId());
        } catch (SQLException e) {
            throw new DAOException("SQLException thrown " + e.getMessage());
        }
        return user;
    }

    private boolean isValid(String phone) {
        String pattern = "^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\\d{8}$";
        boolean flag = phone.matches(pattern);
        return flag;
    }

}

其他代码见GitHub

三、单元测试

3.1 生成Test方法

1)点击我们需要生成测试的类,右击鼠标Generate->Test
在这里插入图片描述

2)选择对应的Junit版本和测试方法

在这里插入图片描述
3)这样在test目录下生成测试类
在这里插入图片描述

3.2 引入测试类

使用@Spy注解引入RegistrationServiceImpl这个待测试的类

在这里插入图片描述

这里有两个数据库操作salesDao.findRep和userDao.save,现在模拟salesDao正例操作,userDao反例操作,抛出异常

在这里插入图片描述

首先对userDao进行mock,模拟出对象,进行打桩

在这里插入图片描述
@InjectMocks注解的作用是将Mock出来的UserDao对象注入到RegistrationServiceImpl类当中

3. 3 测试前准备

引入@BeforeEach前置处理器,开启@Spy和@Mock

@BeforeEach
void setUp(){
    MockitoAnnotations.openMocks(this);
}

3.4 测试

3.4.1 name和phone参数校验

在这里插入图片描述
如果是空就或者0抛出异常

@Test
void register() throws Exception {
    String name = null;
    String phone = "15012345678";
    try {
        registrationService.register(name, phone);
        Assertions.fail("这里说明程序挂了");// 如果执行代码能快速定位
    } catch (Exception e) {
        Assertions.assertTrue(e instanceof ValidationException);
    }
}

这里name为null,会抛出异常,catch会捕捉到异常,通过 Assertions.assertTrue去接收异常,Assertions.fail(“这里说明程序挂了”);用于定位异常;

在这里插入图片描述
鼠标右击第三个,以单元测试覆盖路运行

在这里插入图片描述
运行结果,单元测试行覆盖率只有27%,我们只测试了26、27行,绿色代表我们覆盖了的地方,红色代表我们没有覆盖的地方。

接下来,我们对phone这个参数进行测试,

@Test
void register() throws Exception {
    String name = null;
    String phone = "15012345678";
    try {
        registrationService.register(name, phone);
        Assertions.fail("这里说明程序挂了");// 如果执行代码能快速定位
    } catch (Exception e) {
        Assertions.assertTrue(e instanceof ValidationException);
    }

    name ="Hanson";
    phone = null;
    try {
        registrationService.register(name, phone);
        Assertions.fail("这里说明程序挂了");// 如果执行代码能快速定位
    } catch (Exception e) {
        Assertions.assertTrue(e instanceof ValidationException);
    }
}

测试:

在这里插入图片描述

单元测试行覆盖率变成了38%,

3.4.2 测试数据库访问

@Test
void register() throws Exception {
    String name = null;
    String phone = "15012345678";
    try {
        registrationService.register(name, phone);
        Assertions.fail("这里说明程序挂了");// 如果执行代码能快速定位
    } catch (Exception e) {
        Assertions.assertTrue(e instanceof ValidationException);
    }

    name ="Hanson";
    phone = null;
    try {
        registrationService.register(name, phone);
        Assertions.fail("这里说明程序挂了");// 如果执行代码能快速定位
    } catch (Exception e) {
        Assertions.assertTrue(e instanceof ValidationException);
    }

    phone = "15012345678";
    MockedStatic<FindUtils> staticService = Mockito.mockStatic(FindUtils.class);
    staticService.when(() -> FindUtils.getAreaCode("15012345678")).thenReturn("a");// 可以返回具体的操作
    staticService.when(() -> FindUtils.getOperatorCode("15012345678")).thenReturn("b");// 可以返回具体的操作

    // 数据库操作正常
    Mockito.when(salesDao.findRep("a","b")).thenCallRealMethod();
    Mockito.when(userDao.save(name,phone,"Hanson")).thenCallRealMethod();
    User user = registrationService.register(name, phone);
    Assertions.assertEquals("Hanson",user.getRepId());
}

测试结果:
在这里插入图片描述
单元测试行覆盖率从38%变成了88%,为什么不是100%呢?那是因为这些都是正例,所有catcah没有捕获到异常

3.4.3 数据库反例

我们通过thenThrow()方法返回SQLException模拟异常,通过catch捕捉异常进行断言

@Test
void register() throws Exception {
    String name = null;
    String phone = "15012345678";
    try {
        registrationService.register(name, phone);
        Assertions.fail("这里说明程序挂了");// 如果执行代码能快速定位
    } catch (Exception e) {
        Assertions.assertTrue(e instanceof ValidationException);
    }

    name ="Hanson";
    phone = null;
    try {
        registrationService.register(name, phone);
        Assertions.fail("这里说明程序挂了");// 如果执行代码能快速定位
    } catch (Exception e) {
        Assertions.assertTrue(e instanceof ValidationException);
    }

    phone = "15012345678";
    MockedStatic<FindUtils> staticService = Mockito.mockStatic(FindUtils.class);
    staticService.when(() -> FindUtils.getAreaCode("15012345678")).thenReturn("a");// 可以返回具体的操作
    staticService.when(() -> FindUtils.getOperatorCode("15012345678")).thenReturn("b");// 可以返回具体的操作

    // 数据库操作正常
    Mockito.when(salesDao.findRep("a","b")).thenCallRealMethod();
    Mockito.when(userDao.save(name,phone,"Hanson")).thenCallRealMethod();
    User user = registrationService.register(name, phone);
    Assertions.assertEquals("Hanson",user.getRepId());

    // 数据库操作异常
    Mockito.when(userDao.save(name,phone,"Hanson")).thenThrow(new SQLException());
    try {
        registrationService.register(name,phone);
    } catch (Exception e) {
        Assertions.assertTrue(e instanceof DAOException);
    }

测试:

在这里插入图片描述
单元测试方法覆盖率和行覆盖率都是100%,完毕

总结

首先,确定测试类,对测试类进行有引入,其次确定是否有其他对象的注入,例如demo中的salesDao和userDao两个对象,还有可能是一些Service,我们需要用@Spy@Mock两个注解将对象注入进来,并在测试类对象是添加@InjectMocks将对象注入到测试类对象中;

其次我们要开启mock和spy方法(使用前置处理器,MockitoAnnotations.openMocks(this);

测试方法正反例写,静态方法使用Mockito.mockStatic方法将静态方法所属的类mock出来,在对这个方法进行打桩;

对于try-catch,对结果进行分类,以异常捕获和未捕获进行校验;

Jnuit文章见《【单元测试】一文读懂java单元测试》
单元测试之Mockito见文章《【单元测试】单元测试之Mockito的使用》
觉得有用的话还请来个三连!!!

  • 15
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值