同一專案、不同版本之間原始碼的閱讀

一瑜一琂發表於2022-06-04

上一篇我們講了如何通過關聯延伸閱讀梳理專案之間的關係。

本篇是《如何高效閱讀原始碼》專題的第十三篇,來聊一聊如何閱讀專案的不同版本。

閱讀不同的版本原始碼的目的有兩個:

  • 一個比較火的開源專案,往往經歷了較長時間的開發週期,較多的版本迭代。新版本往往比老版本功能更多更完善,在瞭解了老版本的邏輯後,對於變化不是太大的版本,我們可以通過閱讀差異程式碼來較快的理解新版本的程式碼邏輯。

  • 而對於邏輯差異較大的版本,我們可以對比差異,理解為什麼會有這樣的差異,是什麼原因導致了這樣的差異,繼而 更好的理解新版本的專案

對於JUnit來說,JUnit4和JUnit3之間的差異很大,我們通過閱讀JUnit4和JUnit3之間的差異來理解版本之間的差異,以及理解為什麼會有這些差異。

最新的JUnit5的變化更大,拆分成了三個大模組。鑑於篇幅,這裡不做介紹。
JUnit4的流程已經梳理過了,我們根據前面的步驟來快速的梳理JUnit3,並比較一下兩者的差異。

JUnit3原始碼快速梳理

找出核心模組

在專欄第六篇文章,我們梳理出了下圖的核心模組。

從依賴關係,我們可以知道junit.framework是最核心的模組,而junit.runner次之,junit.textui則是最外圍的模組,從名字也可以知道textui是用於做展示用的。

找出核心模型

通過對framework的梳理,我們可以梳理出下圖的核心模型

梳理核心流程(同時提出問題)

我們從Test介面開始梳理,Test介面的程式碼如下:

可以一眼就看出核心方法是run方法,它是用來執行測試用例的。傳入的TestResult用於收集測試的結果。在子類TestCase中的實現是直接委託給TestResult的run方法來執行

我們接下來看TestResult類的run方法:

核心的方法是構建了一個Protectable的匿名類,然後去呼叫runProtected方法去執行。
為什麼要構建一個Protectable的匿名類來執行呢?直接執行不好嗎?
我們繼續深入runProtected方法:

這裡就是執行前面匿名Protectable類的protect方法,如果報錯則進行錯誤資訊的記錄。這裡使用try-catch很好理解,因為有多個測試要執行,不能因為一個測試用例失敗就導致後面的測試用例不執行了。

protect方法最終執行的是test的runBare方法:

這裡我們就可以看到測試執行的流程了:

  • 首先執行setUp()方法,就是我們在TestCase中編寫的setUp方法

  • runTest執行測試方法

  • 最終執行tearDown方法,做清理工作

  • 如果有異常就丟擲異常。這裡丟擲的異常就會被前面的runProtected方法catch到,記錄到TestResult中

很明顯,這裡是個模板方法模式,這裡定義了測試的整體流程,而我們編寫的TestCase就是具體的測試邏輯實現。

模板方法模式:定義一個操作中的演算法的骨架,而將一些步驟延遲到子類中。Template Method使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。

最後就是runTest方法:

這裡的程式碼看起來很多,邏輯實際很簡單,就是使用反射來執行方法!

畫圖加深理解

 

 

閱讀擴充套件模組,補充流程

在JUnit4中向下相容了JUnit3,是如何相容的呢?從前面我們梳理的JUnit4的原始碼,再結合這裡JUnit3的流程,我們可以知道,相容的方式是通過Runner來處理的。

前面我們知道了AllDefaultPossibilitiesBuilder的runnerForClass方法構建了5個預設的builder.

其中有一個junit3Builder,這裡就是用於構建執行JUnit3的Runner的。有興趣的可自行梳理。

理解核心流程設計

我們回答上面提出的問題:為什麼要用一個Protectable構建一個匿名類來執行呢?直接執行不好嗎?
實際上,這主要是為了規避程式碼重複!
檢視原始碼,我們可以看到TestSetup類中的run方法,也例項化了一個Protectable物件,繼而進行呼叫。

在這裡,Protectable中封裝了不同的執行邏輯,然後傳遞給runProtected方法來執行。
這和JUnit4裡的Statement的作用是否類似?也是用的命令模式。

JUnit3與JUnit4的差異

兩者的一個很明顯的差異就是技術實現上的差異,JUnit4使用了註解,而JUnit3沒有。如果你瞭解JUnit的發展史,你就能很容易理解。JUnit4的版本是在jdk5釋出之後,而jdk5的一個重要特性就是引入了註解。

這也是為什麼在專欄第二篇裡提出「要了解版本技術背景」的原因
技術實現上的差異導致了程式碼結構的差異,可以對比一下JUnit3和JUnit4的抽象模型:

 

 

雖然JUnit3的核心類相對更少,但實際上JUnit4的結構更清晰。比如:JUnit3的核心類既承擔了建模的作用,又承擔了執行流程的職責,執行流程在Test和TestResult之間繞了很多次,程式碼看起來也比較累;而JUnit4中測試執行過程獨立到Runner中,TestClass等類只負責建模。這更加的符合單一職責原則。

同時JUnit4為了相容JUnit3,實際使用的是一個策略模式來構建不同的Runner來執行對應的測試模型。

策略模式:定義一系列的演算法,把它們一個個封裝起來,並且使它們可相互替換。本模式使得演算法可獨立於使用它的客戶而變化。

總結

本文首先通過前面梳理的閱讀原始碼的流程,快速的對JUnit3進行了梳理,接著比較了JUnit3和JUnit4之間的差異。總體而言,JUnit4比JUnit3結構更清晰。

至此,我們原始碼閱讀的完整步驟已經全部講解完。下一篇是本專欄的最後一篇,對原始碼閱讀做一個總結。

相關文章