如何写好Java标准单测
1.引入依赖
<dependencies> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-inline</artifactId> <version>3.7.7</version> <scope>test</scope> </dependency> <dependency> <artifactId>mockito-core</artifactId> <groupId>org.mockito</groupId> <version>3.7.7</version> <scope>test</scope> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-params</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.0.0-M5</version> </plugin> </plugins> </build>
2.单测原则
强制:
- 以Class为最小测试单元
- 尽量不依赖Spring环境
- mock依赖的下游服务或基础组件
3.常见单测场景
例子:有如下待测试类
1 @Service//被定义为一个spring bean 2 public class HRTokenThriftServiceImpl implements HRTokenThriftService.Iface { 3 4 @Autowired//依赖其它bean 5 private TokenService tokenService; 6 7 @Override 8 @UpperExceptionAnnotation(functionCode = "getToken", errorClazz = ErrorTo.class) 9 public HRTokenAndEncryptResTo getTokenAndEncrypt(HRTokenAndEncryptReqTo reqTo) throws TException { 10 validateHRTokenAndEncryptReqTo(reqTo); 11 TokenAndEncryptReq tokenAndEncryptReq = convertTokenAndEncryptReq(reqTo); 12 //依赖其它类的方法,需要mock 13 TokenAndEncryptResult tokenAndEncryptResult = tokenService.getTokenAndEncrypt(tokenAndEncryptReq); 14 return buildHRTokenAndEncryptResTo(tokenAndEncryptResult); 15 } 16 17 private void validateHRTokenAndEncryptReqTo(HRTokenAndEncryptReqTo reqTo) { 18 ParameterValidateUtil.validateHRPlainType(reqTo.getHrPlainType()); 19 ParameterValidateUtil.validateHRPlainText(reqTo.getHrPlainText(), reqTo.getHrPlainType()); 20 } 21 22 private TokenAndEncryptReq convertTokenAndEncryptReq(HRTokenAndEncryptReqTo reqTo) { 23 TokenAndEncryptReq tokenAndEncryptReq = new TokenAndEncryptReq(); 24 tokenAndEncryptReq.setHrPlainType(HRPlainType.forCode(reqTo.getHrPlainType())); 25 tokenAndEncryptReq.setHrPlainText(reqTo.getHrPlainText()); 26 return tokenAndEncryptReq; 27 } 28 29 private HRTokenAndEncryptResTo buildHRTokenAndEncryptResTo(TokenAndEncryptResult tokenAndEncryptResult) { 30 HRTokenAndEncryptResTo resTo = new HRTokenAndEncryptResTo(); 31 if (tokenAndEncryptResult.isSuccess()) { 32 resTo.setStatus(GlobalConstant.SUCCESS); 33 HRTokenAndEncryptResDataTo data = new HRTokenAndEncryptResDataTo(); 34 data.setToken(tokenAndEncryptResult.getHrToken()); 35 data.setEncrypt(tokenAndEncryptResult.getHrPlainTextEncrypt()); 36 resTo.setData(data); 37 } else { 38 resTo.setStatus(GlobalConstant.FAIL); 39 resTo.setError(ErrorToFactory.buildErrorTo(tokenAndEncryptResult.getError())); 40 } 41 return resTo; 42 } 43 }
3.1.创建单测类
1 //1、使用idea快捷键command+shift+T创建单测类 2 //2、引入junit断言,mockito依赖 3 import static org.junit.jupiter.api.Assertions.*; 4 import static org.mockito.BDDMockito.*; 5 6 //3、单测类以Test结尾,与被测试类在同package下 7 class HRTokenThriftServiceImplTest { 8 9 //4、InjectMocks注解定义被测试类 10 @InjectMocks 11 private HRTokenThriftServiceImpl hrTokenThriftService; 12 //5、Mock注解定义被测试类中依赖的bean 13 @Mock 14 private TokenService tokenService; 15 16 @BeforeEach 17 void setup() { 18 //启用mockito注解 19 MockitoAnnotations.openMocks(this); 20 } 21 22 //…… 23 }
3.2.参数化测试
使用场景:测试参数和返回结果不同,其它测试代码相同,适用于接口参数校验等测试场景。
1 //参数生成方法 2 static Stream<Arguments> illegalParametersGenerator() { 3 return Stream.of( 4 //与参数化单测方法入参对应 5 Arguments.of(new HRTokenAndEncryptReqTo("", 3), new BusinessException("hrPlainType check failed,code:3", ErrorEnum.ILLEGAL_HR_PLAIN_TYPE)), 6 Arguments.of(new HRTokenAndEncryptReqTo("", 1), new BusinessException("illegal hrPlainText:", ErrorEnum.ILLEGAL_HR_PLAIN_TEXT)), 7 Arguments.of(new HRTokenAndEncryptReqTo("abc", 1), new BusinessException("illegal hrPlainText:abc", ErrorEnum.ILLEGAL_HR_PLAIN_TEXT)), 8 Arguments.of(new HRTokenAndEncryptReqTo("abc", 2), new BusinessException("illegal hrPlainText:abc", ErrorEnum.ILLEGAL_HR_PLAIN_TEXT)) 9 ); 10 } 11 12 @ParameterizedTest//参数化注解 13 @MethodSource("illegalParametersGenerator")//指定参数生成方法名 14 @DisplayName("参数错误")//测试方法别名 15 void illegalParameters(HRTokenAndEncryptReqTo reqTo, BusinessException e) { 16 System.out.println(reqTo); 17 //执行被测试方法 18 BusinessException exception = assertThrows(BusinessException.class, () -> hrTokenThriftService.getTokenAndEncrypt(reqTo)); 19 System.out.println(exception); 20 assertEquals(e.getErrorEnum(), exception.getErrorEnum());//断言 21 assertEquals(e.getMessage(), exception.getMessage());//断言 22 }
3.3.异常测试
使用场景:执行被测试方法时会抛出异常。
@Test//junit5 @DisplayName("参数错误")//测试方法别名 void illegalParameters() { HRTokenAndEncryptReqTo reqTo = new HRTokenAndEncryptReqTo(); System.out.println(reqTo); //被测试方法可能抛出异常,assertThrows()捕获异常 BusinessException exception = assertThrows(BusinessException.class, () -> hrTokenThriftService.getTokenAndEncrypt(reqTo)); System.out.println(exception); assertEquals(e.getErrorEnum(), exception.getErrorEnum());//断言 assertEquals(e.getMessage(), exception.getMessage());//断言 }
3.4.典型mock
使用场景:大多数测试场景。
1 @Test//junit5 2 @DisplayName("成功")//单测方法别名 3 void success() throws TException { 4 //构造mock结果 5 TokenAndEncryptResult tokenAndEncryptResult = new TokenAndEncryptResult(); 6 tokenAndEncryptResult.setStatus(GlobalConstant.SUCCESS); 7 tokenAndEncryptResult.setHrToken("hr_hhhhhhhToCht20161"); 8 tokenAndEncryptResult.setHrPlainTextEncrypt("AwQAAABJAgAAAAEAAADvAAAAPDZ1iUfSTRuPUMe9PFRZGR8wLYxLbn//uz5x65831Z98NWj1GfT8BcZiYCgI43wE4lyGsTDcm5Vak6JNsgAAACLckOpeclNcPGyU/HoOrOd2P7W2x2ap09GmG3iqispkCtNG"); 9 //stub,设置mock结果。语法:given(mock类.mock方法(入参...)).willReturn(mock结果) 10 given(tokenService.getTokenAndEncrypt(any())).willReturn(tokenAndEncryptResult); 11 HRTokenAndEncryptReqTo reqTo = new HRTokenAndEncryptReqTo().setHrPlainText("123456").setHrPlainType(1); 12 System.out.println(reqTo); 13 //执行被测试方法 14 HRTokenAndEncryptResTo resTo = hrTokenThriftService.getTokenAndEncrypt(reqTo); 15 System.out.println(resTo); 16 //对mock类进行断言。语法:then(mock类).should(times(指定mock方法执行次数,默认为1,可选)).mock方法(入参) 17 then(tokenService).should().getTokenAndEncrypt(any()); 18 //断言 19 assertNotNull(resTo); 20 assertEquals(GlobalConstant.SUCCESS, resTo.getStatus()); 21 assertEquals("hr_hhhhhhhToCht20161", resTo.getData().getToken()); 22 assertEquals("AwQAAABJAgAAAAEAAADvAAAAPDZ1iUfSTRuPUMe9PFRZGR8wLYxLbn//uz5x65831Z98NWj1GfT8BcZiYCgI43wE4lyGsTDcm5Vak6JNsgAAACLckOpeclNcPGyU/HoOrOd2P7W2x2ap09GmG3iqispkCtNG", resTo.getData().getEncrypt()); 23 }
3.5.mock静态方法
使用场景:需要mock静态方法。
1 @ParameterizedTest 2 @MethodSource("batchGetBankcardToken_illegalList_generator") 3 @DisplayName("批量获取银行卡token-list错误") 4 void batchGetBankcardToken_illegalList(BatchBankcardTokenReqTo reqTo, BusinessException e, int times) { 5 //try块定义被mock静态方法类,使用mockStatic(静态方法类) 6 try (MockedStatic iniUtil = mockStatic(IniUtil.class)) { 7 //stub,设置mock结果 8 iniUtil.when(() -> IniUtil.getIniIntValue(anyString(), anyInt())).thenReturn(1); 9 System.out.println(reqTo); 10 BusinessException exception = assertThrows(BusinessException.class, () -> certifyTokenThriftService.batchGetBankcardToken(reqTo)); 11 System.out.println(exception); 12 //断言,验证静态mock类 13 iniUtil.verify(() -> IniUtil.getIniIntValue(anyString(), anyInt()), times(times)); 14 //断言 15 assertEquals(e.getErrorEnum(), exception.getErrorEnum()); 16 assertEquals(e.getMessage(), exception.getMessage()); 17 } 18 }

浙公网安备 33010602011771号