记一次关于mock Systemc.currentTimeMillis的实践
因為在寫單測過程中,發現@PrepareForTest和JaCoCo會有沖突,所以想要將JaCoCo修改為offline模式,但是這樣一來,就需要對utils等模塊全部重新寫單測。
從單測的角度來說各個模塊的單測各自獨立是比較推薦的,模塊A的單測就寫在模塊A里。但是很多工具類單獨寫單測也還是比較麻煩的,就想著是否有其他辦法可以繞過這個問題
介于我們項目中需要使用@PrepareForTest的場景幾乎都是用于mock系統時間戳(其他場景下用于第三方類并不影響我們項目覆蓋率的統計),由于使用@PrepareForTest會導致調用了系統時間戳的類無法統計覆蓋率,所以目前項目中有很多涉及到時間的數據構造都是通過采用在當前時間戳的基礎上加減小時數來做到的。這樣一來就會有幾個問題
就想到針對我們項目的這種情形,是否可以不使用JaCoCo的離線模式,而采用其他辦法在測試類中mock系統時間戳
封裝工具類,替換掉Systemc.currentTimeMillis()
最簡單的辦法就是將System.currentTimeMillis()封裝成一個靜態工具類,把所有調用系統時間戳的地方都改成調用該方法,這樣在使用PowerMock的時候就不用擔心系統時間戳的調用類無法統計到覆蓋率
public class SystemUtils {public static Long currentTimeMillis(){return System.currentTimeMillis();} } //原來的public static boolean isBeforeTime(long beginTimeUTC, long offset) {long timeNow = System.currentTimeMillis();if (beginTimeUTC - timeNow > THREE_DAYS) {return true;} return false;}//修改后public static boolean isBeforeTime(long beginTimeUTC, long offset) {long timeNow = SystemUtils.currentTimeMillis();if (beginTimeUTC - timeNow > THREE_DAYS) {return true;} return false;}使用其他時間戳api
java 8 提供了新的關于日期操作的api, 相比以前的api更加易用和安全,推薦使用java 8的api替換上述獲取系統時間戳的方法。 比方說下面的LocalDateTime,我們可以通過 Clock.fixed在寫單測的時候將系統時間設置成任何你想要的值,或者直接mockLocalDateTime.now返回的時間結果也是可以的
private static Clock clock = Clock.systemDefaultZone();private static ZoneId zoneId = ZoneId.systemDefault();public static Long getSystemTime(){return LocalDateTime.now(clock).atZone(zoneId).toInstant().toEpochMilli();}//設置當前系統時間為指定的LocalDateTimepublic static void useFixedClockAt(LocalDateTime date) {clock = Clock.fixed(date.atZone(zoneId).toInstant(), zoneId);}//恢復當前系統時間public static void useSystemDefaultZoneClock() {clock = Clock.systemDefaultZone();}使用AspectJ代理
我只是想要將系統時間戳的方法調用結果進行修改,那么是否可以使用代理來實現呢?
根據參考資料,我先嘗試了使用AspectJ來實現對Systemc.currentTimeMillis()方法的代理
這樣,我們就可以通過setSystemTimestamp方法設置系統時間戳的返回值,默認返回0
但是在驗證過程中發現一個問題:
我只是想要在測試代碼里對系統時間戳的調用進行代理,在測試或者生產環境真正調用代碼的時候不需要這個代理操作,在網上查了下資料發現可以通過aspectj-maven-plugin插件實現 , 配置如下
這樣做,在執行mvn相關命令的是(test-compile,package),可以在test-classes下看到生成的被織入的class文件
(但是沒有事先通過maven編譯是無法生成對應的代理class,也就無法起到修改系統時間戳的目的)
織入成功后的class
private boolean isOverdue(Long beginTime, Double timeZone) {return (double)(beginTime + 28800000L) - timeZone * 3600.0D * 1000.0D + (double)OVERDUE_TIME < (double)currentTimeMillis_aroundBody1$advice(this, ChangeSystemTimeMillis.aspectOf(), (AroundClosure)null);}本來以為這樣就成功了,但是在提交執行單測的時候又發現了另一個問題
由于JaCoCo需要通過修改字節碼文件加入統計代碼類統計覆蓋率,而上述aspectj-maven-plugin在編譯時在test-classes下生成了新的class文件,這里是不包含統計代碼的,所以哪怕我的單測都通過了,原來的代碼也沒有被JaCoCo統計到覆蓋率
這就跟一開始的目的相違背了,所以最后還是采用了離線JaCoCo模式來解決這個問題。
記錄一下這次探索,我看到StackOverFlow有人建議使用LTW,不過我沒嘗試成功,那個對系統類好像并不有效,如果有人解決了這個問題歡迎一起分享下
參考資料:
Overriding System Time for Testing in Java
總結
以上是生活随笔為你收集整理的记一次关于mock Systemc.currentTimeMillis的实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java8 日期/时间(Date Tim
- 下一篇: 单元测试之JUnit 5 参数化测试使用