2021年12月25日 星期六

4.2. 使用requires和requires transitive

使用requires some-module表示當前模組依賴於指定的some-module。若使用 requires transitive some-module表示任何需要當前模組的模組也將依賴於some-module。單字transitive可以解釋為「可傳遞的」,意思大概就是把這層依賴關係也傳遞出去。

這樣的現象在開源的程式設計裡其實相當常見。若模組B在開發過程中引用了模組C;當模組A要引用模組B時,自然也會需要模組C。因此B依賴C的關係,傳遞給A後變成A除了依賴B之外也要依賴C。原本的模組資訊檔使用requires時可以編寫為:

module B {
    exports B;
    requires C;
}
module A {
    requires B;
    requires C;
}
若使用requires transitive則可以簡化為:
module B {
    exports B;
    requires transitive C;
}
module A {
    requires B;
}

以上假設模組和套件同名。另外,只是模組資訊檔可以簡化,模組A依然需要模組B與模組C的函式庫。

相似的概念,我們可以將zoo專案的4個模組全部重新檢視。

1. 修改模組zoo.animal.feeding

模組zoo.animal.feeding是最基本的,沒有變動:

module zoo.animal.feeding {
    exports zoo.animal.feeding;
}

2. 修改模組zoo.animal.care

模組zoo.animal.care原本是:

module zoo.animal.care {
    exports zoo.animal.care.medical;
    requires zoo.animal.feeding;
}
將行3修改如下。以後任何模組使用到模組zoo.animal.care都不需要在模組資訊檔內參照模組zoo.animal.feeding,我們將在下個模組驗證這個結果:
module zoo.animal.care {
    exports zoo.animal.care.medical;
    requires transitive zoo.animal.feeding;
}

3. 修改模組zoo.animal.talks

模組zoo.animal.talks原本是:
module zoo.animal.talks {
    exports zoo.animal.talks.content;
    exports zoo.animal.talks.media;
    exports zoo.animal.talks.schedule;
    requires zoo.animal.feeding;
    requires zoo.animal.care;
}

簡化時可以直接移除原本行5,並把行6由requires也改為requires transitive,以後任何模組使用到模組zoo.animal.talks都不需要在模組資訊檔內參照模組zoo.animal.care:

module zoo.animal.talks {
    exports zoo.animal.talks.content to zoo.staff;
    exports zoo.animal.talks.media;
    exports zoo.animal.talks.schedule;
    requires transitive zoo.animal.care;
}

4. 修改模組zoo.staff

模組zoo.staff原本如下:

module zoo.staff {
    requires zoo.animal.feeding;
    requires zoo.animal.care;
    requires zoo.animal.talks;
}
簡化後可以去除兩個requires敘述:
module zoo.staff {
    requires zoo.animal.talks;
}

4.1. 使用exports

使用exports package-name可以將一個套件公開或導出給其他模組使用,也可以將套件導出給特定模組使用。例如原本的模組zoo.animal.talks不限制對象導出套件zoo.animal.talks.content:

module zoo.animal.talks {
    exports zoo.animal.talks.content;
    // others
}  

以下將限制導出對象只給zoo.staff模組:

module zoo.animal.talks {
    exports zoo.animal.talks.content to zoo.staff;
    // others
}  

如此模組zoo.staff沒有影響,但其他模組將無法存取zoo.animal.talks.content套件。

當exports某一個套件時,該套件內所有public類別(class)、介面(interface)和列舉型別(enum)的public和protected成員都將允許被其他模組使用。private的成員依然限定在類別內部存取,default層級的成員也限定在套件內部存取,這2種的存取控制不受模組化影響。

下表列出完整的存取控制選項:

存取層級

模組內部

模組外部

private

只能讓同一類別內部的其他成員存取

無法存取

default

只能讓同一套件內的其他類別存取

無法存取

protected

同一套件或具有繼承關係的子類別可以存取

若exports套件,則套件內具備繼承關係的子類別可以存取

public

不受限制

若exports套件則不受限制


3.3 使用Eclipse設定專案的模組相依關係

前面一共建立了4個專案,並各內含一個模組。模組間的依賴關係如下: 

模組

依賴模組

zoo.animal.feeding

無

zoo.animal.care

zoo.animal.feeding

zoo.animal.talks

zoo.animal.feeding

zoo.animal.care

zoo.staff

zoo.animal.feeding

zoo.animal.care

zoo.animal.talks

所以在編譯新的模組之前,都要先把相依的模組放到每個專案的mods資料夾內。然後使用javac的命令列指令,以「--module-path」指定相依的模組JAR檔目錄,以「-d」指定程式碼目錄,然後進行編譯。

雖然使用javac可以陸續編譯所有類別,但在Eclipse會老是顯示編譯失敗,讓人看了心裡不踏實:


解決方式是在Eclipse上設定每一個專案相依的模組,分別示範如下。

專案zoo.animal.feeding

因為沒有相依模組,因此不用設定。

專案zoo.animal.care

專案zoo.animal.care相依於zoo.animal.feeding,設定方式為:

1. 使用滑鼠右鍵點擊專案,選擇「Properties」,先開啟專案的屬性視窗。然後點選左側的Java Build Path選項,再點擊右側的Projects頁籤,可以看到和編譯本專案相關的2個路徑,一個是Modulepath,一個是Classpath。在過去Java還沒有模組化功能前,使用舊版Eclipse只會看到Classpath的選項:

2. 點選Modulepath選項並點擊按鍵Add,在彈出的Required Project Selection選單中,勾選專案zoo.animal.feeding:

3. 完成之後可以看到Modulepath下出現相依專案zoo.animal.feeding,點擊按鍵Apply and Close關閉專案屬性視窗:
4. 完成專案相依模組的設定後,專案編譯通過:

專案zoo.animal.talks

依循前例,開啟zoo.animal.talks專案的屬性視窗並設定Modulpath相依於專案zoo.animal.feeding與zoo.animal.care:

專案zoo.staff

依循前例,開啟zoo.staff專案的屬性視窗並設定Modulpath相依於專案zoo.animal.feeding、zoo.animal.care與zoo.animal.talks:



3.2 使用requires相依模組外的套件

建立zoo.animal.care模組

接下來我們要建立zoo.animal.care模組,該模組有2個套件: 

  1. 套件zoo.animal.care.medical包含開放給其他模組使用的類別和方法。 
  2. 套件zoo.animal.care.details只供模組自己使用,不對外開放,可以視為動物的醫療隱私。

下圖顯示該模組的內容。記得所有模組都必須有1個模組資訊文件!

和建立模組zoo.animal.feeding的處理方式一樣:

  1. 使用Eclipse建立專案zoo.animal.care。
  2. 建立2個套件。
  3. 每個套件下建立各自類別。
  4. 建立模組資訊檔案module-info.java。

模組的2個類別如下,因為只是要驗證模組對存取控制的效果,基本上都沒有甚麼內容:

package zoo.animal.care.details;
import zoo.animal.feeding.Task;
public class HippoBirthday {
    private Task task;
}
package zoo.animal.care.medical;
public class Diet {
}
這次module-info.java比較特別:
module zoo.animal.care {
    exports zoo.animal.care.medical;
    requires zoo.animal.feeding;
}
行1指定模組的名稱,行2使用exports指出要公開的套件,以便後續其他模組可以使用它。到目前為止和模組zoo.animal.feeding是相似的。
在行3使用「requires」關鍵字指定本模組將相依於zoo.animal.feeding模組,這也是和先前模組最大差異的地方。
另外和前模組一樣,我們也在與src目錄的同一檔案階層建立了一個名為mods的目錄,然後將前一個專案打包好的zoo.animal.feeding.jar放入其中。前述步驟都完成後,專案結構如下:

編譯和打包zoo.animal.care模組

建立模組後,讀者可以發現某些*.java檔案在Eclipse內將編譯失敗,這是因為我們只使用Eclipse建立模組專案,但並未設定Eclipse專案間的相依關係,後續會再說明。而且目前編譯使用javac的指令進行,是否編譯失敗以管理者視窗的輸出結果為主。
仿照前之前的方式開啟以模組專案zoo.animal.care的根目錄為預設路徑的管理者視窗,執行以下指令進行編譯:

javac 
--module-path mods
-d src 
src/zoo/animal/care/details/*.java src/zoo/animal/care/medical/*.java src/module-info.java
編譯模組過程未出現錯誤訊息:
進行打包的指令如下:
jar  
-cvf mods/zoo.animal.care.jar 
-C src/ .

更新(refresh)專案後,可以看到src目錄下出現*.class檔案,而且mods目錄內有zoo.animal.care.jar檔案:

建立、編譯、打包zoo.animal.talks模組

接下來的模組相依情形會愈來愈明顯。

由圖7-1我們知道zoo.animal.talks模組相依於zoo.animal.feeding模組和 zoo.animal.care模組,這也表示著module-info.java檔案中必須有2個requires 關鍵字。模組zoo.animal.talks的內容可以參考圖7-2,這裡就不在重複。此外模組zoo.animal.talks具備的3個套件將全部開放。以下列舉各套件的各類別內容:

1. 套件zoo.animal.talks.content的類別ElephantScript內容如下:

package zoo.animal.talks.content;
public class ElephantScript {
}
2. 套件zoo.animal.talks.content的類別SeaLionScript內容如下:
package zoo.animal.talks.content;
public class SeaLionScript {
}
3. 套件zoo.animal.talks.media的類別Announcement 內容如下:
package zoo.animal.talks.media;
public class Announcement {
    public static void main(String[] args) {
        System.out.println("We will be having talks");
    }
}
4. 套件zoo.animal.talks.media的類別Signage 內容如下:
package zoo.animal.talks.media;
public class Signage {
}

5. 套件zoo.animal.talks.schedule的類別Weekday 內容如下:

package zoo.animal.talks.schedule;
public class Weekday {
}
6. 套件zoo.animal.talks.schedule的類別Weekend 內容如下:
package zoo.animal.talks.schedule;
public class Weekend {
}
7. 最後,模組資訊檔module-info.java內容如下:
module zoo.animal.talks {
    exports zoo.animal.talks.content;
    exports zoo.animal.talks.media;
    exports zoo.animal.talks.schedule;
    requires zoo.animal.feeding;
    requires zoo.animal.care;
}

另外和前模組一樣,我們也在與src目錄的同一檔案階層建立了一個名為mods的目錄,然後將前2個專案打包好的zoo.animal.feeding.jar、zoo.animal.care.jar放入其中。前述步驟都完成後,專案結構如下:

模組專案建立完畢之後,仿照前之前的方式開啟以模組專案zoo.animal.talks的根目錄為預設路徑的管理者視窗,執行以下指令進行編譯:
javac 
--module-path mods
-d src 
src/zoo/animal/talks/content/*.java
src/zoo/animal/talks/media/*.java
src/zoo/animal/talks/schedule/*.java
src/module-info.java

執行以下指令進行打包:

jar  
-cvf mods/zoo.animal.talks.jar 
-C src/ .

更新(refresh)專案後,可以看到src目錄下出現*.class檔案,而且mods目錄內有zoo.animal.talks.jar檔案:

建立、編譯、打包zoo.staff模組

由圖7-1我們知道最後一個模組zoo.staff相依於其他3個模組,這也表示著module-info.java檔案中必須有3個requires 關鍵字。模組zoo.staff的內容如下:

唯一的套件與類別:

package zoo.staff;
public class Jobs {
}

模組資訊檔module-info.java內容如下:

module zoo.staff {
    requires zoo.animal.feeding;
    requires zoo.animal.care;
    requires zoo.animal.talks;
}

另外和前模組一樣,我們也在與src目錄的同一檔案階層建立了一個名為mods的目錄,然後將前3個專案打包好的zoo.animal.feeding.jar、zoo.animal.care.jar、zoo.animal.talks.jar都放入其中。前述步驟都完成後,專案結構如下:


模組專案建立完畢之後,仿照前之前的方式開啟以模組專案zoo.staff的根目錄為預設路徑的管理者視窗,執行以下指令進行編譯:
javac 
--module-path mods
-d src 
src/zoo/staff/*.java
src/module-info.java

執行以下指令進行打包:

jar  
-cvf mods/zoo.staff.jar 
-C src/ .

更新(refresh)專案後,可以看到src目錄下出現*.class檔案,而且mods目錄內有zoo.staff.jar檔案:



3.1 使用exports開放模組內的套件

我們知道專案Zoo的其他模組都相依於zoo.animal.feeding,在開始建立其他模組之前,需要先「開放」zoo.animal.feeding,此時藉由修改module-info.java文件達成這個需求:

module zoo.animal.feeding {
    exports zoo.animal.feeding;
}
行2的exports關鍵字用於指示讓模組外的Jav模組A碼可以使用列舉的套件。如果沒有exports關鍵套件,模組就只能如先前範例單獨執行。
更新module-info.java後重新執行編譯和打包的指令:
  • javac -p mods -d src src/zoo/animal/feeding/*.java src/module-info.java
  • jar -cvf mods/zoo.animal.feeding.jar -C src/ .
如此可以更新zoo.animal.feeding.jar檔案。

2021年12月12日 星期日

2. 建立和執行模組化程式

在本節中,我們將建立、構建和執行zoo.animal.feeding模組。我們選擇這個作為開始是因為所有其他模組都相依於它。 下圖顯示了該模組的設計。除了module-info.java檔案外,它還有1個套件,裡面有1個類別:
後續將建立、編譯、執行和打包zoo.animal.feeding模組。

建立模組專案

建立套件、類別與模組資訊檔案

首先建立專案zoo.animal.feeding與套件zoo.animal.feeding,本例專案名稱與套件名稱相同;然後建立簡單的類別Task.java,內容如下:

package zoo.animal.feeding;
public class Task {
    public static void main(String... args) {
        System.out.println("All are fed!");
    }
}

接下來是建立模組資訊(module info)檔案。模組資訊檔案和一般Java類別之間有一些主要區別:

  1. module-info檔案必須位於模組的根目錄中,一般Java類別應該在套件中。
  2. module-info檔案內容宣告模組時使用關鍵字module而不是class、interface或enum。
  3. 模組檔案名稱遵循套件名稱的命名規則,它的名稱中通常包含「.」。一般類別和套件名稱不允許有「-」,但模組可以。
後續還會說明更多模組資訊檔案的內容規則,就一個最簡單的模組資訊檔案內容如下:
module zoo.animal.feeding {
}

此外,我們在與src目錄的同一檔案階層建立了一個名為mods的目錄,我們將在本章稍後部分使用它來存放與自身模組相依的其他模組。這個目錄可以任意命名,但mods是一個比較通用名稱。完成後專案目錄結構如下:

專案結構的src目錄是模組目錄,模組資訊檔案就在它根目錄。我們也有zoo.animal.feeding套件,以作業系統的角度來看每一層套件就表示一個子目錄,可以由Eclipse的Project Explorer和Navigator比較。Task類別位於其套件的相應子目錄中。

使用Eclipse建立的專案預設會將*.java檔案自動編譯為*.class檔案,並存放在專案內的bin目錄中。不過我們只是使用Eclipse協助建立專案,後續編譯將以指令進行。

編譯模組專案

在可以執行模組化程式碼之前,需要先予以編譯,指令如下。為了說明指令列選項讓讀者容易理解,因此拆解成4行,實際執行時必須合併為1行,或是使用作業系統的斷行符號,如Linux使用「\」:

javac 
--module-path mods
-d src 
src/zoo/animal/feeding/*.java src/module-info.java

說明:

2

使用選項「--module-path」指示任何自定義模組檔案的位置,本例是mods目錄。由於mods目前沒有任何相依模組檔案,因此可以忽略本選項。此外選項「--module-path」和大家熟悉的選項「-classpath」相似,當處理模組化程式時,可以將「--module-path」視為替換「-classpath」選項。

3

使用選項「-d」指定放置編譯完成的類別檔案的目錄。

4

指令的結尾是要編譯的Java檔案清單。可以單獨列出這些檔案,也可以對子目錄中的所有Java檔案使用萬用字元「*.java」。使用空白(space)區隔清單內容。

就像類別路徑一樣,我們可以在指令列中使用縮寫。選項「--module-path」和「-p」是等效的,因此前述指令列可以修改如下:

javac -p mods -d src src/zoo/animal/feeding/*.java src/module-info.java

以本例而言,執行指令時必須在src資料夾。要找出src目錄的路徑可以在src目錄的節點上以滑鼠右鍵點擊,選擇Properties:

彈出「Properties for src」視窗後點擊以下Location圖式:

在彈出的檔案總管視窗的資料夾路徑處,改鍵入「cmd」文字並點擊鍵盤Enter鍵:

彈出系統管理員視窗,且目前路徑即為專案的src目錄:

鍵入編譯模組的指令並Enter:

範例專案每一個的*.java將伴隨著*.class檔案:

在一開始學習Java時理解java與javac指令是必要的。但實際在開發專案時,如果繼續組裝指令列會發現它們變得冗長而復雜。我們使用Eclipse開發時,編譯和執行都由IDE代勞;若要結合DevOps的開發流程,大多數開發人員會使用自動化構建工具,例如Maven或Gradle。讀者若對這些進階內容有興趣,可以參考「Spring Boot情境式網站開發指南:使用Spring Data JPA、Spring Security、Spring Web Flow」一書的「CH1. 使用Maven管理Java專案」。

執行模組專案

在打包模組之前,我們應該先確認該模組是否可以正確執行。為此我們需要了解完整的語法。假設有一個名為lab.module的模組,該模組中具備org.some的套件,和帶有一個main()方法的Test的類別。下圖顯示了執行模組的語法,特別注意lab.module/org.some.Test部分。請務必記住指定的模組名稱後跟著「/」,之後才是完整的類別名稱。

現在我們已經理解了語法,可以編寫指令來執行zoo.animal.feeding套件中的Task類別。在以下指令中,套件名稱和模組名稱相同,都是zoo.animal.feeding。模組名稱經常會與套件的完整名稱相同,或是取套件開頭的幾個名稱空間(namespace),如zoo.animal。
java 
--module-path src 
--module zoo.animal.feeding/zoo.animal.feeding.Task
說明

2

使用「--module-path」選項指定模組路徑,也可以使用「-p」。

3

使用「--module」指定執行對象,也可以使用「-m」。

執行對象以「/」區隔。之前為模組名稱,之後為完整的套件和類別名稱。

在本範例中行2使用src作為模組路徑,是因為這是本專案、同時也是前一個範例編譯後產出*.class的地方。後續範例打包模組成為JAR檔案時,執行時就會改指定JAR檔案位置。

執行「java -p src -m zoo.animal.feeding/zoo.animal.feeding.Task」後可以得到視窗輸出「All are fed!」字樣:


打包模組專案

如果我們只能在建立模組的路徑如src目錄中執行它,那程式模組化的用處就不大。我們的下一步是打包它,讓模組可以在其他地方執行,或是給其他模組使用。指令如下:

jar  
-cvf mods/zoo.animal.feeding.jar 
-C src/ .
說明

2

使用「-cvf」選項指定要打包為JAR檔案。

本例中「mods」是JAR檔案的產出目錄,要事先建立。「zoo.animal.feeding.jar」是JAR檔案名稱。

3

使用「-C」指定編譯好的*.class檔案位置。

本例中「src/ .」表示路徑src內的所有檔案。

事實上該指令不只可以打包Java的模組化程式為一個JAR檔案,即便「非模組化」程式也是相同的打包指令。打包完成後該Java程式就可以給其他專案使用!

以指令「jar -cvf mods/zoo.animal.feeding.jar -C src/ .」進行打包:

更新(refresh)專案後,可以看到mods目錄出現zoo.animal.feeding.jar檔案:

現在讓我們以打包後的模組化JAR檔案執行程式:

java 
--module-path mods 
--module zoo.animal.feeding/zoo.animal.feeding.Task
注意行2指定的模組路徑是mods而不是src,目錄mods已經存在打包好的「zoo.animal.feeding.jar」,目錄src存放編譯好的鬆散*.class檔案。

執行「java -p mods -m zoo.animal.feeding/zoo.animal.feeding.Task」後可以得到視窗輸出「All are fed!」字樣:




2021年12月5日 星期日

Spring REST API開發與測試指南:使用Swagger、HATEOAS、JUnit、Mockito、PowerMock、Spring Test

簡介:

鑒於前後端分離與微服務架構的興起,伺服器端需要提供API供使用者存取。傳統的SOAP協定是以複雜的XML作為傳送媒介,已經逐漸被使用輕量化JSON傳送資訊的REST API取代,成為開發與建置API的主流。
除此之外,API的開發和上線應用,效率與品質應該兼顧;透過DevOps流程結合開發、測試、營運,可以更快地將新的或修正的功能發佈給使用者;在這樣的環節中,自動化測試至關重要。
本書第一部分介紹Java的單元測試框架如JUnit5、Mockito、PowerMock,並探討如何提升程式的可測試性。
第二部分先介紹REST API的概念與背景,在使用Spring Boot建置初版REST API後,逐章示範API的例外處理、Swagger文件說明、版本控制、分頁與排序、與套用HATEOAS等。
最後結合Spring Test模組對REST API進行單元測試、整合測試、與端對端測試,以期產出符合企業品質與效率的REST API。

書綱:

<<PART-1>> 建構Java單元測試

CH01: 使用JUnit執行單元測試 (p.41)

1.1. 正式程式碼與測試程式碼的差異

1.2. 單元測試常見名詞說明

1.3. 使用JUnit 5

1.3.1. 簡介JUnit 5

1.3.2. 建立第一個JUnit 5測試專案

1.3.3. 在JUnit 5的架構下執行JUnit 4的測試

1.3.4. 執行JUnit 5的大量測試

1.3.5. 單元測試的生命週期

1.3.6. 使用Assertions

1.3.7. 使用Assumptions

CH02: 建立測試替身 (p.28)

2.1. 執行單元測試

2.1.1. 單元測試的效益

2.1.2. 單元測試的特徵

2.1.3. 單元測試的限制

2.2. 認識測試替身(test doubles)

2.2.1. 測試替身的意義

2.2.2. 測試替身的分類

2.3. 使用Dummy Object

2.3.1. Dummy Object使用情境

2.3.2. Dummy Object使用範例

2.4. 使用Test Stub

2.4.1. Test Stub使用情境

2.4.2. Test Stub使用範例

2.5. 使用Test Spy

2.5.1. Test Spy使用情境

2.5.2. Test Spy使用範例

2.6. 使用Mock Object

2.6.1. Mock Object使用情境

2.6.2. Mock Object使用範例

2.7. 使用Fake Object

2.7.1. Fake Object使用情境

2.7.2. Fake Object使用範例

CH03: 使用Mockito(一) (p.45)

3.1. 認識Mockito

3.1.1. 了解單元測試質量

3.1.2. 設定Maven關聯Mockito

3.2. 章節情境說明

3.3. 使用Mockito驅動單元測試

3.4. 以when()與thenReturn()定義Mock Object的方法

3.5. 以when()與thenThrow()定義Mock Object的方法

3.6. 使用參數配對器(ArgumentMatcher)

3.6.1. 使用類別ArgumentMatchers定義的萬用配對器

3.6.2. 使用介面ArgumentMatcher建立自定義的參數配對器

3.7. 以verify()驗證Mock Object的方法呼叫

3.7.1. 使用Mockito.verify()

3.7.2. 使用Mockito.verifyNoMoreInteractions()

3.7.3. 使用Mockito.verifyNoInteractions()

3.8. 以when()與thenAnswer()定義Mock Object的方法

CH04: 使用Mockito(二) (p.46)

4.1. 章節情境說明

4.2. 快樂路徑與悲傷路徑的測試案例

4.2.1. 測試快樂路徑

4.2.2. 測試悲傷路徑

4.3. 測試void方法

4.3.1. 使用doThrow()

4.3.2. 使用doNothing()

4.3.3. 使用doAnswer()

4.3.4. 使用doCallRealMethod()

4.3.5. 使用連鎖doXXX()

4.4. 使用doReturn()測試有Return的方法

4.5. 使用參數捕捉器(ArgumentCaptor)

4.5.1. 捕捉一般參數

4.5.2. 捕捉使用泛型的集合物件參數

4.5.3. 捕捉陣列參數&可變動個數的參數

4.6. 使用InOrder驗證Mock Object呼叫順序

4.7. 使用Spy Object

4.7.1. 比較Spy Object與Mock Object

4.7.2. 局部偽冒Spy Object

4.7.3. 使用Spy Object驗證SUT內部方法交互作用

4.8. 使用Mockito標註類別

4.8.1. 使用@Captor

4.8.2. 使用@Spy

4.8.3. 使用@InjectMocks

CH05: 使用PowerMock測試legacy程式碼 (p.27)

5.1. 認識legacy程式碼

5.1.2. 測試障礙說明

5.1.3. 簡介PowerMock

5.2. 偽冒static方法

5.3. 抑止類別static成員執行初始化

5.4. 抑止父類別建構子的執行

5.5. 抑止類別建構子的執行

5.6. 抑止方法的執行

5.7. 偽冒private方法

5.8. 偽冒final方法

5.9. 建立final類別的Mock Object

5.10. 使用Mockito偽冒static方法、final方法與類別

5.10.1. 偽冒static方法

5.10.2. 偽冒final方法和類別

CH06: 依據Mockito的可測試性設計程式碼 (p.34)

6.1. 了解測試障礙的可能原因

6.2. 識別建構子的問題

6.2.1. 測試性不良的設計

6.2.2. 以依賴注入提升測試性的設計

6.3. 識別初始化的問題

6.3.1. 測試性不良的設計

6.3.2. 以依賴注入提升測試性的設計

6.4. 識別private方法對單元測試的影響

6.4.1. 測試性不良的設計

6.4.2. 使用關注分離提升測試性的設計

6.4.3. 提升存取層級以提升測試性

6.5. 識別static方法對單元測試的影響

6.5.1. 測試性不良的設計

6.5.2. 以非static的方法重構

6.5.3. 啟用Mockito偽冒static的支援

6.6. 識別final方法對單元測試的影響

6.6.1. 測試性不良的設計

6.6.2. 以非final的方法重構

6.7. 識別final類別對單元測試的影響

6.7.1. 測試性不良的設計

6.7.2. 以建立final類別的介面執行重構

6.8. 識別使用new呼叫建構子造成的測試問題

6.8.1. 測試性不良的設計

6.8.2. 以依賴注入提升測試性的設計

6.9. 識別使用static變數和程式碼區塊造成的測試問題

6.9.1. 測試性不良的設計

<<PART-2>> 建構Spring REST API

CH07: 簡介REST (p.17)

7.1. 什麼是REST?

7.2. 認識資源(Resources)

7.2.1. 辨識資源

7.2.2. URI模板(Templates)

7.3. 資源呈現(Representation)

7.4. HTTP方法

7.4.1. GET方法

7.4.2. HEAD方法

7.4.3. DELETE方法

7.4.4. PUT方法

7.4.5. POST方法

7.4.6. PATCH方法

7.4.7. CRUD 和 HTTP 動詞

7.5. HTTP狀態碼

7.6. 理查森的成熟度模型

CH08: 建構REST API專案 (p.40)

8.1. 介紹QuickPoll(快速投票)範例專案

8.2. 設計QuickPoll專案

8.2.1 資源識別

8.2.2 資源呈現(Representation)

8.2.3 端點(Endpoint)識別

8.2.4 HTTP方法識別

8.3. QuickPoll專案架構說明

8.4. 實作QuickPoll專案

8.4.1 建置Spring Boot專案

8.4.2 實作Domain物件

8.4.3 實作Repository

8.4.4 介紹API測試工具Talend API Test 

8.4.5 實作REST API

CH09: REST API的例外處理 (p.22)

9.1. 資源不存在的例外處理

9.1.1 資源不存在應回應404狀態碼

9.1.2 客製化回應資訊

9.2. 請求參數異常的例外處理

9.2.1 以JSR-303與JSR-349驗證請求參數

9.2.2 客製化回應資訊與參數驗證提示

9.3. 請求JSON格式異常的例外處理

CH10: 建立REST API使用文件 (p.32)

10.1. 簡介Swagger

10.2. 使用swagger-springmvc套件整合Swagger

10.3. 簡介Swagger UI

10.4. 客製Swagger UI的資源列表文件

10.4.1 Swagger UI需要調整的地方

10.4.2 客製Swagger UI的方式

10.5. 客製Swagger UI的API宣告文件

10.5.1 使用@Api客製群組端點概述

10.5.2 使用@ApiOperation客製端點操作敘述

10.5.3 使用@ApiResponse客製端點回應描述

10.6. 客製Swagger UI其他頁面呈現

10.7. 使用springfox-swagger2與springfox-swagger-ui套件整合Swagger

10.7.1 設定pom.xml

10.7.2 設定SwaggerConfig.java

CH11: REST API的版本控制、分頁與排序 (p.26)

11.1. 對API執行版本控制 (Versioning)

11.1.1 版本控制策略介紹

11.1.2 為範例專案套用版本控制

11.1.3 設定Swagger提供版本控制說明

11.2. 對API的回應內容分頁 (Pagination)

11.2.1 分頁策略介紹

11.2.2 為範例專案套用分頁機制

11.2.3 修改分頁預設資料筆數

11.3. 對API的回應內容排序 (Sorting)

11.3.1 升冪排序或降冪排序

11.3.2 為範例專案套用排序機制

CH12: 套用HATEOAS (p.20)

12.1. 簡介HATEOAS與HAL

12.1.1 簡介HATEOAS

12.1.2 簡介HAL

12.2. 為範例專案套用Spring HATEOAS

12.2.1 簡介Spring HATEOAS

12.2.2 整合HATEOAS與未分頁回應

12.2.3 整合HATEOAS與分頁回應

<<PART-3>> 測試Spring REST API

CH13: 存取與測試REST API (p.52)

13.1. 軟體的端對端測試、整合測試、單元測試

13.2. REST API的端對端測試

13.2.1. 使用JDK函式庫與RestTemplate存取資源端點

13.2.2. 使用RestTemplate的POST相關方法新增資源

13.2.3. 使用RestTemplate的GET相關方法查詢資源

13.2.4. 使用RestTemplate的PUT與PATCH相關方法更新資源

13.2.5. 使用RestTemplate的DELETE相關方法刪除資源

13.2.6. 使用RestTemplate的GET相關方法查詢分頁資源

13.3. REST API的單元測試

13.3.1. 測試Rest Controller的POJO功能性

13.3.2. 測試Rest Controller的Controller功能性

13.4. REST API的整合測試