如何写好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 }
posted @ 2022-04-08 15:10  L·先生  阅读(370)  评论(0)    收藏  举报