Kai Chen
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    2
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # 六角鼠年鐵人賽 Week 14 - Spring Boot - JUnit 5 單元測試 ==大家好,我是 "為了拿到金角獎盃而努力著" 的文毅青年 - Kai== ## 名句 Kobe Bryant :::info Everything negative — pressure, challenges — is all an opportunity for me to rise. ::: ## 主題 今天來跟大家分享的是做單元測試專用的套件 JUnit (版本5)。 這應該是一個在任何網路Java貼文中,只要涉及程式教學的專案幾乎都會引用的套件。 工程師都會同意的就是程式上線前必須要經過測試,通過專業測試員的檢測才能知道程式到底有哪些操作會引發錯誤?功能在任何情境下是否都能正常運作?資訊安全?系統負載能力等等。 這件事情在系統尚未發展成巨型架構、搭載許多功能時通常不會有任何問題。但在系統越趨成熟或為了分流產出了許多客製化的功能後,測試這件事情就會越來越棘手。 舉例: 系統一共有 30個功能,測試員接收 release 後,需要花費五天測試180個試驗並產出結果,平均一個功能會用上六種情境。 半年後,系統擴充到了60個功能,我們都會期望的是測試員同樣在每個功能上用上六種情境,並產出360個試驗結果出來。這時測試員可能會需要十天。 但現實往往沒有這麼美好,如果說只能給同樣5天呢? 測試員就得斟酌降低每一個功能使用的情境;如果說系統多出來的30功能中,有10個在A案,另外20個在B案,那測試員要測試的便是40+50=90個功能。 這都還只是很小的數字,巨型系統諸如銀行、電商等,可能達到上百上千的功能,人工偵測、除錯有其極限且所費不貲 (時間就是金錢)。 因此測試這件事情本身也逐漸需要被程序化,畢竟測試本身是一件變化性質少且重複性質高的事情,單元測試、功能測試、系統測試等都可以被自動化處理。 ## 介紹 JUnit 5 JUnit 起於 1997 年,由 Kent Beck 和 Erich Gamma 兩位歷史級的工程師,在旅程的飛機上合作構思出的架構,專門給 Java 做程式測試用的,當時並沒有一套成熟、完整的體系可以增進 Java 工程師在開發上的難處,因此在 JUnit 橫空出世後,大幅的減低了工程師的負擔,而測試流程的改變也增進了系統的品質,間接降低了企業費用的支出。 目前在 Java 來說,**JUnit** 和 **TestNG** 分別為兩大測試套件,他們都可以幫助工程師編寫可重複運行且架構工整的單元測試程式。而 JUnit 本身發展得比較久,從最初 1997 年開發的版本到 2019 年的 JUnit 5 也有20年的發展歷史,名稱上來說也比較好記,因此成為大多數要踏入學習單元測試的首選。 > 基本上大多數的功能兩者都在伯仲之間,最大的不同只是 **TestNG** 可以做 Dependent Tests,而 JUnit 5 並未支援此項目 目前來說 JUnit 已經到了第五個版本,且 JDK 的需求也提升到了 1.8 版本,可能會有部分的使用者不適應變化的部分,畢竟 Java 7 -> 8 包含了許多突破性的改變,詳細不在這裡說明,大家可以自行找些比較的文章了解^^ ## JUnit 5 的三大模組 JUnit 5 由 **JUnit Platform**、**JUnit Jupiter**、**JUnit Vintage** - Platform - 是 JUnit 用於 JVM上啟動的服務,用於支持測試時候的程式 - Jupiter - 是 JUnit 5 新的 Compile 和 Extends 模組,主要用於讀寫測試程式語法 - Vintage: - 用於兼容 JUnit 4 和 JUnit 3 版本的模組 --- 觀察下面的架構圖會更清楚其模組相依的關係 ![](https://i.imgur.com/LkKZvIe.png) ## Why JUnit 5 ? 比起 JUnit 4 來說,JUnit 5 要求最低的 JDK 版本為 1.8 版,意味著在5版本是可以支持 Lambda 表達式與 Stream API。 > 這兩個功能就是 1.7 與 1.8 最大的分歧。相較於 Java 11 -> 12 還有著更多更大的影響。但這裡不說這個~ 且 Junit 5 多了模組測試,可以讓測試程式解析模組解耦與依賴的問題,其他如支持動態測試、重複測試、參數化測試等。 上述都是讓 Kai 這邊選擇使用5版本來介紹而不是4或3版本的原因,畢竟這類不會動用到基本盤的套件,能用新版本的功能就選用新版本的,但需要強調使用的依舊是新版本中的穩定版本,產品內的絕對不要使用任何 beta 版的套件,這觀念套用在任何工作都適用,請絕對不要把還在嘗試的東西放入正式產品中。 ## Gradle 設定 首先需要把 JUnit 5 的套件給引用,Kai 這邊使用的是 Gradle,因此只需要在 dependencies 中加入下面的套件名稱即可,因為只有在測試的時候使用,因此只需要設定 testImplementation 和 testRuntimeOnly。 ```xml dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.1' } ``` ## 常用的 Annotation 與其參數介紹 JUnit 5 與 4 和 3 一樣,都是透過 Annotation 的方式進行測試模組功能的呼叫與執行。 在個別的 Annotation 設定忠亦可加入部分功能提供的屬性參數,以增加對於執行測試的細節調整。 ### @DisplayName(" [Input String] ") 說明: 測試類別或程式的名稱,會在執行前印出在 Console 上 撰寫位置: 宣告 Class 或 function 的程式碼前 ### @BeforeAll 說明: 在該 Class 進行 @Test 的程式前,先行運行的程式,多用於定義物件或參數處理 撰寫位置: 宣告 function 的程式碼前 ### @AfterAll 說明: 在該 Class 進行完所有 @Test 的程式後,才運行的程式,多用於釋放資源或整理結果訊息 撰寫位置: 宣告 function 的程式碼前 ### @BeforeEach 說明: 在任何 @Test 進行前,先行運行的程式,多用於印出 Trace Log 或重整資源 撰寫位置: 宣告 function 的程式碼前 ### @AfterEach 說明: 在任何 @Test 進行後,才運行的程式,多用於印出結果、Trace Log 或釋放資源 撰寫位置: 宣告 function 的程式碼前 ### @Test 說明: 執行測試的程式,會依照設置程式碼的先後順序,優先執行排列較上面的 @Test 程式 撰寫位置: 宣告 function 的程式碼前 ### @Disabled 說明: 標記該 Annotation 的類別或程式將不會被 JUnit 捕捉放入預備執行的程式列中,多為測試時候動態處理是否執行特定環節時候使用 撰寫位置: 宣告 Class 或 function 的程式碼前 ### @Nested 說明: 用於內部類別的測試 撰寫位置: 宣告非 public 的 Class 的程式碼前 ### @RepeatedTest( value = [Integer], name=" [Input String] ") 說明: 用於設定該測試需要重複的次數,另外可放入 name 的屬性做到輸出目前的次數與測試訊息 支援的參數如下: - {displayName}: 會秀出 @DisplayName 的設定值 - {currentRepetition}: 會秀出目前執行的次數值 - {totalRepetitions}: 會秀出總共需要執行的次數值 範例: name = "{displayName} 第{currentRepetition}次測試,總共需要執行{totalRepetitions}次" 撰寫位置: 宣告 function 的程式碼前 ### @ParameterTest 須搭配資料來源方可建立參數化測試,可搭配的參數 Annotation 如下: - @ValueSource: 支援 @NullSource、@EmptySource、@NullAndEmptySource 設立 Object 類型物件陣列值以作測試輸入值 **範例**: ```java= @ValueSource(ints = {1,2,3}) @ValueSource(strings = {"1","2","3"}) @ValueSource(strings = {""," ",null,"\r\n"}) ``` - @EnumSource: 支援使用 ENUM 類作測試輸入值,可在參數內輸入 ENUM 類,或是 function 內輸入後讓JUNIT 偵測目標 Class 來用,可輸入 name 和 mode 屬性去設定使用特定的 ENUM 類中的物件或排除使用特定物件 **範例**: ```java= // 一般寫法 @EnumSource(Test.class) public void testWithEnumSource(TemporalUnit unit) // 讓 JUnit 自動偵測寫法 @EnumSource() public void testWithEnumSourceAutoDetection(Test unit) // 限定使用物件 @EnumSource(names = {"TestObject1","TestObject3"}) public void testWithEnumSourceAutoDetection(Test unit) // 排除使用物件 @EnumSource(mode = EXCLUDE, names = {"TestObject2","TestObject4") public void testWithEnumSourceAutoDetection(Test unit) // 支援 REGEX 寫法 @EnumSource(mode = MATCH_ALL, names = {"^.*TestObject$") public void testWithEnumSourceAutoDetection(Test unit) ``` - @MethodSource: 支援使用類別方法作為測試輸入值,類別方法必須回傳 Stream 物件且其包含參數必須為繼承 Ojbect 類的物件。 **範例**: ```java= // 一般寫法 @MethodSource("stringProvider") public void testWithExplicitLocalMethodSource(String argument) static Stream<String> stringProvider() { return Stream.of("apple", "banana"); } // 讓 JUnit 自動偵測寫法 @MethodSource() public void testWithExplicitLocalMethodSource(String argument) static Stream<String> testWithExplicitLocalMethodSource() { return Stream.of("apple", "banana"); } ``` - @CsvSource 支援使用 CSV 格式輸入值作測試值,以逗號分隔,一列有多少個參數,則 function 便需要 input 多少個參數,可使用同一參數類型 ... 的方式處理。 另外 @CsvSource 預設以逗號切割一列的值,若字符串中間有逗號不想因此被切割,可以用單引號將其框住,避免完整物件值被切分開。 支援屬性如下 - value: 預設屬性,作測試輸入值的接收參數 - delimiterString: 可調整切割一列物件值的字串符號 - nullValues: 設定特定字串符,當物件值符合時會自動變更為 Null **範例**: ```java= // 一般寫法 @CsvSource({"apple,1","banana,2","'lemon, lime', 0xF1"}) public void testWithCsvSource(String fruit, int rank) { // 當物件值為 NIL 時,將其設置為 Null @CsvSource(value = { "apple, banana, NIL" }, nullValues = "NIL") ``` - @CsvFileSource 類似於 @CsvSource,但測試值為外部文件輸入,且不想被分割的物件值部分,@CsvSource 是用單引號,但 @CsvFileSource 則使用雙引號 支援屬性如下: - resources: 資料來源 - numLinesToSkip: 略過行數,通常設為 1,因為多數 CSV 檔案第一行為資料欄位名稱 **範例**: ```java= @CsvFileSource(resources = "/two-column.csv", numLinesToSkip = 1) ``` ## 結語 :::danger 簡單介紹完了 JUnit 5,最重要的還是開發者撰寫出適合自己公司系統業務需求的測試程式,每一次更新就會增加一段給新功能的測試,同時因應面對的問題,增加不同例外狀況模擬,確保系統可以永遠保持在一個穩定的狀況中成長。 下一篇將介紹會合併一起使用於單元測試的框架 Mockito~ [六角鼠年鐵人賽 Week 15 - Spring Boot - Mockito 模擬測試框架](/g3YGfarYSA6iu8IBfyq1Gg) ::: 首頁 [Kai 個人技術 Hackmd](/2G-RoB0QTrKzkftH2uLueA) ###### tags: `Spring Boot`,`w3HexSchool`

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully