对时间强依赖的方法如何做单元测试

打印 上一主题 下一主题

主题 917|帖子 917|积分 2751

背景

项目当中需要进行业务时间的校验,如上午 9:00-下午 17:00,在 9:00 前或 17:00 后是不能处理相关业务的。因此在业务校验的 Service 中定义了一个 checkBizTime() 方法。原本代码如下:
  1. public void checkBizTime() {
  2.     Date currentTime = new Date();
  3.     // DateUtil.parse的作用是将配置文件中读取的时间字符串转换为Date对象,
  4.     // bizStartTimeStr、bizEndTimeStr 是从配置文件中读取的变量,用 @Value 注解注入
  5.     Date bizStartTime = DateUtil.parse(bizStartTimeStr, "HH:mm:ss");
  6.     Date bizEndTime = DateUtil.parse(bizEndTimeStr, "HH:mm:ss");
  7.     if (currentTime.before(bizStartTime) || currentTime.after(bizEndTime)) {
  8.         throw new BizException("不在业务时间范围内,无法处理业务");
  9.     }
  10. }
复制代码
但是如何对这个方法进行单元测试,成了一个很头疼的问题。我们知道,单元测试具有独立性和可重复性,但如果要测试上面这段方法,就会发现当系统时间在 9:00 ~ 17:00 内时,这个方法可以通过测试,而不在这个时间范围内,这个方法就会抛出异常,也就是说,这个测试方法依赖于当前系统时间,且不同时间运行测试,得到的测试结果是不同的!这违反了单元测试的独立性和可重复性。因此我们必须让时间固定在某个特定的时间。
解决方法

解决方法:在 DateUtil 类中建立一个 getCurrentDate() 方法,这个方法返回 new Date() 对象。(如果 DateUtil 是第三方库的,或是其他人开发的,那么就在项目中自己定义一个,当然名字需要和 DateUtil 区分开)
  1. public static Date getCurrentDate() {
  2.     return new Date();
  3. }
复制代码
然后把上述业务代码中的 new Date() 部分替换成 DateUtil.getCurrentDate()
  1. public void checkBizTime() {
  2.     Date currentTime = DateUtil.getCurrentDate();
  3.     // DateUtil.parse的作用是将配置文件中读取的时间字符串转换为Date对象,
  4.     // bizStartTimeStr、bizEndTimeStr 是从配置文件中读取的变量,用 @Value 注解注入
  5.     Date bizStartTime = DateUtil.parse(bizStartTimeStr, "HH:mm:ss");
  6.     Date bizEndTime = DateUtil.parse(bizEndTimeStr, "HH:mm:ss");
  7.     if (currentTime.before(bizStartTime) || currentTime.after(bizEndTime)) {
  8.         throw new BizException("不在业务时间范围内,无法处理业务");
  9.     }
  10. }
复制代码
然后编写单元测试,注意要先引入 mockito-inline 这个包,才可以对静态方法进行 Mock。
  1. <dependency>
  2.     <groupId>org.mockito</groupId>
  3.     <artifactId>mockito-inline</artifactId>
  4.     <scope>test</scope>
  5. </dependency>
复制代码
单元测试代码如下:
  1. class BizCheckServiceTest {
  2.     @InjectMocks
  3.     private BizCheckServiceImpl bizCheckServiceUnderTest;
  4.     @Mock
  5.     private MockedStatic<DateUtil> mockedDateUtil;
  6.     @BeforeEach
  7.     void setup() {
  8.         openMocks(this);
  9.         mockedDateUtil
  10.             .when(DateUtil::getCurrentDate)
  11.             .thenReturn(new Date(2024, 2, 3, 10, 0, 0));
  12.         // 假设固定返回 2024年2月3日 10:00:00。但此构造函数已弃用,可以使用其他方式返回Date对象
  13.         // 对 DateUtil 类中的其他方法,可以让他执行真实方法
  14.         mockedDateUtil
  15.             .when(() -> DateUtil.parse(anyString(), anyString()))
  16.             .thenCallRealMethod();
  17.     }
  18.     @Test
  19.     void testCheckBizTime() {
  20.         bizCheckServiceUnderTest.checkBizTime();
  21.         // 验证 getCurrentTime() 方法被执行1次,
  22.         // parse() 方法被执行2次
  23.         verify(mockedDateUtil, times(1)).getCurrentTime();
  24.         verify(mockedDateUtil, times(2)).parse(anyString(), anyString());
  25.     }
  26.     @AfterEach
  27.     void tearDown() {
  28.         // 每次使用完 MockedStatic 接口需要关闭,不然会导致测试方法报错
  29.         mockedDateUtil.close();
  30.     }
  31. }
复制代码
这样就可以重复执行该单元测试,每次执行的结果应该都是一样的。保持了单元测试的独立性和可重复性。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

络腮胡菲菲

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表