REST 服務介紹
REST(Representational State Transfer)是 Roy Fielding 博士在 2000 年提出的一種新的軟件架構風格,它以資源(resource)為核心,使用 HTTP、 URI、XML 以及 HTML 等現有流行協議和標准來完成對資源的操作及顯示。 這些操作包括獲取、創建、修改和刪除資源,分別對應於 HTTP 協議的 GET、POST、PUT 和 DELETE 方法。REST 架構定義了以下設計准則:
- 網絡中的所有事物都被抽象為資源(resource)。
- 每個資源對應一個唯一的資源標識(resource identifier)。
- 通過通用的連接器接口(generic connector interface)對資源進行操作。
- 對資源的各種操作不會改變資源標識。
- 所有的操作都是無狀態的(stateless)。
REST 服務(RESTful Service)是一種基於 HTTP 和 REST 准則的輕量級 Web 服務。這類服務可以看作一系列資源(resource)的集合,服務的定義可以視為以下三個切面的組合 :
- 訪問 Web Service 的 URI,如:http://example.com/resources。
- Web Service 所支持的數據 MIME 類型,如:JSON, XML, YAML 等。
- Web Service 使用 HTTP 協議支持的操作,如 GET, POST, PUT, DELETE。
相比目前流行的 Web 服務實現方案 SOAP 和 XML-RPC, REST 服務更加簡潔,它可以完全通過 HTTP 協議實現,還可以利用緩存 Cache 來提高響應速度, 其性能,效率和易用性等方面均優於 SOAP 協議。 本文主要介紹如何使用 soapUI 來測試此類 Web 服務。
soapUI 介紹
由於 Web 服務是被程序調用的, 一般不會提供界面讓最終用戶或測試人員直接使用,在 soapUI 等工具出現之前,測試人員不得不自己編寫程序來測試它, 這就要求測試人員花費很大的精力了解底層的接口,調用關系和詳細的協議,導致他們不能把注意力集中到測試中。
soapUI 的出現極大的改變了這一局面。 作為一個開源的工具,soapUI 強大的功能、易用的界面,吸引了很多用戶。用戶可以在 soapUI 中通過簡單的操作完成復雜的測試,不需要了解底層的細節, 極大的減輕了工作量。soapUI 支持多樣的測試, 例如功能測試,性能測試,回歸測試等。到目前為止 soapUI 的下載量已經超過了 100 萬次,成為了事實的 Web 服務測試標准和領先的 Web 服務測試工具。 它不僅僅可以測試基於 SOAP 的 Web 服務,也可以測試 REST 風格的 Web 服務,后者也是本文介紹的重點。
soapUI 基於 Java 開發,支持多個平台,安裝非常簡單。讀者可以到 soapUI 的 官方網站下載一個安裝包 ( 本文使用的是 Window 版本 3.0.1),直接安裝即可。在該安裝包中,包括了一個 soapUI 所需要的 JRE1.6 版本。安裝完畢以后,讀者需要設置 JAVA_HOME 變量指向到相應的 JRE 目錄,同時修改 PATH 變量,將 JRE1.6 的 bin 目錄添加進去。
REST 服務案例
為了避免空洞的講解,同時為了更好的展示 soapUI 對 REST 服務的測試功能,本文假想了一個在線書店 (http://www.example.com) 的例子。該在線書店對外提供了一些 REST 服務讓第三方的應用程序調用。 為了讓讀者把注意力集中在使用 soapUI 進行測試上,我們對這些 REST 服務進行了必要的簡化,僅僅只包含下面 3 種功能:書籍列表,書籍詳情和添加評論。這 3 個 REST 服務覆蓋了層次狀的 REST 資源、基本的 HTTP 操作和多種展現形式。
服務名稱 | HTTP 操作 | 資源 URI | 資源展現 | 注釋 |
---|---|---|---|---|
書籍列表 | GET | http://www.example.com/books | application/json,text/xml | 該 REST 服務的目的是列出在線書店中的書籍列表,參見 代碼清單 1和 代碼清單 2 |
書籍詳情 | GET | http://www.example.com/books/<book id > | application/json | 該 REST 服務目的是給定一個書籍 ID,返回該書籍的詳細信息,參見 代碼清單 3,需要注意的是書籍詳細信息是一個帶有層次結構的 json 展示 |
添加評論 | POST | http://www.example.com/books/<book id >/comments | 無 , 系統僅僅返回 200 OK | 該 REST 服務的目的是對一個書籍添加評論,調用方需要 POST 類似 author=xxx&content=xxx 的數據到服務器端。 |
清單 1. 書籍列表 application/json
{"books": [ {"book": { "id": "1234", "name": "book1", "price": 29 }}, {"book": { "id": "5678", "name": "book2", "price": 18 }} ]}
清單 2. 書籍列表 text/xml
<bookes> <book> <id>1234</id> <name>book1</name> <price>29.0</price> </book> <book> <id>5678</id> <name>book2</name> <price>18</price> </book> </bookes>
清單 3. 書籍詳情 application/json
{ "id": "1234", "name": "book1", "description": "this is book 1", "author": "author1", "price": 29, "comments": [ {"comment": { "user": "user1", "content": "good book" }}, {"comment": { "user": "user2", "content": "not bad" }} ] }
在 soapUI 中建立測試用例
基本概念
在創建測試用例之前,我們先來看一看在 soapUI 中的基本概念,soapUI 把 REST 服務、資源及其操作組織為一個層次結構。如 圖 1所示,主要包括如下層次:
- 項目定義:位於最上層 (BookStoreTest),項目可以包含多個服務的定義。
- REST 服務定義:服務其實是對多個 REST 資源的一個分組,在我們的例子中只有一個服務 BookStoreServie
- REST 資源定義:具體描述該資源的名稱,URI, 參數等屬性
- REST 方法定義:針對每個資源的方法 (GET,POST,PUT,DELETE 等 ),圖 1 中的方法名就是 GetBookList
- REST 操作請求定義:基於每個方法,可以有一個或多個請求操作,如 GetBookListRequest,這些請求操作才是真正被 soapUI 所調用執行的。每個請求可以設置非常豐富的信息,例如 Accept 類型,請求的 Header 信息,運行了該請求以后,就能以各種方式查看運行結果。但是這里還不能加入斷言來驗證結果 - 必須在建立測試用例以后才能使用。
注: 讀者可以在 下載區的 bookstore-soapui-project.zip 找到完整的例子,下文中主要以該例子為基礎進行講解。讀者解壓 zip 文件以后,能得到一個 xml 文件,可以通過 soapUI 的 File->import project 把項目導入到自己的工作區中。
圖 1. soapUI 中的層次結構
對於測試用例來講,同樣是一個層次結構:
- TestSuite:類似於 Junit 中的測試套件,其中可以加入多個 TestCase
- TestCase:可以包含多個 TestStep
- TestStep:一個 TestCase 可以包含多個 TestStep,TestStep 有多種類型,它可以是上面提到一個 REST 操作請求,也可以是一個 Groovy 的腳本,還可以試一個設置屬性的操作。 TestStep 甚至支持分支跳轉操作:根據特定的條件,從一個 step 可以跳轉到其他 step, 而不必順序執行。
soapUI 實際上是一個平台,它支持強大的編程能力,開發或者測試人員可以利用 groovy 腳本來訪問 soapUI 中的對象,在運行時修改 REST request/response, 這就提供了極大的靈活性。
圖 2. TestCase 定義
創建測試用例
有了上面的基本概念以后,在 soapUI 中創建測試用例就比較簡單了, 用戶幾乎可以根據自己的直覺來一步一步的完成一個測試。圖 3展示的就是一個建立書籍列表 REST 服務的步驟:
1. 新建一個名為 BookStoreTest 的項目
2. 在項目上點擊右鍵,選擇"New Rest Service",在對話框中輸入 Service Name(BookStoreService) 和 Endpoint(http://localhost:9080)
3. 在"BookstoreService"上點擊右鍵,選擇“New Resource”, 在對話框中輸入 Resource Name(BookList) 和 Resource Path (/books),點擊 OK
4. 在彈出的對話框中輸入 Method Name: GetBookList,HTTP Method 選擇默認的 GET, 點擊 OK
圖 3. 創建一個 REST 服務
有了 REST 服務,就可以建立 TestCase,主要有兩種方式:
- 自動生成,步驟如下:
(1). 右鍵點擊一個 REST 服務,例如本例中的"BookStoreService", 選擇"Generate TestSuite"
(2). 在彈出的對話框中,保持默認設置, 選擇"OK"
(3). 輸入名稱 , 例如"BookStoreService TestSuite", 選擇"OK"即可。 - 手工創建,步驟如下:
(1). 在項目"BooksStoreTest"上點擊右鍵,選擇“New TestSuite”, 在對話框中輸入"BookStoreService TestSuite"
(2). 在 BookStoreService TestSuite 上點擊右鍵, 選擇"New TestCase", 在對話框中輸入"BookList TestCase"
(3). 然后在左邊的導航欄中展開 BookList TestCase, 在“Test Steps”上點右鍵,選擇 Add Step->Rest Test Request
(4). 在彈出的對話框中選擇 GetBookListRequest_XML
一個完成的 TestCase 如 圖 4所示,用戶可以在其中加入 Assertion 對運行結果進行驗證,這也是自動化測試所必須的。
圖 4. REST TestCase
使用變量
soapUI 支持使用自定義變量(Property)在 Project 中存儲和共享數據。Property 是一個命名的字符串可以被 Groovy Script,Property Transfer 或者 Property-Expansion 引用, 目前所有的變量均被處理為字符串。soapUI 允許在項目的各個層次中定義變量,常用的層次包括:Project,TestSuite,TestCase,Global 等。
1. 使用 Property 編輯器定義變量。
用戶可以使用 soapUI 自帶的 Property Editor 定義各個層次的變量。以 Project 變量為例,點擊 BookStoreTest,在 Properties 面板中添加自定義變量,如下圖所示:
圖 5. 使用 Property 編輯器定義項目變量
2. 使用命令行指定變量。
修改 soapUI.bat 文件中的 Java 參數如下:
清單 x. 使用命令行指定變量
set JAVA_OPTS=%JAVA_OPTS% -Xms128m -Xmx256m -Dsoapui.properties=properties.txt
其中,properties.txt 為指定的全局變量文件名字,可以通過添加如下代碼到全局變量文件來設置 project/testsuite/testcase 等層次的變量:
清單 x. 使用命令行指定變量
soapui.properties.<shortened-name-of-object>=pathtopropertiesfile
<shortened-name-of-object> 為相應對象的名字。
3. 使用變量
soapUI 使用如下語法引用項目中定義的變量:
清單 x. 使用命令行指定變量
${[scope]propertyName[#xpath-expression]}
其中,scope 可以為 #Project#,#TestSuite#,#TestCase# ,#Global#,#System#,#MockService#,#Env#,[TestStep name]#。
驗證結果
測試用例建好之后,需要向測試用例中添加 Assertions 以便驗證結果的正確性。soapUI 支持 Response SLA, Script Assertion, Contains, XQuery Match, Schema Compliance, XPath Match 以及 Not Contains 等多種斷言來對 response 進行判斷來保證對 Web 服務高質量的測試。本文以 XPath Match 和 Script Assertion 為例來對在線書店服務返回的 XML 和 JSON 格式的 response 進行判斷。
1. 使用 XPath Match 測試請求 GetBookListRequest_XML 返回的結果中,id 為 1234 的 book 的 price 為 29.0, response 參見 代碼清單 2。
點擊 TestCase 的添加 Assertions 按鈕,如 圖 4所示。 在彈出的 Select Assertion 窗口中選擇 XPath Match 斷言,點擊 OK。配置 XPath 如下圖所示:
圖 6. 使用 XPATH 測試 XML 格式的書籍列表
在 XPath Expression 面板中書寫用於匹配 Response 的 XPath 表達式,Expected Result 面板中填寫期望的值。soapUI 將使用 XPath Expression 面板中填寫的 XPath 表達式與 Service 調用結果匹配, 將匹配結果與期望值比較,如果相同則測試通過,否則失敗。
2. 使用 Script Assertion 測試請求 GetBookListRequest_JSON 返回的結果,response 參見 代碼清單 1。
Assertion 添加過程與 XPath Match 類似,在 Select Assertion 窗口中選擇 Script Assertion,並在之后彈出的 Script Assertion 窗口中書寫如下代碼:
清單 5. 使用 Script Assertion 測試 JSON 格式的書籍列表
//assert the response header assert messageExchange.responseHeaders["Content-Type"]=="application/json;charset=UTF-8"; assert messageExchange.responseHeaders["Cache-Control"] == "no-cache"; //assert the repsonse body def booksRoot = net.sf.json.JSONSerializer.toJSON(messageExchange.responseContent); def books = booksRoot.get("books"); //assert book detail assert books[0].get("book").get("id") == "1234"; assert books[0].get("book").get("name") == "book1"; assert books[0].get("book").get("price") == 29;
3. 使用 Property 測試請求 GetBookRequest_JSON 返回的結果, response 參見 代碼清單 3。
在 Script Assertion 窗口中寫入如下代碼:
清單 6. 使用 Property 測試 JSON 格式的書籍詳情
//get property def expectedID = context.expand('${#Project#book.id}'); def expectedName = context.expand('${#Project#book.name}'); //assert the response header assert messageExchange.responseHeaders["Cache-Control"] == "no-cache"; //assert the response body def bookRoot = net.sf.json.JSONSerializer.toJSON(messageExchange.responseContent); assert bookRoot.get("id") == expectedID; assert bookRoot.get("name") == expectedName; assert bookRoot.get("price") == 29.0;
上述使用 Groovy Script 對 Service 調用結果進行驗證,可用的 soapUI 對象包括 :messageExchange, context 以及 log。
- messageExchange: 當前交互 request/response 的 MessageExchange,可以用來直接訪問 message content, HTTP Headers,Attachment 等對象。
- context: 運行當前 TestCase 的 TestRunContext 對象,具體使用方式請參見 soapUI API 文檔。
- log: 一個標准的 Log4j Logger 對象,可以用來輸出日志。
依照上述步驟定義好 TestCase 並添加適當的斷言之后,就可以對在線書店 REST 服務進行測試。雙擊 BookStoreSerive_TestSuite, 點擊 Run 按鈕來運行所有的 TestCase,結果如下圖所示:
圖 7. 運行測試用例
性能測試
性能測試在 soapUI 中稱為 Load Test, 針對一個 soapUI 的 TestCase, 可以建立一個或多個 LoadTest, 這些 LoadTest 會自動的 把 TestCase 中的所有步驟都添加到其中, 在運行的時候,soapUI 會自動的使用多個線程來運行這些 TestStep,同時也會監控 它們的運行時間, 例如最短時間,最長時間,平均時間等等。這樣用戶能夠很直觀的看到 REST 服務的響應時間,從而對性能進行調優。
建立 LoadTest 非常簡單,只需要在“Load Tests”上點擊右鍵, 選擇"New LoadTest", 然后輸入名稱即可,下圖是一個針對 GetBookList 的 性能測試, 可以看到有兩個 TestStep : "GetBookList_xml" 和"GetBookList_json" , 100 個線程並發執行, 時間限制是 60 秒。 最后的結果是,最短時間 4 毫秒,最長時間 1204 毫秒,平均時間 20.54 毫秒。
圖 8. 性能測試
性能測試還支持斷言,用戶可以對一個 TestStep 或 TestCase 設置運行時間要求,例如平均時間大於 2 秒就認為失敗,點擊 圖 8 中中的 “LoadTest Assertions”就可以設置。 當然根據需要,用戶也可以編寫腳本來做一些准備工作,或者清除工作。 參見 圖 8 中的"Setup Script"和“TearDown Script”。
與 BuildForge 集成
測試可以有效的保證代碼的質量,但是僅僅手工的、在本機上運行的 REST 服務測試時遠遠不夠的。實際上把測試作為軟件構建的一部分,加入到持續集成中去是一個常見的敏捷開發實踐,通過頻繁的,自動化的測試, 可以更有效的發現缺陷,保證代碼質量。
IBM Rational BuildForge 是一個管理軟件構建和發布的平台 , 它提供了一個框架來自動化整個構建流程,不僅僅自動化單獨的任務,可以集成多種用戶現有的腳本和工具, 最大限度的保護用戶投資。作為一個框架,BuildForge 幾乎可以調用操作系統上的任何腳本。本文重點不在介紹 BuildForge, 假定讀者對 BuildForge 已經比較熟悉, 不熟悉的讀者可以查看參考資料中的相關文章。
對於 soapUI 來說,最簡單的一種集成方式就是提供命令行腳本讓 BuildForge 調用。在上文中 我們已經展示了通過 soapUI 的 GUI 運行 TestCase 的功能,那么 soapUI 可不可以通過命令行完成類似的功能呢? 答案是肯定的。
soapUI 提供了一個命令行工具 testrunner.bat 來運行一個項目中的 TestSuite, 可以在 <soap_ui_home>/bin 下找到它, 它的使用非常簡單,只需要設置下面的幾個常用參數即可:
- -s 指定要運行的 TestSuite
- -f 指定運行結果的輸出目錄
- -j 生成 junit 風格的 report
- -r 運行完成以后打印一個簡單的 summary
下面這行命令就是運行 bookstore.xml 這個 soapUI 項目中的 BookstoreTestSuite, 把結果輸出到 c:\temp\reports 中。
testrunner.bat -sBookstoreTestSuite -r -j C:\developerWorks\soapui\bookstore.xml -f c:\temp\reports
有了 testrunner,把 soapUI 的測試集成到 BuildForge 中就很簡單了,只需要在 BuildForge 的項目中添加一個步驟,參見下圖:
圖 9. BuildForge 中使用 testrunner
總結
從上文可以看出,soapUI 是一款強大的 Web 服務測試工具 , 它提供了便利的 GUI 界面幫助用戶對 REST 服務進行快速的測試,使用簡單,學習成本很低。 它也提供了可編程能力例如變量,Groovy 腳本等, 以便讓用戶應對一些復雜的情況。 總而言之,有效的實際項目中使用 soapUI,可以極大的減輕工作量,提供工作效率。當然作為開源的工具, soapUI 仍然在不斷的發展完善過程中,本文所能介紹的也只是它的部分功能, 感興趣的讀者可以到 soapUI 網站上獲取更多資料。