單元測(cè)試--Java
使用mockito+powermock進(jìn)行java單元測(cè)試
實(shí)例
如下一個(gè)正常業(yè)務(wù)代碼嫡意,接下來就對(duì)HelloController.say方法進(jìn)行單元測(cè)試用例代碼開發(fā)。
業(yè)務(wù)代碼
public class HelloController{
@Autowired
private HelloService helloService;
@Autowired
private UserService userService;
@GetMapping("/say")
public String say(Integer id){
HelloUser user=userService.findById(id);
if(user==null){
throw new RuntimeException("user not found");
}
return helloService.doHello(user);
}
}
一個(gè)正常用例
在HelloController.say()個(gè)方法體內(nèi),引用了另外兩個(gè)Bean,userService和helloService。
正常服務(wù)啟動(dòng),我們是必須要有這兩個(gè)Bean的實(shí)例。但是在測(cè)試用例代碼中其實(shí)我們是沒必要知道UserService和和HelloService的具體實(shí)現(xiàn)的茬祷,所以這兩個(gè)Bean我們可以通過mock的方式虛擬出來。
@RunWith(MockitoJUnitRunner.class)
public class TestHelloController{
@InjectMock
private HelloController helloController;
@Mock
private HelloService helloService;
@Mock
private UserService userService;
/**
* 正常用例
**/
@Test
public void testSay(){
Integer id = 1;
HelloUser user = new HlloUser();
//模擬方法調(diào)用的傳參和返回值
Mockito.when(userService.findById(id)).thenReturn(user);
Mockito.when(helloService.doHello(user)).thenReturn("say hi!");
String resp=helloController.say(id);
//驗(yàn)證方法調(diào)用次數(shù)
Mockito.verfiy(userService,times(1)).findById(id);
Mockito.verfiy(helloService,times(1)).doHello(user);
//驗(yàn)證結(jié)果
Assert.assertTrue(resp.equals("say hi!"));
}
/**
* 異常用例
**/
@Test(expected=RuntimeException.class)
public void testSayException(){
//這里模擬userService調(diào)用findById無論傳入什么參數(shù)并蝗,返回都是null
Mockito.when(userService.findById(Mockito.anyString())).thenReturn(null);
Integer id = 1;
helloController.say(id);
}
}
靜態(tài)方法模擬
一些方法中有時(shí)候需要調(diào)用靜態(tài)方法(如HelloController.create方法)祭犯,但是靜態(tài)方法內(nèi)部,也不是該用例需要關(guān)心的滚停,這個(gè)使用可以使用powermock對(duì)靜態(tài)方法進(jìn)行mock沃粗。
//業(yè)務(wù)代碼
public class HelloController{
//...省略部分代碼
@GetMapping("/create")
public String create(HelloUser user){
user.setNo(BusinessNoUtils.generateNo());
userService.save(user);
return user.getNo();
}
}
//用例代碼--因?yàn)檫@里使用了Powermock,所以這里運(yùn)行器一定是要使用PowerMockRunner.class
@Runwith(PowerMockRunner.class)
@PrepareForTest(BusinessNoUtils.class)
public class TestHelloController(){
//...省略部分代碼
@Test
public void testCreate(){
HelloUser user= new HelloUser();
//需要聲明键畴,模擬靜態(tài)方法
PowerMockito.mockStatic(BusinessNoUtils.class);
Mockito.when(BusinessNoUtils.generateNo()).thenReturn("111111");
Mockito.when(userService.save(Mockito.argThat(e->{
//簡單判斷傳入?yún)?shù)是否正確
if(e.getNo().equals("111111")){
return true;
}
return false;
})));
String resp=helloController.create(user);
Assert.assertTrue(resp.equals("111111"));
}
}
new對(duì)象模擬
有一些業(yè)務(wù)代碼中需要?jiǎng)?chuàng)建一個(gè)對(duì)象最盅,這個(gè)對(duì)象中有些操作也沒必要在業(yè)務(wù)代碼中體現(xiàn),而且這個(gè)對(duì)象可能會(huì)依賴其他東西起惕。對(duì)于這種也可以通過Powermock的方式進(jìn)行模擬涡贱。
// 業(yè)務(wù)代碼
public HelloController{
//...省略其他代碼
public String copyUser(String id){
HelloUser helloUser=userService.findById(id);
HelloUser destUser = new HelloUser();
String userNo=destUser.copyFrom(helloUser);
return userNo;
}
}
//用例代碼
@Runwith(PowerMockRunner.class)
//這里的PrepareForTest引用的類是要 new 對(duì)象所在的類,這里要注意跟模擬靜態(tài)方法不一樣的地方
@PrepareForTest(HelloController.class)
public TestHelloController{
//...省略其他代碼
@Test
public void testCopyUser(){
HelloUser rawUser = new HelloUser();
rawUser.setName("123");
Mockito.when(userService.findById(1)).thenReturn(rawUser);
HelloUser destUser = Mockito.mock(HelloUser.class);//模擬對(duì)象
//需要聲明new對(duì)象返回值
PowerMockito.whenNew(HelloUser.class).withNoArguments().thenReturn(destUser);
//模擬copyFrom方法
Mockito.when(destUser.copyFrom(rawUser)).thenReturn("123");
String taskNo=controller.copyUser(1);
Mockito.verfiy(taskNo.equals("123"));
}
}
@Spy的使用
上面的代碼是因?yàn)橐故居美窍耄院芏鄻I(yè)務(wù)邏輯是寫在Controller層的问词。但實(shí)際業(yè)務(wù)開發(fā)中,Controller層只是薄薄的一層嘀粱,沒有什么業(yè)務(wù)邏輯激挪,但Controller層又是整個(gè)用例的入口。這個(gè)時(shí)候锋叨,我們不希望UserService在該用例中被模擬垄分,而是希望這個(gè)用例下來,整條鏈路上我們自己寫的代碼都能覆蓋到娃磺。這個(gè)時(shí)候我們就需要@Spy注解了锋喜。
//業(yè)務(wù)代碼
public class HelloContrller{
@Autowired
UserService userService;
@GetMapping("/delete/{id}")
public boolean deleteUser(@PathVariable("id")String id){
return userService.deleteById(id);
}
}
public class UserService{
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
public boolean deleteById(String id){
HelloUser userInfo=Optional.ofNullable(userMapper.findById(id)).orElseThrow(()->new RuntionException("not found"));
//空方法
userMapper.deleteByNo(userInfo.getNo);
roleService.deleteByUserId(id);
return true;
}
}
public class RoleService{
@Autowired
private RoleMapper roleMapper;
public boolean deleteByUserId(String userId){
return roleMapper.deleteByUserId(userId);
}
}
//用例代碼
@RunWith(MockitoJUnitRunner.class)
public class TestHelloController{
@InjectMocks HelloController helloController;
@Spy UserService userService ;
@Spy RoleService roleService ;
//dao層繼續(xù)模擬
@Mock RoleMapper roleMapper;
@Mock UserMapper userMapper;
@Before
public void setUp(){
//由于沒有自動(dòng)注入,所以手動(dòng)set進(jìn)去
WhiteBox.setInternalState(userService,"userMapper",userMapper);
WhiteBox.setInternalState(userService,"roleService",roleService);
WhiteBox.setInternalState(roleService,"roleMapper",roleMapper);
}
public void testDelete(){
String userId = "1";
UserInfo userInfo = new UserInfo();
userInfo.setNo("123");
Mockito.when(userMapper.findById(userId)).thenReturn(userInfo);
//對(duì)于空方法調(diào)用處理
Mockito.doNothing(userMapper).when(deleteByNo("123"));
Mockito.when(roleMapper.deleteByUserId(uesrId)).thenReturn(true);
Assert.assertTrue(helloController.deleteUser(userId));
}
}
注解作用
- @RunWith(MockitoJUnitRunner.class) :@Runwith是一個(gè)運(yùn)行器,@RunWith(MockitoJUnitRunner.class)嘿般,表示使用MockitoJUnitRunner來運(yùn)行。
- @Mock: 聲明的對(duì)象涯冠,對(duì)函數(shù)調(diào)用均執(zhí)行mock炉奴,不執(zhí)行真正部分
- @Spy :聲明的對(duì)象,對(duì)函數(shù)調(diào)用均執(zhí)行真正部分
- @InjectMocks: 自動(dòng)講模擬對(duì)象或偵查域注入到被測(cè)試對(duì)象中蛇更。
- @PrepareForTest:表示聲明哪些類需要被修改
總結(jié)
寫好的單元測(cè)試用例要比實(shí)際寫代碼的工作量要多瞻赶,這部分工作量沒有太多技術(shù)性的東西(從另外的角度來看,可以對(duì)你的代碼有更深入的理解)派任,但確是比較重要的一部分砸逊。
如果項(xiàng)目是一錘子買賣,沒有后續(xù)迭代掌逛,你可以不需要寫測(cè)試用例师逸,否則都需要寫測(cè)試用例。
當(dāng)需要進(jìn)行代碼重構(gòu)的時(shí)候豆混,這些測(cè)試用例會(huì)給你很大幫助篓像。
當(dāng)你改了一段祖?zhèn)鞔a,如果有測(cè)試用例皿伺,會(huì)給你提供不少思路上的幫助员辩。當(dāng)然改完代碼之后,記得同時(shí)修改測(cè)試用例鸵鸥。