单元测试用例编写Java编程技巧之单元测试用例编写流程java单元测试用例怎么写




单元测试用例编写Java编程技巧之单元测试用例编写流程java单元测试用例怎么写

2022-07-21 2:23:50 网络知识 官方管理员



前言

上一篇文章《Java单元测试技巧之PowerMock》除了介绍单元测试基础知识外,主要介绍了“为什么要编写单元测试”。很多同学读完后,还是不能快速地编写单元测试用例。而这篇文章,立足于“如何来编写单元测试用例”,能够让同学们“有章可循”,能快速地编写出单元测试用例。

1.编写单元测试用例

1.1.测试框架简介

Mockito是一个单元测试模拟框架,可以让你写出优雅、简洁的单元测试代码。Mockito采用了模拟技术,模拟了一些在应用中依赖的复杂对象,从而把测试对象和依赖对象隔离开来。

PowerMock是一个单元测试模拟框架,是在其它单元测试模拟框架的基础上做出扩展。通过提供定制的类加载器以及一些字节码篡改技术的应用,PowerMock实现了对静态方法、构造方法、私有方法以及final方法的模拟支持等强大的功能。但是,正因为PowerMock进行了字节码篡改,导致部分单元测试用例并不被JaCoco统计覆盖率。

通过作者多年单元测试的编写经验,优先推荐使用Mockito提供的功能;只有在Mockito提供的功能不能满足需求时,才会采用PowerMock提供的功能;但是,不推荐使用影响JaCoco统计覆盖率的PowerMock功能。在本文中,我们也不会对影响JaCoco统计覆盖率的PowerMock功能进行介绍。

下面,将以Mockito为主、以PowerMock为辅,介绍一下如何编写单元测试用例。

1.2.测试框架引入

为了引入Mockito和PowerMock包,需要在maven项目的pom.xml文件中加入以下包依赖:

单元测试用例编写(Java编程技巧之单元测试用例编写流程)(1)

其中,powermock.version为2.0.9,为当前的最新版本,可根据实际情况修改。在PowerMock包中,已经包含了对应的Mockito和JUnit包,所以无需单独引入Mockito和JUnit包。

1.3.典型代码案例

一个典型的服务代码案例如下:

/***用户服务类*/@ServicepublicclassUserService{/**服务相关*//**用户DAO*/@AutowiredprivateUserDAOuserDAO;/**标识生成器*/@AutowiredprivateIdGeneratoridGenerator;/**参数相关*//**可以修改*/@Value("${userService.canModify}")privateBooleancanModify;/***创建用户**@paramuserCreate用户创建*@return用户标识*/publicLongcreateUser(UserVOuserCreate){//获取用户标识LonguserId=userDAO.getIdByName(userCreate.getName());//根据存在处理//根据存在处理:不存在则创建if(Objects.isNull(userId)){userId=idGenerator.next();UserDOcreate=newUserDO();create.setId(userId);create.setName(userCreate.getName());userDAO.create(create);}//根据存在处理:已存在可修改elseif(Boolean.TRUE.equals(canModify)){UserDOmodify=newUserDO();modify.setId(userId);modify.setName(userCreate.getName());userDAO.modify(modify);}//根据存在处理:已存在禁修改else{thrownewUnsupportedOperationException("不支持修改");}//返回用户标识returnuserId;}}

1.4.测试用例编写

采用Mockito和PowerMock单元测试模拟框架,编写的单元测试用例如下:

UserServiceTest.java:

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}

userCreateVO.json:

{"name":"test"}

userCreateDO.json:

{"id":1,"name":"test"}

userModifyDO.json:

{"id":1,"name":"test"}

通过执行以上测试用例,可以看到对源代码进行了100%的行覆盖。

2.测试用例编写流程

通过上一章编写Java类单元测试用例的实践,可以总结出以下Java类单元测试用例的编写流程:

单元测试用例编写(Java编程技巧之单元测试用例编写流程)(2)

单元测试用例编写流程

上面一共有3个测试用例,这里仅以测试用例testCreateUserWithNew(测试:创建用户-新)为例说明。

2.1.定义对象阶段

第1步是定义对象阶段,主要包括定义被测对象、模拟依赖对象(类成员)、注入依赖对象(类成员)3大部分。

2.1.1.定义被测对象

在编写单元测试时,首先需要定义被测对象,或直接初始化、或通过Spy包装……其实,就是把被测试服务类进行实例化。

/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;

2.1.2.模拟依赖对象(类成员)

在一个服务类中,我们定义了一些类成员对象——服务(Service)、数据访问对象(DAO)、参数(Value)等。在Spring框架中,这些类成员对象通过@Autowired、@Value等方式注入,它们可能涉及复杂的环境配置、依赖第三方接口服务……但是,在单元测试中,为了解除对这些类成员对象的依赖,我们需要对这些类成员对象进行模拟。

/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;

2.1.3.注入依赖对象(类成员)

当模拟完这些类成员对象后,我们需要把这些类成员对象注入到被测试类的实例中。以便在调用被测试方法时,可能使用这些类成员对象,而不至于抛出空指针异常。

/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}

2.2.模拟方法阶段

第2步是模拟方法阶段,主要包括模拟依赖对象(参数或返回值)、模拟依赖方法2大部分。

2.2.1.模拟依赖对象(参数或返回值)

通常,在调用一个方法时,需要先指定方法的参数,然后获取到方法的返回值。所以,在模拟方法之前,需要先模拟该方法的参数和返回值。

LonguserId=1L;

2.2.2.模拟依赖方法

在模拟完依赖的参数和返回值后,就可以利用Mockito和PowerMock的功能,进行依赖方法的模拟。如果依赖对象还有方法调用,还需要模拟这些依赖对象的方法。

//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextMockito.doReturn(userId).when(idGenerator).next();

2.3.调用方法阶段

第3步是调用方法阶段,主要包括模拟依赖对象(参数)、调用被测方法、验证参数对象(返回值)3步。

2.3.1.模拟依赖对象(参数)

在调用被测方法之前,需要模拟被测方法的参数。如果这些参数还有方法调用,还需要模拟这些参数的方法。

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}0

2.3.2.调用被测方法

在准备好参数对象后,就可以调用被测试方法了。如果被测试方法有返回值,需要定义变量接收返回值;如果被测试方法要抛出异常,需要指定期望的异常。

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}1

2.3.3.验证数据对象(返回值)

在调用被测试方法后,如果被测试方法有返回值,需要验证这个返回值是否符合预期;如果被测试方法要抛出异常,需要验证这个异常是否满足要求。

Assert.assertEquals("用户标识不一致",userId,/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}1);

2.4.验证方法阶段

第4步是验证方法阶段,主要包括验证依赖方法、验证数据对象(参数)、验证依赖对象3步。

2.4.1.验证依赖方法

作为一个完整的测试用例,需要对每一个模拟的依赖方法调用进行验证。

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}3

2.4.2.验证数据对象(参数)

对应一些模拟的依赖方法,有些参数对象是被测试方法内部生成的。为了验证代码逻辑的正确性,就需要对这些参数对象进行验证,看这些参数对象值是否符合预期。

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}4

2.4.3.验证依赖对象

作为一个完整的测试用例,应该保证每一个模拟的依赖方法调用都进行了验证。正好,Mockito提供了一套方法,用于验证模拟对象所有方法调用都得到了验证。

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}5

3.定义被测对象

在编写单元测试时,首先需要定义被测对象,或直接初始化、或通过Spy包装……其实,就是把被测试服务类进行实例化。

3.1.直接构建对象

直接构建一个对象,总是简单又直接。

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}6

3.2.利用Mockito.spy方法

Mockito提供一个spy功能,用于拦截那些尚未实现或不期望被真实调用的方法,默认所有方法都是真实方法,除非主动去模拟对应方法。所以,利用spy功能来定义被测对象,适合于需要模拟被测类自身方法的情况,适用于普通类、接口和虚基类。

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}7

3.3.利用@Spy注解

@Spy注解跟Mockito.spy方法一样,可以用来定义被测对象,适合于需要模拟被测类自身方法的情况,适用于普通类、接口和虚基类。@Spy注解需要配合@RunWith注解使用。

@RunWith(PowerMockRunner.class)publicclassCompanyServiceTest{@Spyprivate/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}6...}

注意:@Spy注解对象需要初始化。如果是虚基类或接口,可以用Mockito.mock方法实例化。

3.4.利用@InjectMocks注解

@InjectMocks注解用来创建一个实例,并将其它对象(@Mock、@Spy或直接定义的对象)注入到该实例中。所以,@InjectMocks注解本身就可以用来定义被测对象。@InjectMocks注解需要配合@RunWith注解使用。

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}9

4.模拟依赖对象

在编写单元测试用例时,需要模拟各种依赖对象——类成员、方法参数和方法返回值。

4.1.直接构建对象

如果需要构建一个对象,最简单直接的方法就是——定义对象并赋值。

LonguserId=1L;StringuserName="admin";UserDOuser=newUser();user.setId(userId);user.setName(userName);List<Long>userIdList=Arrays.asList(1L,2L,3L);

4.2.反序列化对象

如果对象字段或层级非常庞大,采用直接构建对象方法,可能会编写大量构建程序代码。这种情况,可以考虑反序列化对象,将会大大减少程序代码。由于JSON字符串可读性高,这里就以JSON为例,介绍反序列化对象。

反序列化模型对象:

{"name":"test"}1

反序列化集合对象:

{"name":"test"}2

反序列化映射对象:

{"name":"test"}3

4.3.利用Mockito.mock方法

Mockito提供一个mock功能,用于拦截那些尚未实现或不期望被真实调用的方法,默认所有方法都已被模拟——方法为空并返回默认值(null或0),除非主动执行doCallRealMethod或thenCallRealMethod操作,才能够调用真实的方法。

利用Mockito.mock方法模拟依赖对象,主要用于以下几种情形:

  1. 只使用类实例,不使用类属性;
  2. 类属性太多,但使用其中少量属性(可以mock属性返回值);
  3. 类是接口或虚基类,并不关心其具体实现类。
{"name":"test"}4

4.4.利用@Mock注解

@Mock注解跟Mockito.mock方法一样,可以用来模拟依赖对象,适用于普通类、接口和虚基类。@Mock注解需要配合@RunWith注解使用。

{"name":"test"}5

4.5.利用Mockito.spy方法

Mockito.spy方法跟Mockito.mock方法功能相似,只是Mockito.spy方法默认所有方法都是真实方法,除非主动去模拟对应方法。

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}7

4.6.利用@Spy注解

@Spy注解跟Mockito.spy方法一样,可以用来模拟依赖对象,适用于普通类、接口和虚基类。@Spy注解需要配合@RunWith注解使用。

@RunWith(PowerMockRunner.class)publicclassCompanyServiceTest{@Spyprivate/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}6...}

注意:@Spy注解对象需要初始化。如果是虚基类或接口,可以用Mockito.mock方法实例化。

5.注入依赖对象

当模拟完这些类成员对象后,我们需要把这些类成员对象注入到被测试类的实例中。以便在调用被测试方法时,可能使用这些类成员对象,而不至于抛出空指针异常。

5.1.利用Setter方法注入

如果类定义了Setter方法,可以直接调用方法设置字段值。

{"name":"test"}8

5.2.利用ReflectionTestUtils.setField方法注入

JUnit提供ReflectionTestUtils.setField方法设置属性字段值。

{"name":"test"}9

5.3.利用Whitebox.setInternalState方法注入

PowerMock提供Whitebox.setInternalState方法设置属性字段值。

{"id":1,"name":"test"}0

5.4.利用@InjectMocks注解注入

@InjectMocks注解用来创建一个实例,并将其它对象(@Mock、@Spy或直接定义的对象)注入到该实例中。@InjectMocks注解需要配合@RunWith注解使用。

{"id":1,"name":"test"}1

5.5.设置静态常量字段值

有时候,我们需要对静态常量对象进行模拟,然后去验证是否执行了对应分支下的方法。比如:需要模拟Lombok的@Slf4j生成的log静态常量。但是,Whitebox.setInternalState方法和@InjectMocks注解并不支持设置静态常量,需要自己实现一个设置静态常量的方法:

{"id":1,"name":"test"}2

具体使用方法如下:

{"id":1,"name":"test"}3

注意:经过测试,该方法对于int、Integer等基础类型并不生效,应该是编译器常量优化导致。

6.模拟依赖方法

在模拟完依赖的参数和返回值后,就可以利用Mockito和PowerMock的功能,进行依赖方法的模拟。如果依赖对象还有方法调用,还需要模拟这些依赖对象的方法。

6.1.根据返回模拟方法

6.1.1.模拟无返回值方法

{"id":1,"name":"test"}4

6.1.2.模拟方法单个返回值

{"id":1,"name":"test"}5

6.1.3.模拟方法多个返回值

直接列举出多个返回值:

{"id":1,"name":"test"}6

转化列表为多个返回值:

{"id":1,"name":"test"}7

6.1.4.模拟方法定制返回值

可利用Answer定制方法返回值:

{"id":1,"name":"test"}8

6.1.5.模拟方法抛出单个异常

指定单个异常类型:

{"id":1,"name":"test"}9

指定单个异常对象:

{"id":1,"name":"test"}0

6.1.6.模拟方法抛出多个异常

指定多个异常类型:

{"id":1,"name":"test"}1

指定多个异常对象:

{"id":1,"name":"test"}2

6.1.7.直接调用真实方法

{"id":1,"name":"test"}3

6.2.根据参数模拟方法

Mockito提供do-when语句和when-then语句模拟方法。

6.2.1.模拟无参数方法

对于无参数的方法模拟:

{"id":1,"name":"test"}4

6.2.2.模拟指定参数方法

对于指定参数的方法模拟:

{"id":1,"name":"test"}5

6.2.3.模拟任意参数方法

在编写单元测试用例时,有时候并不关心传入参数的具体值,可以使用Mockito参数匹配器的any方法。Mockito提供了anyInt、anyLong、anyString、anyList、anySet、anyMap、any(Classclazz)等方法来表示任意值。

{"id":1,"name":"test"}6

6.2.4.模拟可空参数方法

Mockito参数匹配器的any具体方法,并不能够匹配null对象。而Mockito提供一个nullable方法,可以匹配包含null对象的任意对象。此外,Mockito.any()方法也可以用来匹配可空参数。

{"id":1,"name":"test"}7

6.2.5.模拟必空参数方法

同样,如果要匹配null对象,可以使用isNull方法,或使用eq(null)。

{"id":1,"name":"test"}8

6.2.6.模拟不同参数方法

Mockito支持按不同的参数分别模拟同一方法。

{"id":1,"name":"test"}9

注意:如果一个参数满足多个模拟方法条件,会以最后一个模拟方法为准。

6.2.7.模拟可变参数方法

对于一些变长度参数方法,可以按实际参数个数进行模拟:

/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;0

也可以用Mockito.any()模拟一个通用匹配方法:

/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;1

注意:Mockito.<T>any()并不等于Mockito.any(Class<T>type),前者可以匹配null和类型T的可变参数,后者只能匹配T必填参数。

6.3.模拟其它特殊方法

6.3.1.模拟final方法

PowerMock提供对final方法的模拟,方法跟模拟普通方法一样。但是,需要把对应的模拟类添加到@PrepareForTest注解中。

/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;2

6.3.2.模拟私有方法

PowerMock提供提对私有方法的模拟,但是需要把私有方法所在的类放在@PrepareForTest注解中。

/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;3

6.3.3.模拟构造方法

PowerMock提供PowerMockito.whenNew方法来模拟构造方法,但是需要把使用构造方法的类放在@PrepareForTest注解中。

/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;4

6.3.4.模拟静态方法

PowerMock提供PowerMockito.mockStatic和PowerMockito.spy来模拟静态方法类,然后就可以模拟静态方法了。同样,需要把对应的模拟类添加到@PrepareForTest注解中。

/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;5

注意:第一种方式不适用于PowerMockito.spy模拟的静态方法类。

7.调用被测方法

在准备好参数对象后,就可以调用被测试方法了。

如果把方法按访问权限分类,可以简单地分为有访问权限无访问权限两种。但实际上,Java语言中提供了public、protected、private和缺失共4种权限修饰符,在不同的环境下又对应不同的访问权限。具体映射关系如下:

修饰符

本类

本包

子类

其它

public

protected

缺省

private

下面,将根据有访问权限无访问权限两种情况,来介绍如何调用被测方法。

7.1.调用构造方法

7.1.1.调用有访问权限的构造方法

可以直接调用有访问权限的构造方法。

/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;6

7.1.2.调用无访问权限的构造方法

调用无访问权限的构造方法,可以使用PowerMock提供的Whitebox.invokeConstructor方法。

/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;7

备注:该方法也可以调用有访问权限的构造方法,但是不建议使用。

7.2.调用普通方法

7.2.1.调用有访问权限的普通方法

可以直接调用有访问权限的普通方法。

/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;8

7.2.2.调用无权限访问的普通方法

调用无访问权限的普通方法,可以使用PowerMock提供的Whitebox.invokeMethod方法。

/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;9

也可以使用PowerMock提供Whitebox.getMethod方法和PowerMockito.method方法,可以直接获取对应类方法对象。然后,通过Method的invoke方法,可以调用没有访问权限的方法。

/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;0

备注:该方法也可以调用有访问权限的普通方法,但是不建议使用。

7.3.调用静态方法

7.3.1.调用有权限访问的静态方法

可以直接调用有访问权限的静态方法。

/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;1

7.3.2.调用无权限访问的静态方法

调用无权限访问的静态方法,可以使用PowerMock提供的Whitebox.invokeMethod方法。

/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;2

备注:该方法也可以调用有访问权限的静态方法,但是不建议使用。

8.验证依赖方法

在单元测试中,验证是确认模拟的依赖方法是否按照预期被调用或未调用的过程。Mockito提供了许多方法来验证依赖方法调用,给我们编写单元测试用例带来了很大的帮助。

8.1.根据参数验证方法调用

8.1.1.验证无参数方法调用

/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;3

8.1.2.验证指定参数方法调用

/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;4

8.1.3.验证任意参数方法调用

/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;5

8.1.4.验证可空参数方法调用

/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;6

8.1.5.验证必空参数方法调用

/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;7

8.1.6.验证可变参数方法调用

对于一些变长度参数方法,可以按实际参数个数进行验证:

/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;8

也可以用Mockito.any()进行通用验证:

/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;9

8.2.验证方法调用次数

8.2.1.验证方法默认调用1次

/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}0

8.2.2.验证方法从不调用

/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}1

8.2.3.验证方法调用n次

/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}2

8.2.4.验证方法调用至少1次

/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}3

8.2.5.验证方法调用至少n次

/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}4

8.2.2.验证方法调用最多1次

/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}5

8.2.6.验证方法调用最多n次

/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}6

8.2.7.验证方法调用指定n次

Mockito允许按顺序进行验证方法调用,未被验证到的方法调用将不会被标记为已验证。

/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}7

8.2.8.验证对象及其方法调用1次

用于验证对象及其方法调用1次,如果该对象还有别的方法被调用或者该方法调用了多次,都将导致验证方法调用失败。

/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}8

相当于:

/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}0Mockito.verifyNoMoreInteractions(userDAO);

8.3.验证方法调用并捕获参数值

Mockito提供ArgumentCaptor类来捕获参数值,通过调用forClass(Class<T>clazz)方法来构建一个ArgumentCaptor对象,然后在验证方法调用时来捕获参数,最后获取到捕获的参数值并验证。如果一个方法有多个参数都要捕获并验证,那就需要创建多个ArgumentCaptor对象。

ArgumentCaptor的主要接口方法:

  1. capture方法,用于捕获方法参数;
  2. getValue方法,用于获取捕获的参数值,如果捕获了多个参数值,该方法只返回最后一个参数值;
  3. getAllValues方法,用户获取捕获的所有参数值。

8.3.1.使用ArgumentCaptor.forClass方法定义参数捕获器

在测试用例方法中,直接使用ArgumentCaptor.forClass方法定义参数捕获器。

单元测试用例编写(Java编程技巧之单元测试用例编写流程)(3)

注意:定义泛型类的参数捕获器时,存在强制类型转化,会引起编译器警告。

8.3.2.使用@Captor注解定义参数捕获器

也可以用Mockito提供的@Captor注解,在测试用例类中定义参数捕获器。

LonguserId=1L;0

注意:定义泛型类的参数捕获器时,由于是Mockito自行初始化,不会引起编译器告警。

8.3.3.捕获多次方法调用的参数值列表

单元测试用例编写(Java编程技巧之单元测试用例编写流程)(4)

8.4.验证其它特殊方法

8.4.1.验证final方法调用

final方法的验证跟普通方法类似,这里不再累述。

8.4.2.验证私有方法调用

PowerMockito提供verifyPrivate方法验证私有方法调用。

LonguserId=1L;1

8.4.3.验证构造方法调用

PowerMockito提供verifyNew方法验证构造方法调用。

LonguserId=1L;2

8.4.4.验证静态方法调用

PowerMockito提供verifyStatic方法验证静态方法调用。

LonguserId=1L;3

9.验证数据对象

JUnit测试框架中Assert类就是断言工具类,主要验证单元测试中实际数据对象与期望数据对象一致。在调用被测方法时,需要对返回值和异常进行验证;在验证方法调用时,也需要对捕获的参数值进行验证。

9.1.验证数据对象空值

9.1.1.验证数据对象为空

通过JUnit提供的Assert.assertNull方法验证数据对象为空。

LonguserId=1L;4

9.1.2.验证数据对象非空

通过JUnit提供的Assert.assertNotNull方法验证数据对象非空。

LonguserId=1L;5

9.2.验证数据对象布尔值

9.2.1.验证数据对象为真

通过JUnit提供的Assert.assertTrue方法验证数据对象为真。

LonguserId=1L;6

9.2.2.验证数据对象为假

通过JUnit提供的Assert.assertFalse方法验证数据对象为假。

LonguserId=1L;7

9.3.验证数据对象引用

在单元测试用例中,对于一些参数或返回值对象,不需要验证对象具体取值,只需要验证对象引用是否一致。

9.3.1.验证数据对象一致

JUnit提供的Assert.assertSame方法验证数据对象一致。

LonguserId=1L;8

9.3.1.验证数据对象不一致

JUnit提供的Assert.assertNotSame方法验证数据对象一致。

LonguserId=1L;9

9.4.验证数据对象值

JUnit提供Assert.assertEquals、Assert.assertNotEquals、Assert.assertArrayEquals方法组,可以用来验证数据对象值是否相等。

9.4.1.验证简单数据对象

对于简单数据对象(比如:基础类型、包装类型、实现了equals的数据类型……),可以直接通过JUnit的Assert.assertEquals和Assert.assertNotEquals方法组进行验证。

//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextMockito.doReturn(userId).when(idGenerator).next();0

9.4.2.验证简单数组或集合对象

对于简单数组对象(比如:基础类型、包装类型、实现了equals的数据类型……),可以直接通过JUnit的Assert.assertArrayEquals方法组进行验证。对于简单集合对象,也可以通过Assert.assertEquals方法验证。

//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextMockito.doReturn(userId).when(idGenerator).next();1

9.4.3.验证复杂数据对象

对于复杂的JavaBean数据对象,需要验证JavaBean数据对象的每一个属性字段。

//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextMockito.doReturn(userId).when(idGenerator).next();2

9.4.4.验证复杂数组或集合对象

对于复杂的JavaBean数组和集合对象,需要先展开数组和集合对象中每一个JavaBean数据对象,然后验证JavaBean数据对象的每一个属性字段。

//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextMockito.doReturn(userId).when(idGenerator).next();3

9.4.5.通过序列化验证数据对象

如上一节例子所示,当数据对象过于复杂时,如果采用Assert.assertEquals依次验证每个JavaBean对象、验证每一个属性字段,测试用例的代码量将会非常庞大。这里,推荐使用序列化手段简化数据对象的验证,比如利用JSON.toJSONString方法把复杂的数据对象转化为字符串,然后再使用Assert.assertEquals方法进行验证字符串。但是,序列化值必须具备有序性、一致性和可读性。

//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextMockito.doReturn(userId).when(idGenerator).next();4

通常使用JSON.toJSONString方法把Map对象转化为字符串,其中key-value的顺序具有不确定性,无法用于验证两个对象是否一致。这里,JSON提供序列化选项SerializerFeature.MapSortField(映射排序字段),可以用于保证序列化后的key-value的有序性。

//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextMockito.doReturn(userId).when(idGenerator).next();5

9.4.6.验证数据对象私有属性字段

有时候,单元测试用例需要对复杂对象的私有属性字段进行验证。而PowerMockito提供的Whitebox.getInternalState方法,获取轻松地获取到私有属性字段值。

//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextMockito.doReturn(userId).when(idGenerator).next();6

9.5.验证异常对象内容

异常作为Java语言的重要特性,是Java语言健壮性的重要体现。捕获并验证异常数据内容,也是测试用例的一种。

9.5.1.通过@Test注解验证异常对象

JUnit的注解@Test提供了一个expected属性,可以指定一个期望的异常类型,用来捕获并验证异常。但是,这种方式只能验证异常类型,并不能验证异常原因和消息。

//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextMockito.doReturn(userId).when(idGenerator).next();7

9.5.2.通过@Rule注解验证异常对象

如果想要验证异常原因和消息,就需求采用@Rule注解定义ExpectedException对象,然后在测试方法的前面声明要捕获的异常类型、原因和消息。

//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextMockito.doReturn(userId).when(idGenerator).next();8

9.5.3.通过Assert.assertThrows验证异常对象

在最新版的JUnit中,提供了一个更为简洁的异常验证方式——Assert.assertThrows方法。

//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextMockito.doReturn(userId).when(idGenerator).next();9

10.验证依赖对象

10.1.验证模拟对象没有任何方法调用

Mockito提供了verifyNoInteractions方法,可以验证模拟对象在被测试方法中没有任何调用。

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}00

10.2.验证模拟对象没有更多方法调用

Mockito提供了verifyNoMoreInteractions方法,在验证模拟对象所有方法调用后使用,可以验证模拟对象所有方法调用是否都得到验证。如果模拟对象存在任何未验证的方法调用,就会抛出NoInteractionsWanted异常。

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}01

备注:Mockito的verifyZeroInteractions方法与verifyNoMoreInteractions方法功能相同,但是目前前者已经被废弃。

10.3.清除模拟对象所有方法调用标记

在编写单元测试用例时,为了减少单元测试用例数和代码量,可以把多组参数定义在同一个单元测试用例中,然后用for循环依次执行每一组参数的被测方法调用。为了避免上一次测试的方法调用影响下一次测试的方法调用验证,最好使用Mockito提供clearInvocations方法清除上一次的方法调用。

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}02

11.典型案例分析

这里,只收集了几个经典案例,解决了特定环境下的特定问题。

11.1.测试框架特性导致问题

在编写单元测试用例时,或多或少会遇到一些问题,大多数是由于对测试框架特性不熟悉导致,比如:

  1. Mockito不支持对静态方法、构造方法、final方法、私有方法的模拟;
  2. Mockito的any相关的参数匹配方法并不支持可空参数和空参数;
  3. 采用Mockito的参数匹配方法时,其它参数不能直接用常量或变量,必须使用Mockito的eq方法;
  4. 使用when-then语句模拟Spy对象方法会先执行真实方法,应该使用do-when语句;
  5. PowerMock对静态方法、构造方法、final方法、私有方法的模拟需要把对应的类添加到@PrepareForTest注解中;
  6. PowerMock模拟JDK的静态方法、构造方法、final方法、私有方法时,需要把使用这些方法的类加入到@PrepareForTest注解中,从而导致单元测试覆盖率不被统计;
  7. PowerMock使用自定义的类加载器来加载类,可能导致系统类加载器认为有类型转化问题;需要加上@PowerMockIgnore({"javax.crypto.*"})注解,来告诉PowerMock这个包不要用PowerMock的类加载器加载,需要采用系统类加载器来加载。
  8. ……

对于这些问题,可以根据提示信息查阅相关资料解决,这里就不再累述了。

11.2.捕获参数值已变更问题

在编写单元测试用例时,通常采用ArgumentCaptor进行参数捕获,然后对参数对象值进行验证。如果参数对象值没有变更,这个步骤就没有任何问题。但是,如果参数对象值在后续流程中发生变更,就会导致验证参数值失败。

原始代码:

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}03

测试用例:

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}04

问题现象:

执行单元测试用例失败,抛出以下异常信息:

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}05

问题原因:

由于参数dataList在调用dataStorage.test方法后,都被主动调用dataList.clear方法进行清空。由于ArgumentCaptor捕获的是对象引用,所以最后捕获到了同一个空列表。

解决方案:

可以在模拟依赖方法dataStorage.test时,保存传入参数的当前值进行验证。代码如下:

单元测试用例编写(Java编程技巧之单元测试用例编写流程)(5)

11.3.模拟Lombok的log对象问题

Lombok的@Slf4j注解,广泛地应用于Java项目中。在某些代码分支里,可能只有log记录日志的操作,为了验证这个分支逻辑被正确执行,需要在单元测试用例中对log记录日志的操作进行验证。

原始方法:

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}06

测试用例:

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}07

问题现象:

执行单元测试用例失败,抛出以下异常信息:

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}08

原因分析:

经过调式跟踪,发现ExampleService中的log对象并没有被注入。通过编译发现,Lombok的@Slf4j注解在ExampleService类中生成了一个静态常量log,而@InjectMocks注解并不支持静态常量的注入。

解决方案:

采用作者实现的FieldHelper.setStaticFinalField方法,可以实现对静态常量的注入模拟对象。

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}09

11.4.兼容Pandora等容器问题

阿里巴巴的很多中间件,都是基于Pandora容器的,在编写单元测试用例时,可能会遇到一些坑。

原始方法:

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}10

测试用例:

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}11

问题现象:

执行单元测试用例失败,抛出以下异常信息:

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}12

原因分析:

基于Pandora容器的中间件,需要使用Pandora容器加载。在上面测试用例中,使用了PowerMock容器加载,从而导致抛出类加载异常。

解决方案:

首先,把PowerMockRunner替换为PandoraBootRunner。其次,为了使@Mock、@InjectMocks等Mockito注解生效,需要调用MockitoAnnotations.initMocks(this)方法进行初始化。

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}13

12.消除类型转换警告

在编写测试用例时,特别是泛型类型转换时,很容易产生类型转换警告。常见类型转换警告如下:

单元测试用例编写(Java编程技巧之单元测试用例编写流程)(6)

作为一个有代码洁癖的轻微强迫症程序员,是绝对不容许这些类型转换警告产生的。于是,总结了以下方法来解决这些类型转换警告。

12.1.利用注解初始化

Mockito提供@Mock注解来模拟类实例,提供@Captor注解来初始化参数捕获器。由于这些注解实例是通过测试框架进行初始化的,所以不会产生类型转换警告。

问题代码:

单元测试用例编写(Java编程技巧之单元测试用例编写流程)(7)

建议代码:

单元测试用例编写(Java编程技巧之单元测试用例编写流程)(8)

12.2.利用临时类或接口

我们无法获取泛型类或接口的class实例,但是很容易获取具体类的class实例。这个解决方案的思路是——先定义继承泛型类的具体子类,然后mock、spy、forClass以及any出这个具体子类的实例,然后把具体子类实例转换为父类泛型实例。

问题代码:

单元测试用例编写(Java编程技巧之单元测试用例编写流程)(9)

建议代码:

单元测试用例编写(Java编程技巧之单元测试用例编写流程)(10)

12.3.利用CastUtils.cast方法

SpringData包中提供一个CastUtils.cast方法,可以用于类型的强制转换。这个解决方案的思路是——利用CastUtils.cast方法屏蔽类型转换警告。

问题代码:

单元测试用例编写(Java编程技巧之单元测试用例编写流程)(11)

建议代码:

单元测试用例编写(Java编程技巧之单元测试用例编写流程)(12)

这个解决方案,不需要定义注解,也不需要定义临时类或接口,能够让测试用例代码更为精简,所以作者重点推荐。如果不愿意引入SpringData包,也可以自己参考实现该方法,只是该方法会产生类型转换警告。

注意:CastUtils.cast方法本质是——先转换为Object类型,再强制转换对应类型,本身不会对类型进行校验。所以,CastUtils.cast方法好用,但是不要乱用,否则就是大坑(只有执行时才能发现问题)。

12.4.利用类型自动转换

在Mockito中,提供形式如下的方法——泛型类型只跟返回值有关,而跟输入参数无关。这样的方法,可以根据调用方法的参数类型自动转换,而无需手动强制类型转换。如果手动强制类型转换,反而会产生类型转换警告。

单元测试用例编写(Java编程技巧之单元测试用例编写流程)(13)

问题代码:

单元测试用例编写(Java编程技巧之单元测试用例编写流程)(14)

建议代码:

单元测试用例编写(Java编程技巧之单元测试用例编写流程)(15)

其实,SpringData的CastUtils.cast方法之所以这么强悍,也是采用了类型自动转化方法。

12.5.利用doReturn-when语句代替when-thenReturn语句

Mockito的when-thenReturn语句需要对返回类型强制校验,而doReturn-when语句不会对返回类型强制校验。利用这个特性,可以利用doReturn-when语句代替when-thenReturn语句解决类型转换警告。

问题代码:

单元测试用例编写(Java编程技巧之单元测试用例编写流程)(16)

建议代码:

单元测试用例编写(Java编程技巧之单元测试用例编写流程)(17)

12.6.利用instanceof关键字

JDK提供的Method.invoke方法返回的是Object类型,转化为具体类型时需要强制转换,会产生类型转换警告。而PowerMock提供的Whitebox.invokeMethod方法返回类型可以自动转化,不会产生类型转换警告。

问题代码:

单元测试用例编写(Java编程技巧之单元测试用例编写流程)(18)

建议代码:

单元测试用例编写(Java编程技巧之单元测试用例编写流程)(19)

12.7.利用instanceof关键字

在具体类型强制转换时,建议利用instanceof关键字先判断类型,否则会产生类型转换警告。

/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}14

建议代码:

if(objectinstanceofJSONArray){/***用户服务测试类*/@RunWith(PowerMockRunner.class)publicclassUserServiceTest{/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}/***测试:创建用户-新*/@TestpublicvoidtestCreateUserWithNew(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextLonguserId=1L;Mockito.doReturn(userId).when(idGenerator).next();//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptor<UserDO>userCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);}/***测试:创建用户-旧*/@TestpublicvoidtestCreateUserWithOld(){//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:userDAO.modifyArgumentCaptor<UserDO>userModifyCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userModifyCaptor.capture());text=ResourceHelper.getResourceAsString(getClass(),"userModifyDO.json");Assert.assertEquals("用户修改不一致",text,JSON.toJSONString(userModifyCaptor.getValue()));//验证依赖对象Mockito.verifyNoInteractions(idGenerator);Mockito.verifyNoMoreInteractions(userDAO);}/***测试:创建用户-异常*/@TestpublicvoidtestCreateUserWithException(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.FALSE);//模拟依赖方法//模拟依赖方法:userDAO.getByNameLonguserId=1L;Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());//调用被测方法Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);UnsupportedOperationExceptionexception=Assert.assertThrows("返回异常不一致",UnsupportedOperationException.class,()->userService.createUser(userCreate));Assert.assertEquals("异常消息不一致","不支持修改",exception.getMessage());}}14}

12.8.利用Class.cast方法

在泛型类型强制转换时,会产生类型转换警告。可以采用泛型类的cast方法转换,从而避免产生类型转换警告。

问题代码:

单元测试用例编写(Java编程技巧之单元测试用例编写流程)(20)

建议代码:

单元测试用例编写(Java编程技巧之单元测试用例编写流程)(21)

12.9.避免不必要的类型转换

有时候,没有必要进行类型转换,就尽量避免类型转换。比如:把Object类型转换为具体类型,但又把具体类型当Object类型使用,就没有必要进行类型转换。像这种情况,可以合并表达式或定义基类变量,从而避免不必要的类型转化。

问题代码:

单元测试用例编写(Java编程技巧之单元测试用例编写流程)(22)

建议代码:

单元测试用例编写(Java编程技巧之单元测试用例编写流程)(23)


发表评论:

最近发表
网站分类
标签列表