背景
项目当中需要进行业务时间的校验,如上午 9:00-下午 17:00,在 9:00 前或 17:00 后是不能处理相关业务的。因此在业务校验的 Service 中定义了一个 checkBizTime() 方法。原本代码如下:- public void checkBizTime() {
- Date currentTime = new Date();
- // DateUtil.parse的作用是将配置文件中读取的时间字符串转换为Date对象,
- // bizStartTimeStr、bizEndTimeStr 是从配置文件中读取的变量,用 @Value 注解注入
- Date bizStartTime = DateUtil.parse(bizStartTimeStr, "HH:mm:ss");
- Date bizEndTime = DateUtil.parse(bizEndTimeStr, "HH:mm:ss");
- if (currentTime.before(bizStartTime) || currentTime.after(bizEndTime)) {
- throw new BizException("不在业务时间范围内,无法处理业务");
- }
- }
复制代码 但是如何对这个方法进行单元测试,成了一个很头疼的问题。我们知道,单元测试具有独立性和可重复性,但如果要测试上面这段方法,就会发现当系统时间在 9:00 ~ 17:00 内时,这个方法可以通过测试,而不在这个时间范围内,这个方法就会抛出异常,也就是说,这个测试方法依赖于当前系统时间,且不同时间运行测试,得到的测试结果是不同的!这违反了单元测试的独立性和可重复性。因此我们必须让时间固定在某个特定的时间。
解决方法
解决方法:在 DateUtil 类中建立一个 getCurrentDate() 方法,这个方法返回 new Date() 对象。(如果 DateUtil 是第三方库的,或是其他人开发的,那么就在项目中自己定义一个,当然名字需要和 DateUtil 区分开)- public static Date getCurrentDate() {
- return new Date();
- }
复制代码 然后把上述业务代码中的 new Date() 部分替换成 DateUtil.getCurrentDate()- public void checkBizTime() {
- Date currentTime = DateUtil.getCurrentDate();
- // DateUtil.parse的作用是将配置文件中读取的时间字符串转换为Date对象,
- // bizStartTimeStr、bizEndTimeStr 是从配置文件中读取的变量,用 @Value 注解注入
- Date bizStartTime = DateUtil.parse(bizStartTimeStr, "HH:mm:ss");
- Date bizEndTime = DateUtil.parse(bizEndTimeStr, "HH:mm:ss");
- if (currentTime.before(bizStartTime) || currentTime.after(bizEndTime)) {
- throw new BizException("不在业务时间范围内,无法处理业务");
- }
- }
复制代码 然后编写单元测试,注意要先引入 mockito-inline 这个包,才可以对静态方法进行 Mock。- <dependency>
- <groupId>org.mockito</groupId>
- <artifactId>mockito-inline</artifactId>
- <scope>test</scope>
- </dependency>
复制代码 单元测试代码如下:- class BizCheckServiceTest {
- @InjectMocks
- private BizCheckServiceImpl bizCheckServiceUnderTest;
- @Mock
- private MockedStatic<DateUtil> mockedDateUtil;
- @BeforeEach
- void setup() {
- openMocks(this);
- mockedDateUtil
- .when(DateUtil::getCurrentDate)
- .thenReturn(new Date(2024, 2, 3, 10, 0, 0));
- // 假设固定返回 2024年2月3日 10:00:00。但此构造函数已弃用,可以使用其他方式返回Date对象
- // 对 DateUtil 类中的其他方法,可以让他执行真实方法
- mockedDateUtil
- .when(() -> DateUtil.parse(anyString(), anyString()))
- .thenCallRealMethod();
- }
- @Test
- void testCheckBizTime() {
- bizCheckServiceUnderTest.checkBizTime();
- // 验证 getCurrentTime() 方法被执行1次,
- // parse() 方法被执行2次
- verify(mockedDateUtil, times(1)).getCurrentTime();
- verify(mockedDateUtil, times(2)).parse(anyString(), anyString());
- }
- @AfterEach
- void tearDown() {
- // 每次使用完 MockedStatic 接口需要关闭,不然会导致测试方法报错
- mockedDateUtil.close();
- }
- }
复制代码 这样就可以重复执行该单元测试,每次执行的结果应该都是一样的。保持了单元测试的独立性和可重复性。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |