05.15 maven 生成自動測試 RESTful 服務項目腳手架

額外的話: 我會非常感激如果您在讀本文的時候 Follow 文中的操作步驟在你的環境中實踐本文所講內容, 我保證這個過程不會非常複雜, 即便加上你研究代碼的時間也不需要超過 15 分鐘

和傳統後端頁面生成技術相較, RESTful 數據服務專注與數據邏輯, 而將數據呈現完全交給前端應用. 這樣做可以讓後端開發更加單純, 而且更容易測試. 本文將講述如何使用 maven 生成一個支持端到端自動測試的 RESTful 服務的項目腳手架.

1. 準備環境

如果你打算跟隨本文在你的開發機上試驗, 需要一下環境:

  1. Java SDK 1.7+

  2. maven 3.5+

如果有 IDE 就更好. 作者推薦使用 IntelliJ IDEA, 可以使用社區版, 完全免費.

2. 定義項目包和應用名稱

項目包 (package) 和應用名稱 (artifact) 是你的項目在 Java 依賴體系中的座標, 即使你的項目無需被其他項目引用, 也應該給出簡單明確的包和應用名字, 以便於溝通交流.

這裡為我們即將生成的應用給出下面的包名和應用名:

  • 包名: <code>demo.restful/<code>

  • 應用名: <code>SimpleService/<code>

3. 生成項目腳手架

我們使用 actframework 的 archetype-simple-restful-service 來生成項目腳手架. 在命令行下

mvn archetype:generate -B \\
-DgroupId=demo.restful \\ -DartifactId=simple-service \\ -DarchetypeGroupId=org.actframework \\ -DarchetypeArtifactId=archetype-simple-restful-service \\ -DarchetypeVersion=1.8.8.1

運行以上命令大致可以看到下面的 log 信息:

[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:3.0.1:generate (default-cli) > generate-sources @ standalone-pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:3.0.1:generate (default-cli) < generate-sources @ standalone-pom <<<
[INFO]
[INFO]
[INFO] --- maven-archetype-plugin:3.0.1:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Batch mode
[INFO] Archetype repository not defined. Using the one from [org.actframework:archetype-simple-restful-service:1.8.7.3] found in catalog remote
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: archetype-simple-restful-service:1.8.8.0[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: demo.restful
[INFO] Parameter: artifactId, Value: simple-service
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: demo.restful
[INFO] Parameter: packageInPathFormat, Value: demo/restful
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: demo.restful
[INFO] Parameter: groupId, Value: demo.restful
[INFO] Parameter: artifactId, Value: simple-service
[INFO] Executing META-INF/archetype-post-generate.groovy post-generation/>[INFO] Project created from Archetype in dir: /tmp/1/simple-service
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

[INFO] Total time: 10.984 s
[INFO] Finished at: 2018-05-13T22:15:58+10:00[INFO] Final Memory: 25M/305M
[INFO] ------------------------------------------------------------------------

4. 項目結構與內容

使用 <code>tree/<code> 命令來查看項目結構:

simple-service/
├── pom.xml
├── run_dev
├── run_dev.bat
├── run_e2e
├── run_e2e.bat
├── run_prod
└── src
├── main
│ ├── java
│ │ └── demo
│ │ └── restful
│ │ ├── AppEntry.java
│ │ └── Service.java
│ └── resources
│ ├── conf
│ │ ├── app.properties
│ │ ├── prod
│ │ │ └── app.properties
│ │ └── uat
│ │ └── app.properties
│ ├── demo
│ │ └── restful
│ ├── e2e
│ │ └── scenarios.yml
│ ├── logback.xml
│ └── rythm
│ └── demo
│ └── restful
└── test
└── java
└── demo

4.1 項目啟動入口: <code>AppEntry/<code>

我們看到生成的項目中有一個 <code>AppEntry.java/<code> 文件, 打開該文件, 其內容為:

public class AppEntry { public static void main(String[] args) throws Exception { 

Act.start();
}
}

這是一個很簡單的 Java 類, 其作用是啟動 ActFramework.

4.2 服務類: <code>Service/<code>

打開本項目中的另一個 Java 文件 <code>Service.java/<code>:

public class Service { @GetAction("hello") public String hello(@DefaultValue("World") String who) { return "Hello " + who;
} @GetAction("date") public DateTime date() { return DateTime.parse("2016-03-09");
}
}

代碼很簡單, 提供了兩個 RESTful 數據服務端口:

4.2.1 GET /hello

當訪問這個服務端口的時候, 應用返回 "Hello xxx" 形式的內容, 其中 xxx 是通過請求參數 <code>who/<code> 傳遞的, 如果請求沒有提供這個參數, 則使用默認值 <code>World/<code>, 即, 返回 "Hello World" 字串.

4.2.2 GET /date

這個服務端口不接受參數, 直接返回一個日期數據

4.3 運行腳本

maven 為項目生成了幾個可執行腳本文件:

  • <code>run_dev/<code> - 以開發模式啟動項目

  • <code>run_dev.bat/<code> - <code>run_dev/<code> 的 windows 版本

  • <code>run_e2e/<code> - 運行端到端測試

  • <code>run_e2e.bat/<code> - <code>run_e2e/<code> 的 windows 版本

  • <code>run_prod/<code> - 以產品模式啟動項目

我們沒有提供 <code>run_prod/<code> 腳本的 windows 版本, 主要原因是 windows 沒有默認安裝支持解包 tar.gz 文件的命令行應用. 如果需要在 windows 下運行產品模式可以這樣做:

  1. 運行 <code>mvn clean package/<code>

  2. 到項目的 <code>target/dist/<code> 目錄下, 找到 <code>tar.gz/<code> 文件, 使用第三方工具, 比如 7zip 等解包, 然後運行 <code>run.bat/<code> 文件

5. 啟動應用

下面我們使用 <code>run_dev/<code> 腳本, 或者直接使用 <code>mvn compile act:run/<code> 啟動應用:

 __ ___ _ _ __ _ _ ___ _ _
(_ | |\\/| |_) | |_ __ (_ |_ |_) \\ / | / |_
__) _|_ | | | |_ |_ __) |_ | \\ \\/ _|_ \\_ |_
powered by ActFramework r1.8.8-RC4-aa2d4
version: v1.0-SNAPSHOT-180513_2237
scan pkg:

base dir: /tmp/1/simple-service
pid: 26126
profile: dev
mode: DEV
zen: Flat is better than nested.
2018-05-13 22:37:47,875 INFO a.Act@[main] - loading application(s) ...
2018-05-13 22:37:47,885 INFO a.a.App@[main] - App starting ....
2018-05-13 22:37:48,104 WARN a.h.b.ResourceGetter@[main] - URL base not exists: META-INF/resources/webjars
2018-05-13 22:37:48,119 WARN a.a.DbServiceManager@[main] - DB service not initialized: No DB plugin found
2018-05-13 22:37:48,741 WARN a.m.MailerConfig@[main] - smtp host configuration not found, will use mock smtp to send email2018-05-13 22:37:49,033 INFO a.a.App@[main] - App[simple-service] loaded in 1148ms2018-05-13 22:37:49,037 INFO a.a.ApiManager@[jobs-thread-3] - start compiling API book2018-05-13 22:37:49,055 INFO o.xnio@[main] - XNIO version 3.3.8.Final2018-05-13 22:37:49,143 INFO o.x.nio@[main] - XNIO NIO Implementation Version 3.3.8.Final2018-05-13 22:37:49,244 INFO a.Act@[main] - network client hooked on port: 54602018-05-13 22:37:49,245 INFO a.Act@[main] - CLI server started on port: 54612018-05-13 22:37:49,246 INFO a.Act@[main] - app is ready at: http://192.168.1.2:54602018-05-13 22:37:49,246 INFO a.Act@[main] - it takes 2149ms to start the app

項目啟動之後可以從瀏覽器來訪問. 首先來看 <code>GET /hello/<code> 服務端口的情況:

maven 生成自動測試 RESTful 服務項目腳手架

再請求中加入一個 <code>who/<code> 參數再試試:

maven 生成自動測試 RESTful 服務項目腳手架

最後看看 <code>GET /date/<code> 服務端口:

maven 生成自動測試 RESTful 服務項目腳手架

6. 測試

我們已經使用瀏覽器來驗證項目可以正常運行, 也能得到期望的結果. 對於簡單的應用來講, 可以使用這種方式進行測試, 但隨著項目的開發, 更多的服務端口會加入進來, 每次都這樣來運行測試, 對開發測試人員來說是很大的負擔. 下面我們先運行一下生成的 <code>run_e2e/<code> 腳本, 或者也可以直接使用 maven 命令 <code>mvn compile act:e2e/<code>:

Listening for transport dt_socket at address: 5005 

__ ___ _ _ __ _ _ ___ _ _
(_ | |\\/| |_) | |_ __ (_ |_ |_) \\ / | / |_
__) _|_ | | | |_ |_ __) |_ | \\ \\/ _|_ \\_ |_
powered by ActFramework r1.8.8-RC4-aa2d4
version: v1.0-SNAPSHOT-180513_2248
scan pkg:
base dir: /tmp/1/simple-service
pid: 27429
profile: e2e
mode: DEV
zen: Simple things should be simple, complex things should be possible.
2018-05-13 22:48:52,651 INFO a.Act@[main] - loading application(s) ...
2018-05-13 22:48:52,661 INFO a.a.App@[main] - App starting ....
2018-05-13 22:48:52,882 WARN a.h.b.ResourceGetter@[main] - URL base not exists: META-INF/resources/webjars
2018-05-13 22:48:52,895 WARN a.a.DbServiceManager@[main] - DB service not initialized: No DB plugin found
2018-05-13 22:48:53,580 WARN a.m.MailerConfig@[main] - smtp host configuration not found, will use mock smtp to send email2018-05-13 22:48:53,910 INFO a.a.App@[main] - App[simple-service] loaded in 1249ms2018-05-13 22:48:53,914 INFO a.a.ApiManager@[jobs-thread-2] - start compiling API book2018-05-13 22:48:54,017 INFO o.xnio@[main] - XNIO version 3.3.8.Final2018-05-13 22:48:54,032 INFO o.x.nio@[main] - XNIO NIO Implementation Version 3.3.8.Final2018-05-13 22:48:54,130 INFO a.Act@[main] - network client hooked on port: 54602018-05-13 22:48:54,131 INFO a.Act@[main] - CLI server started on port: 54612018-05-13 22:48:54,132 INFO a.Act@[main] - app is ready at: http://192.168.1.2:54602018-05-13 22:48:54,133 INFO a.Act@[main] - it takes 2332ms to start the appStart running E2E test scenarios
================================================================================
HELLO SERVICE
a service says hello--------------------------------------------------------------------------------[PASS] send request to hello service without parameter
[PASS] send request to hello servcie with parameter specified
[PASS] send request to hello servcie with parameter specified and require JSON response--------------------------------------------------------------------------------It takes 0s to run this scenario.
================================================================================DATE SERVICE
A service returns an important date in the history--------------------------------------------------------------------------------[PASS] send request to the service
[PASS] send request to the service and request response be JSON format--------------------------------------------------------------------------------It takes 0s to run this scenario.2018-05-13 22:48:55,224 INFO a.a.App@[jobs-thread-4] - App shutting down ....
[INFO] ------------------------------------------------------------------------[INFO] BUILD SUCCESS[INFO] ------------------------------------------------------------------------[INFO] Total time: 5.874 s
[INFO] Finished at: 2018-05-13T22:48:55+10:00[INFO] Final Memory: 26M/389M
[INFO] ------------------------------------------------------------------------

我們看到項目啟動之後運行了 E2E 測試場景, 包括對 <code>GET /hello/<code> 和 <code>GET /date/<code> 的 5 個測試用例, 並且都通過了測試. 下面打開 <code>src/main/resources/e2e/scenarios.yml/<code> 文件來看看測試場景和用例是如何定義的:

Scenario(Hello Service): description: a service says hello interactions: - description: send request to hello service without parameter request: method: GET url: /hello response: text: Hello World # response text must be "Hello World" - description: send request to hello servcie with parameter specified request: method: GET url: /hello?who=ActFramework response:
# this time we demonstrate how to verify text with a list of verifiers text: - eq: Hello ActFramework # value must be equal to "Hello ActFramework" - contains: ActFramework # value must contains "ActFramework" - starts: Hello # value must starts with "Hello" - ends: Framework # value must ends with "Framework" - description: send request to hello servcie with parameter specified and require JSON response request: json: true # specify accept type is application/json method: GET url: /hello?who=Java response: json: # treat result as a JSON object
# act returns json result in `{"result": ...}` style result: Hello Java # result property of the JSON object must be "Hello World"# Test Service#date() endpoint, which is available at `GET /date` endpointScenario(Date Service): description: A service returns an important date in the history interactions: - description: send request to the service request: method: GET url: /date response: text: - after: 1997-05-11 # the returned date should be after date 1997-05-11 - before: 13/May/2018 # the returned date should be before date 13/May/2018 - description: send request to the service and request response be JSON format request: json: true method: GET url: /date response: json: # treat result as a JSON object
# act returns json result in `{"result": ...}` style result: # thus we will use `result` to fetch the date - after: 1997-05-11 # the returned date should be after date 1997-05-11 - before: 13/May/2018 # the returned date should be before date 13/May/2018

這個文件還是很容易理解, 其內容基本是按照下面的方式組織的:

  1. 首先第一層定義測試場景 Scenario, 每個 Scenario 都會給出一個名字放進圓括弧中. 我們在文件中定義了兩個 Scenario

  • Hello Service - 測試 <code>GET /hello/<code> 端口

  • Date Service - 測試 <code>GET /date/<code> 端口

  1. 場景 (Scenario) 下面定義一個或者多個交互 Interactions

  2. 對每個交互定義請求 Request 和 響應 Response

  3. 請求 Request 的內容包括方法和 URL, 也可以指定是否要求返回 JSON 格式的請求

  4. 響應 Response 則定義期望返回內容的驗證

針對每個服務端口可以寫一個或多個場景, 也可以在一個場景中順序測試多個服務端口. 完全有應用自己來定義.

7. 總結

本文介紹瞭如何使用 maven archetype 來生成一個可測試 RESTful 數據服務項目的腳手架, 以及如何通過定義<code>e2e/scenarios.yml/<code> 文件來提供自動測試的功能. 希望能夠對大家帶來幫助.


分享到:


相關文章: