成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

單元測試框架的對比

原創 精選
開發 測試
在開發過程中,我發現 JUnit 并不總是那么好用。它在一些情況下需要耗費挺多精力才能編寫出讓人滿意的測試。

作者 | 高悅翔

在我們日常的 TDD 開發中,永遠繞不過去的就是要編寫測試。而對于一個 Java 程序員,JUnit 似乎是一個不二的選擇。它的確是一個十分優秀的工具,在大多數情況下都能夠幫助我們完成測試的工作。

但是在開發過程中,我發現 JUnit 并不總是那么好用。它在一些情況下需要耗費挺多精力才能編寫出讓人滿意的測試。

JUnit 不擅長的事情

一個讓人滿意的測試,應該能夠清晰地體現被測試的目標、測試的目的以及測試的輸入輸出,并且應遵循 DRY 原則,盡可能的減少測試中的重復內容。

JUnit 可以通過設計測試方法名和組織方法內的代碼的方式清晰地表達意圖,也可以通過參數化測試來減少相同測試目的的代碼重復。但是它在這些地方都做得不夠好。

清晰表達測試的目的

在使用 JUnit 時,清晰的表達測試意圖并不是總能做到的事情。這主要體現在兩個方面。

(1) 如何命名測試方法

第一個體現就是在使用 Java 編寫測試時,采用什么樣的命名風格來命名測試。是為了代碼風格的統一而選擇駝峰?還是為了更高的可讀性選擇下劃線?這個問題在不同的項目中有不同的實踐,看起來是沒有一個統一的認識。

而這個問題的根源是 JUnit 的測試名稱是 Java 的方法名,而 Java 的方法名又不能在其中插入空格。所以除了下面要介紹的兩種測試工具外,采用 Kotlin 來編寫 JUnit 也是一種方式。

(2) 如何組織方法內的代碼

第二個體現就是 JUnit 對測試方法內部如何編寫沒有強制的規定。這就意味著我可以在測試里面任意地組織代碼,比如在一個測試方法里面對一個方法調用多次并驗證每一次的結果,或者把調用測試目錄的邏輯和準備數據的邏輯以及驗證邏輯混合到一起。 總之這樣的結果就是測試方法內的代碼組織方式千奇百怪,每當閱讀別人編寫的測試的時候,總是要花上好幾分鐘才能知道這些代碼到底在干什么。

對于這個問題,我個人會選擇使用注釋來標注一下 given 、 when 、 then,并且給 IDEA 設置了 live template 方便插入它們。

又不是不能用的參數化測試

如果說不能清晰地表達測試意圖這個問題還有一些 workaround 可以繞過去的話,JUnit 那僅僅是能用的參數化測試功能就沒有什么好辦法可以繞過去了。

JUnit 提供了各種 Source 注解來為參數化測試提供數據,但是這個功能實在是太弱了,很難讓人滿意。

難以讓人滿意的第一個原因是,各種 Source 注解基本上只能支持 7 種基本類型再加上 String, Enum 和 Class 類型。如果想要使用其他類型的實例作為參數的話,就必須要使用 MethodSource 或者 ArgumentsSource 注解。

這就導致了第二個原因:這兩個注解需要單獨寫一個靜態方法或一個 ArgumentProvider 的實現,這就導致很難把測試參數寫到測試代碼旁邊。并且 Arguments.of() 方法并不利于閱讀測試參數。

這兩點導致測試的可讀性下降。而按照“測試即文檔”的原則,我們應該盡力去保證測試的可讀性。

第三個原因則是來自 ParameterizedTest 注解。它的 name 字段可以使用參數的索引值來把參數填入模板中,生成更加可讀的測試名稱。

但是它的功能也僅限于此了。因為這個模板只能使用索引值,不能使用索引后再調用里面的方法或者字段。所以如果我們的參數是一個復雜對象,那么一定要重寫 toString 方法才能得到滿意的輸出。但是這又違背了編寫測試的原則之一——不能為了測試而添加實現代碼。

如果我們一定要得到一個更加表意的測試名稱,那么添加一個專用的測試參數也能做到。但是這又會導致 IDE 或者構建工具的警告,因為它們認為這個參數沒有被使用。

總之,盡管 JUnit 可以解決絕大多數問題,但是在這么幾個小地方卻做的不是那么完美。

那么有沒有什么工具可以作為 JUnit 的替代呢?當然是有的。下面我將按照我接觸的順序來介紹兩種種測試框架。可以在 GitHub 上找到下面例子的完整代碼。

使用 Spock 作為測試框架

Spock是一個用 Groovy 編寫的測試框架,按照 given/when/then 的結構定義 dsl,能夠讓測試更加的語義化。它的一大特點是 Data Driven Test,可以方便的編寫參數化測試。

我曾在兩個項目上嘗試過使用 Spock 作為測試框架,幾乎沒有遇到過無法解決的問題。

如何使用 Spock

我們來看一個最簡單的例子:

class MarsRoverSpockTest extends Specification { // 1
def "should return mars rover position and direction when mars rover report"() { //2
given: // 3.1
def marsRover = MarsRoverFixture.buildMarsRover(
position: new Position(1, 2),
direction: Direction.EAST,
)

when: // 3.2
def marsRoverInfo = marsRover.report()

then: // 3.3
marsRoverInfo.position == new Position(1, 2)
marsRoverInfo.direction == Direction.EAST
}
}

(1) 每一個測試都需要繼承抽象類 Specification;

(2) 可以使用字符串來命名測試;

(3) Spock 定義了一些 block,這里的 given 、 when 、 then 都是 block。

  • given block 負責測試的 setup 工作
  • when block 可以是任意代碼,不過最好是對測試目標的調用。它總是和 then 一起出現
  • then block 用來斷言。這里不需要任何的 assertion,只需要編寫返回值是 boolean 的表達式即可

Spock 有非常友好的測試報告輸出。如果我們把上面的斷言特意改錯,就能得到這樣的測試輸出:

Condition not satisfied:

movedMarsRover.position == movedPosition
| | | |
| | | Position(x=-2, y=2)
| | false
| Position(x=-1, y=2)
MarsRover(position=Position(x=-1, y=2), direction=WEST)

在這個輸出里面,我們可以清晰的看出表達式兩端的值是什么,非常便于 debug。

特點

(1) 使用字符串命名測試方法

在前面的例子中,我們可以看到測試的方法名是使用字符串來命名,不需要像 JUnit 一樣遵循 Java 方法的命名規則。這樣我們就不用糾結使用什么樣的命名方法,只需要像寫一句話一樣來編寫測試方法名稱。

(2) 語義化的結構

在前面的例子中,我們看到了 block 的概念。它可以幫助我們更好的組織代碼結構,寫出更加便于閱讀的代碼。其實在每一個 block 聲明之后,我們還可以在添加一個字符串,達到注釋的作用。比如:

given: "a mars rover at position 1,2 and direction is north"

除了上面的例子里看到的,Spock 還提供了 cleanup 、 expect 、 where 這三個 block。詳細信息可以看看它的??文檔??。

因為 Spock 強制要求使用 block 來組織測試代碼,這樣就可以強迫我們寫出結構化的代碼,更加便于閱讀。

(3) 使用 data table 構造參數化測試

對于參數化測試,我們再來看一個例子。

def "should move mars rover from position 1,2 forward to #movedPosition.x,#movedPosition.y when direction is #direction and move length is #length"() { 
given:
def marsRover = MarsRoverFixture.buildMarsRover(
position: new Position(1, 2),
direction: direction,
)

when:
def movedMarsRover = marsRover.forward(length)

then:
movedMarsRover.position == movedPosition
movedMarsRover.direction == direction

where:
direction | length || movedPosition
Direction.EAST | 2 || new Position(3, 2)
Direction.WEST | 3 || new Position(-2, 2)
Direction.SOUTH | -1 || new Position(1, 3)
Direction.NORTH | 1 || new Position(1, 3)
}

我們可以看到代碼的最后一段是一個 where block,這是 Spock 中用來定義數據測試的數據的地方。例子中的寫法被稱作 data table。盡管 Spock 還支持一些其他的寫法,但是我個人認為 data table 是一個更加可讀的寫法,所以這也是我最常使用的寫法。并且這個寫法不需要手動調整格式,IDEA 支持自動 format,堪稱完美。

我們還可以留意一下方法名。方法名中有幾個以 # 開頭的字符串,它們其實是在引用 data table 中定義的變量。這種通過變量名引用的方式可讀性遠遠大于 JUnit 的索引值的方式。并且我們可以看到 #movedPosition.x 這樣的表達式,它們可以直接使用這些對象中的字段值來生成方法名,不需要依賴于對象的 toString 方法。

對于斷言失敗的測試,同樣會像上面的例子一樣在測試輸出中打印具體的數據,方便定位到測試失敗的用例。

與 JUnit 對比起來,Spock 的 data table 既讓參數列表和測試方法放到了一起,又能支持任意類型的參數,還可以沒有副作用地定義參數名稱,算是把 JUnit 參數化測試的痛點都解決了。

(4) 簡潔的斷言

在上面的例子中,我們看到 Spock 的斷言十分簡潔,不需要像使用 assertj 一樣寫很長的 assertThat(xxx).isEqualTo(yyy),只需要一個返回 boolean 的表達式就可以了。

甚至可以把多行斷言提取到一個方法中,返回他們與運算的結果。

(5) 使用 Groovy 編寫測試

使用 Groovy 來編寫測試,可以說既是優點,也是缺點。

優點是在于 Groovy 是動態語言,我們可以利用這一特性在測試中少寫一些啰嗦的代碼。并且在前面的例子中,斷言里面獲取的 marsRover.position 字段本身是 private 字段,測試仍然可以正常執行。這些都是由 Groovy 帶來的靈活性。

缺點在于這是一個相對小眾的語言。如果不是因為 Gradle,或許不會有多少人熟悉它的語法。這也會導致人們在選擇它時會變得更加謹慎。

(6) 與 IDE 的完美集成

我一直使用的 IDEA 是能夠完美適配它的。除了前面提到的 format data table ,最主要的是 IDEA 能像執行 JUnit 一樣執行它,并且不需要任何的配置。

缺點

我在第一個項目里面使用 Spock 時,幾乎沒有發現它有什么缺點,以至于在后來的項目中總是在問 TL 能不能把它加到項目里來。

但是后來在一個 Kotlin 項目中嘗試使用它時,卻遇到一些問題。

與 Kotlin 的集成問題:

(1) 無法識別 Kotlin 的語法糖

Groovy 不能直接識別到 Kotlin 代碼中的各種語法糖,這就讓測試寫起來有那么一點點不舒服。

比如命名參數。其實 Groovy 也支持命名參數,但是語法和 Kotlin 不同。這就顯得有一點尷尬。不過這個問題可以通過為測試編寫一些 fixutre 之類的代碼來幫助處理這里問題。比如下面這個 Kotlin 類型:

data class MarsRover(  
private val position: Position,
private val direction: Direction,
)

我們可以為它編寫一個 fixture,就能在測試里面也使用命名參數了:

class MarsRoverFixture {  
@NamedVariant
static MarsRover buildMarsRover(position, direction) {
new MarsRover(position, direction)
}
}

其他的一些語法問題也基本都能繞過,本質思路就是把要測試的代碼想象成編譯后的 Java,這樣就能找到繞過的辦法。

(2) 沒有對 final class 的 mock 支持

這是一個基本繞不過去的問題。Kotlin 里面的類型默認都是 final,不能再被繼承。但是 Spock 的 mock 卻需要為要 mock 的對象的類型創建一個子類。這就導致我們不能去 mock 那些類型。其實這個問題不是 Spock 特有的,Mockito 也有這個問題。只不過在使用 JUnit 時我們會選擇用 MockK 作為 Kotlin 項目的 mock 工具,而不是 Mockito。

解決這個問題的策略有好幾個:

  • 盡可能不去 mock。這要求我們設計出更容易測試的代碼,這樣就可以避免在測試中使用 mock。
  • 因為 Spring 組件需要被繼承,所以會使用 Kotlin All-open compiler 來為 Spring 提供支持,我們就可以 mock 這些 Spring Component

在寫這篇文章的時候,發現一個很久沒有更新的倉庫 kotlin-test-runner,也許可以借鑒一下這里的思路來解決這個問題。

(3) 與 JUnit 的兼容問題

對于上一個問題,我們當時還有一個 workaround,那就是使用 JUnit5 + MockK 來編寫那些需要 mock 的測試。但是那個時候的 Kotlin 版本還比較低,沒有遇到和 JUnit 的兼容問題。

兼容問題是 JUnit 在編寫 Spring 集成測試的時候,如果有 mock bean 的需求,需要使用 springmock 里面的 @MockkBean 注解。但是從 kotlin 1.5.30 開始,這個庫就不能和 Spock 編寫的 Spring 集成測試兼容,會出現 NPE 問題。這個問題在使用 Kotlin 對 Specification 子類進行反射時會出現。

這個問題一直到 Kotlin 1.6.20-M1 才修復。

Groovy 語言的學習成本:

就像前面提到過的,使用 Groovy 還是有一些學習成本的。如果團隊里沒有熟悉它的人,可能會走一點彎路。

使用 Kotest 作為測試框架

Kotest 是在無意中發現的測試框架,還沒有在實際的項目中實踐過。所以這里只能分享一下如何使用,沒有什么經驗分享。

如何使用 Kotest

我們來看一個例子:

class MarsRoverKotestTest : BehaviorSpec({  
given("a mars rover") {
val marsRover = MarsRover(
position = Position(1, 2),
direction = Direction.EAST,
)
`when`("it report information") {
val (position, direction) = marsRover.report()
then("get it's position and direction") {
position shouldBe Position(1, 2)
direction shouldBe Direction.EAST
}
}
}
})

這是一種 BDD 風格的測試。Kotest 使用 BehaviorSpec 類封裝起來。

在 then 中,我們沒有看到常見的 assertThat() 語句,取而代之的是 Kotest 的 assertion 庫提供的方法。

特點

(1) 豐富的測試風格支持

除了上面的例子,我們還有很多的測試風格可以選擇,這在它的文檔中有介紹:??Testing Styles | Kotest??。

在這些風格中,測試名稱都是通過字符串來編寫的。如前面所說,這樣我們就不用像使用 JUnit 一樣糾結測試方法的命名風格,只管描述測試目的就可以來。

然而除了 BDD 這種測試風格外,其他的測試風格都沒有對測試代碼的組織有任何強制要求。這就需要團隊為測試代碼測組織達成一致并維護它。這和 JUnit 沒有什么區別。

(2) 對 data driven test 的支持

Kotest 提供了擴展來支持 data driven test。當然,不使用這個擴展也可以進行,比如用 list 構造好數據之后 foreach 創建測試。不過這里的例子我們還是使用這個擴展來演示。

class MarsRoverKotestTest : FunSpec({  
context("data test") {
withData(
nameFn = { (direction, length, position) ->
"should move mars rover from 1,2 to ${position.x},${position.y} when direction is $direction and move length is $length"
},
Triple(Direction.EAST, 2, Position(3, 2)),
Triple(Direction.WEST, 3, Position(-2, 2)),
Triple(Direction.SOUTH, -1, Position(1, 3)),
Triple(Direction.NORTH, 1, Position(1, 3)),
) { (direction, length, movedPosition) ->
val marsRover = MarsRover(
position = Position(1, 2),
direction = direction,
)
val movedMarsRover = marsRover.forward(length)
movedMarsRover.report().position shouldBe movedPosition
movedMarsRover.report().direction shouldBe direction
}
}
})

雖然這個 data driven test 相對于 Spock 的 data table 來講沒有那么直觀,但是對比 JUnit 的話,能夠方便的自定義測試方法名、支持任意類型的參數并且測試數據與測試代碼可以放在一起,已經算是一個巨大的進步了。

(3) 簡潔的斷言

在上面的例子里面我們看到,Kotest 提供了自己的斷言庫,不需要再寫冗長的 assertThat() 之類的語句。

(4) 使用 Kotlin 編寫,能與 Kotlin 項目完美結合

使用 Kotlin 來編寫測試,可以使用到 Kotlin 里面的各種語法糖。這樣就不用像 Spock 一樣在語法切換中掙扎。

(5) 支持 MockK

同樣的,因為 Kotest 的測試使用 Kotlin 編寫,自然是支持 MockK 的。這樣就能利用 MockK 的特性,支持對 final class 的 mock。

(6) 與 JUnit 兼容

因為 Kotest 是基于 JUnit 平臺的,所以是能和 JUnit 兼容的,不會出現上面的 Spock 那樣的問題。

缺點

因為沒有在實際的項目中實踐過,所以目前沒有發現很多的缺點。

(1) 與 IDEA 和 Gradle 的集成不夠完美

這個問題的表現是在 IDEA 里面無法執行單個測試方法。但是細究后發現,實際上是和 gradle 的集成不夠好。

默認情況下,IDEA 會使用 gradle 來執行測試。執行單個測試的命令是 gradle test --tests "xxx.Class.yyyMethod"。對于 JUnit,這里的 class 和 method 是很直觀的類名和方法名。但是 Kotest 的寫法卻不是編寫類里面的方法,而是調用方法生成測試。所以 gradle 的這個命令就沒有辦法生效,也就沒有辦法只執行一個測試方法了。

在把 IDEA 的配置更新成使用 IDEA 來運行測試后,在 mac 上能夠正常執行單個測試方法。

不要使用 Spek

前面介紹了兩種值得一試的測試框架,這里再介紹一種不建議使用的框架。

當初想要嘗試這個框架,是因為看到有網友說這是 Kotlin 版本的 Spock 。但是實踐下來并沒有發現它有和 Spock 類似的功能,并且還出現了這些痛點:

  • 與其他測試框架混合使用的問題:當與其他測試框架混合使用時,Spek 測試總是會先執行。哪怕我們在 IDEA 里面只想執行一個 JUnit 的單元測試,Spek 也會先把自己的所有測試跑完,然后才會執行到我們想要執行的測試。這就意味著在 Speck 測試編寫了很多之后,執行其他測試就會等待很久。
  • 不能編寫 Spring 集成測試。
  • 我在寫 demo 的時候發現,它的 IDEA 插件在 Windows 上面無法工作。

如果這些痛點你都能忍,那我也不建議使用這個框架,畢竟上面已經有更好的選擇了。

總結

現在我們有了兩個 JUnit 以外的測試框架選擇。當然它們也不是完美的,JUnit 仍然是那個最穩定、風險最低的那一個。但如果你想嘗試一下這兩個框架的話,可以考慮一下這些方面:

(1) 生產代碼的編程語言:

  • 如果是 Kotlin,那么可以考慮 Kotest,不要考慮 Spock
  • 如果是 Java,那么這兩個都值得考慮

(2) 語言熟悉程度:Kotlin 明顯是比 Groovy 更加流行,這個角度考慮的話 Kotest 是更優的選擇

(3) 測試框架的流行程度(這方面我不知道有什么評價標準,只是作為參考):

  • 兩個框架在 GitHub 上的 star 數量半斤八兩,一個 3.1k,一個 3.2k(JUnit 也才 4.9k)
  • 在 MVNRepository 上,Spock 的 usage 明顯高于 Kotest

(4) IDEA 的集成:

  • Spock 在這方面完全沒有問題
  • Kotest 需要安裝插件,并且需要配置才能運行單個測試

(5Gradle 集成:

  • Spock 完美集成
  • Kotest 不能執行單個測試
責任編輯:趙寧寧 來源: Thoughtworks洞見
相關推薦

2017-01-14 23:42:49

單元測試框架軟件測試

2024-10-16 16:09:32

2009-06-01 10:47:32

jboss seam例jboss seam開jboss seam

2023-12-24 10:00:35

Java單元測試

2009-08-19 09:00:48

單元測試框架自動化測試

2010-08-27 09:11:27

Python單元測試

2023-07-26 08:58:45

Golang單元測試

2011-05-16 16:52:09

單元測試徹底測試

2017-01-16 12:12:29

單元測試JUnit

2017-01-14 23:26:17

單元測試JUnit測試

2022-04-27 08:17:07

OCMock單元測試集成

2011-06-14 15:56:42

單元測試

2020-08-18 08:10:02

單元測試Java

2024-04-26 11:14:34

C#單元測試框架

2017-03-23 16:02:10

Mock技術單元測試

2021-05-05 11:38:40

TestNGPowerMock單元測試

2011-07-04 18:16:42

單元測試

2020-05-07 17:30:49

開發iOS技術

2011-06-14 15:39:46

單元測試

2012-05-21 09:41:54

XcodeiOS單元測試
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 日日夜夜天天 | 久久激情视频 | 亚洲综合色视频在线观看 | 黄色大片在线免费观看 | 最新日韩在线视频 | 成人教育av | 国产高清免费 | 精品视频一区二区三区在线观看 | 在线国产小视频 | 国产精品不卡 | av在线播放一区二区 | 国产精品免费看 | 久久av一区 | 国产清纯白嫩初高生在线播放视频 | 亚洲欧美一区二区三区视频 | 日本成人在线观看网站 | 精品成人免费视频 | 中文字幕丁香5月 | 国产精品免费在线 | 国产在线精品一区二区三区 | 久久国产精品无码网站 | 久久久国产精品 | 亚洲视频第一页 | 精品免费视频一区二区 | 成人欧美一区二区三区黑人孕妇 | 91精品91久久久 | 亚洲成人免费 | 91在线免费观看 | 爱爱爱av | 成人av播放 | 日韩视频一区二区 | 亚洲成人精品在线 | 日韩国产在线 | 亚洲免费人成在线视频观看 | 日日爽 | 91美女视频 | 天堂久 | 亚洲三区视频 | 亚洲综合在线视频 | 一级毛片在线视频 | 视频一区二区三区中文字幕 |