程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 跨越邊界: 在集成框架中進行測試,第2部分

跨越邊界: 在集成框架中進行測試,第2部分

編輯:關於JAVA

超越單元測試的擴展

在這由兩部分組成的迷你系列的 第 1 部分 中,了解了如何用動態語言促進單元測試。本文將展示集 成環境在功能測試和集成測試中的優勢。單元測試包括對小的代碼片斷(例如方法)的測試,而且經常要 把它們與周圍的元素隔離開。功能測試和集成測試所測試的應用程序部分越來越多。功能測試用於測試單 一特性(通常涉及一個接口)、執行任務的業務代碼,以及與中間件服務交互的代碼(例如數據庫)。集 成測試用於測試應用程序的多個不同特性。(功能測試在不太嚴謹的情況下通常也被稱為集成測試。)

Java 開發人員在解決單元測試問題上已經獲得了令人注目的成果,但在集成測試上則沒有帶來太多令 人興奮的消息。多數 Java 測試框架(如 JUnit 或 TestNG)主要側重於單元測試。Java 編程中缺乏集 成測試框架的一個原因是缺乏集中的架構或開發哲學。在後面的小節中,我將繼續使用 Ruby on Rails 示例,這次的重點放在功能測試和新的 Rails 集成測試框架上。您將看到,在使用集成測試框架時,進 行測試要容易得多。

運行測試

如果還沒有閱讀 第 1 部分,那麼請先閱讀它。然後,如果想跟隨這篇文章一起編寫代碼,那麼請確 保您已經獲得一個可工作的 Rails 應用程序。在第 1 部分中,實現了一個簡單的單元測試和幾個 fixture。如果您跟隨第 1 部分一起編寫了代碼,但是記不清是否使應用程序處於工作狀態,那麼您可以 利用測試用例,先切換到項目目錄,然後運行 rake 即可。清單 1 顯示了我的結果:

清單 1. 用 rake 運行所有測試

> bruce-tates-computer:~/rails/trails batate$ rake
(in /Users/batate/rails/trails)
/usr/local/ror/bin/ruby -Ilib:test
  "/usr/local/ror/lib/ruby/gems/1.8/gems/rake-0.7.0/lib/rake/rake_test_loader.rb"
  "test/functional/trails_controller_test.rb"
Loaded suite /usr/local/ror/lib/ruby/gems/1.8/gems/rake- 0.7.0/lib/rake/rake_test_loader
Started
EEEEEEEEEEEEEEEE
Finished in 0.070797 seconds.
  1) Error:
test_create(TrailsControllerTest):
Errno::ENOENT: No such file or directory - /tmp/mysql.sock
   /usr/local/ror/lib/ruby/gems/1.8/gems/activerecord-1.14.0/
    lib/active_record/vendor/mysql.rb:104:in 'initialize'
   /usr/local/ror/lib/ruby/gems/1.8/gems/activerecord-1.14.0/
    lib/active_record/vendor/mysql.rb:104:in 'real_connect'
   /usr/local/ror/lib/ruby/gems/1.8/gems/activerecord-1.14.0/
    lib/ active_record/connection_adapters/mysql_adapter.rb:331:in 'connect'

...results deleted...
8 tests, 0 assertions, 0 failures, 16 errors
/usr/local/ror/bin/ruby -Ilib:test "/usr/local/ror/lib/ruby/gems/1.8/gems/rake-0.7.0/
  lib/rake/rake_test_loader.rb" 
rake aborted!
Test failures
(See full trace by running task with --trace)

可以看到有一些問題存在:rake 生成了 16 個錯誤。跟蹤顯示,Rails 無法建立連接。我忘記啟動數 據庫引擎了。我將啟動數據庫引擎,然後再次運行 rake。這次我得到了清單 2 所示的結果:

清單 2. 在 rake 內通過測試

rake
(in /Users/batate/rails/trails)
/usr/local/ror/bin/ruby -Ilib:test
  "/usr/local/ror/lib/ruby/gems/1.8/gems/rake-0.7.0/lib/rake/rake_test_loader.rb"
  "test/unit/trail_test.rb"
Loaded suite /usr/local/ror/lib/ruby/gems/1.8/gems/rake- 0.7.0/lib/rake/rake_test_loader
Started
...
Finished in 0.09541 seconds.
3 tests, 5 assertions, 0 failures, 0 errors
/usr/local/ror/bin/ruby -Ilib:test
  "/usr/local/ror/lib/ruby/gems/1.8/gems/rake-0.7.0/lib/rake/rake_test_loader.rb"
  "test/functional/trails_controller_test.rb"
Loaded suite /usr/local/ror/lib/ruby/gems/1.8/gems/rake- 0.7.0/lib/rake/rake_test_loader
Started
........
Finished in 0.169756 seconds.
8 tests, 28 assertions, 0 failures, 0 errors

這樣就好多了。測試正常運行,而我們准備構建更多測試用例。如果仔細查看清單 2 就會發現,rake 生成了兩組結果。第一組(第 1 部分的單元測試)看起來應當熟悉。下一組是從框架中自動生成的功能 測試。

控制器和視圖快速入門

在查看測試代碼之前,需要對 Rails 的用戶界面層有更好的理解。在第 1 部分中,用 script/generate scaffold Trail Trails 生成框架代碼時,Rails 根據數據庫的內容為應用程序創建了 一個控制器和系列視圖。控制器的代碼位於 app/controller/trails_controller.rb,視圖則全部位於 app/views/trails 下的不同目錄中。這個應用程序包含:

默認 Web 頁面實現,顯示路線(trail)列表(叫做 list)

路線的細節信息的顯示頁面

路線的通用表單

創建或編輯路線的頁面

要了解這些是如何組合在一起的,請參見 trails_controller.rb 中的 list 方法,如清單 3 所示:

清單 3. app/controllers/trails_controller.rb 中的部分代碼清單

def list
  @trail_pages, @trails = paginate :trails, :per_page => 10
end

傳入的超文傳輸協議(HTTP)請求進入控制器。(HTTP 是支持浏覽器、Rails 和所有基於浏覽器的應 用程序的底層協議)。在這篇文章後面,您將看到功能測試如何通過使用 HTTP 命令來調用功能測試用例 。清單 3 的代碼設置了 Rails 顯示線路的分頁列表時需要的實例變量。視圖需要一個分頁器對象,即 Rails 分配給 @trail_pages 的分頁器對象,還需要 @trails 中的路線列表。默認情況下,Rails 使用 與控制器方法相同的名稱呈現視圖。要查看視圖,請參閱 app/views/trails/list.rhtml 中的表格定義 ,如清單 4 所示:

清單 4. list.rhtml 的部分代碼清單

<table>
  <tr>
  <% for column in Trail.content_columns %>
    <th><%= column.human_name %></th>
  <% end %>
  </tr>
<% for trail in @trails %>
  <tr>
  <% for column in Trail.content_columns %>
    <td><%=h trail.send(column.name) %></td>
  <% end %>
    <td><%= link_to 'Show', :action => 'show', :id => trail %></td>
    <td><%= link_to 'Edit', :action => 'edit', :id => trail %></td>
    <td><%= link_to 'Destroy', { :action => 'destroy', :id => trail },
     :confirm => 'Are you sure?', :post => true %></td>
  </tr>
<% end %>
</table>

Rails 中的視圖策略是:創建一個簡單字符串,然後做一些替換。這個策略叫做建模,它構成了大多 數現代 Web 框架的基礎,包括 Java 框架(例如 Tapestry、JavaServer Faces(JSF)、JavaServer Pages (JSP) 和 WebWork)。在這個示例中,Rails 做了以下工作:

執行 <% 和 %> 之間的代碼段(被稱為語句),並用代碼段的執行輸出替代這一部分。語句可 能不存在。

執行 <%= 和 %> 之間的代碼段(被稱為表達式),並用代碼段返回的值替代這一部分。

處理布局、偏好、幫助程序以及其他類型的代碼片斷時。這些特性允許使用不同的復合部件構建復雜 的 Web 頁面。在這裡,我就不對細節做過多介紹了。

在有了模板策略之後,現在再來看一下 清單 4。您可以看到訪問活動記錄 Trail 模型並用 <% for trail in @trails %> 命令在 @trails 中的每條路線上循環的 list.rhtml 視圖。(您已經填充 了控制器中的 @trails 實例變量)。對於每條路線,該視圖都將得到 Trail.content_columns,它是 trails_development 數據庫中 trails 表的列的列表。然後,該視圖通過在列表中的每個列上進行循環 ,提供數據庫中每一列的值。trail.send(column_name) 命令把 name、difficulty 和 description 方 法發送給 trail。

現在是在屏幕上查看結果的時候了。如果回憶一下,應當記得您已經在第 1 部分的示例中鍵入了一些 fixture 形式的測試數據。要把它們加載到開發環境(fixture 默認裝入測試環境)中,則只需鍵入 rake load_fixtures 即可。啟動 Rails 服務器(在 Unix 上用 script/server,在 Windows 上用 ruby script/server),把浏覽器指向 localhost:3000/trails/list 就可以看到結果。在這個 URL 中, trails 是控制器的名稱,list 是動作的名稱,由 list 控制器方法實現。圖 1 顯示了結果:

清單 1. 列出路線

正如所期望的那樣,可以看到一個包含每條路線的名稱、說明和難度的表。接下來,我將介紹 Rails 的功能測試框架如何只通過一條 HTTP put 命令訪問 Web 頁面。

分解功能測試

回憶一下就可以知道,Rails 單元測試只處理模型。Rails 中的功能測試調用 Web 頁面,然後檢查結 果,從上到下地測試某一特性(包括模型、視圖和管制器)。這種級別的集成測試很重要,因為可以確保 系統的主要元素之間的交互與您對所提供的每個特性的預期一樣。

Rails 的每個功能測試用例都要進行 HTTP put 和 get。它們調用控制器的動作;控制器訪問模型和 視圖,並呈現 Web 頁面和結果。要獲得詳細的工作示例,請參見 Rails 在框架中生成的測試用例:

清單 5. 來自 test/functional/trails_controller_test.rb 的 test_list

def test_list
  get :list
  assert_response :success
  assert_template 'list'
  assert_not_nil assigns(:trails)
end

清單 5 中的測試用例利用 get :list 命令執行了一個簡單的 HTTP get。然後,測試用例運行了三個 斷言:

assert_response :success:HTTP 命令成功完成。

assert_template 'list':控制器動作呈現 list 模板。

assert_not_nil assigns(:trails):控制器把 @trails 實例變量分配給一些非 null 的值。

使用單元測試框架,如果斷言為 ture,沒有錯誤出現,那麼測試用例就通過;否則,測試用例失敗。

test_list 測試用例可以聲明 :success 響應,但是它應當聲明 :redirect (代表 HTTP 重定向)、 :missing (代表 not_found),或代表單個 HTTP 返回代碼的整數。請參閱 參考資料,獲得 HTTP 返回 代碼的詳盡列表。現在請看 test_create,它使用了一個 HTTP put。請將 test_create 更改成如清單 6 所示:

清單 6. 測試表單

def test_create
  num_trails = Trail.count
  post :create, :trail => {:name => "Hermosa Creek", :description =>
    "Lots of altitude, all down", :difficulty => "Medium"}
  assert_response :redirect
  assert_redirected_to :action => 'list'
  assert_equal num_trails + 1, Trail.count
end

trails_controller_test.rb 中自動生成的這個測試用例的版本包括 post :create, :trail => {},它調用 create 方法,空哈希表表示新路線。這個代碼應當創建一條新路線,該路線有一個所有屬性 都為 null 的 Trail 對象。清單 6 修改了代碼,以傳遞代表路線屬性的哈希映射表。這個哈希映射表接 口對於在測試框架中指定對象而言非常有用。然後,測試用例用 Trail 模型確保創建了新路線。

清單 5 和清單 6 中的測試用例不像第 1 部分中的單元測試那樣處理每個細節。但是它們可以保證調 用了業務邏輯,保證控制器邏輯沒有檢測到任何錯誤,並保證得到了正確的 HTTP 響應。

Rails 還提供了另一種測試用例:集成測試。

集成測試

功能測試用於測試單一特性,而集成測試可能觸及許多不同的頁面。例如,購物車單元測試可以測試 出您可能通過模型 API 將一件商品添加到購物車中。購物車的功能測試可以確保您能夠通過登錄某一 Web 頁面將商品添加到購物車中。而集成測試則可以保證能夠登錄、添加商品和結賬。

在 “Running Your Rails App Headless”(請參閱 參考資料)中,Mike Clark(Rails 社區領先的 測試專家之一)詳細介紹了集成測試框架。開始進行討論時,他介紹了如何運行沒有 Web 頁面的(即 headless)應用程序。這項功能使得搜集編寫集成測試的足夠信息變得更容易。從 Rails 1.1 開始,可 以直接從控制台調用控制器。不需要浏覽器,只要調用 app 對象的 put 和 get 方法,就可以訪問應用 程序的 Web 頁面。

請啟動控制台,鍵入清單 7 中的命令,通過 HTTP get 發出列表動作:

清單 7. 從控制台使用 Rails 集成測試框架

> script/console Loading development environment.
>> app.class
=> ActionController::Integration::Session
>> app.get('trails', 'list')
=> 200
>> app.get("trails/list")
=> 200
>> app.response =~ /Barton Creek/
=> false
>> app.response =~ /Emma Long/
=> false
>> app.response.body =~ /Emma Long/
=> 331
>>

在清單 7 中,從控制台以兩種形式發送請求,調用 trails 控制器的 list 動作。然後,通過與正則 表達式 /Emma Long/ 匹配,可以看到生成的 HTML 頁面中包含 Emma Long(一條路線)。您可以繼續運 行 post 和 get:

清單 8. 通過 post 實現刪除

>> app.post("trails/destroy/1")
=> 302
>> Trail.find_all
=> [#<Trail:0x25a8e34 @attributes={"name"=>"Bear Creek", "id"=>"2",
  "description"=>"Too many downed trees.", "difficulty"=>"easy"}>]
>> Trail.find_all.size
=> 1
>> app.response.redirect_url
=> "http://www.example.com/trails/list"
>>

通過控制台集成測試 API,現在有了構建集成測試的足夠信息。請使用 script/generate integration_test DestroyAndShow 生成一個集成測試,並將它編輯成清單 9 那樣:

清單 9. test/integration/destroy_and_show.rb

require "#{File.dirname(__FILE__)}/../test_helper"
class DestroyAndShowTest < ActionController::IntegrationTest
  fixtures :trails
  def test_multiple_actions
   get "trails/list"
   assert_response :success

   post "trails/destroy/1"
   assert_response :redirect
   assert_nil(response.body =~ /Emma Long/)
   assert_equal(2, Trail.find_all.size)

   follow_redirect!
   assert_response :success

   get "trails/show/2"
   assert_response :success

  end
end

這個示例使用的集成框架與前面通過 Rails 控制台使用的框架相同,使用的斷言模型也與功能測試和 單元測試框架的模型相同。可以用 rake 運行測試用例,也可以單獨運行每個測試用例。通過以一致的方 式使用控制台和集成框架,可以嘗試應用程序的各個方面,獲得控制台中的結果,並用這些結果在自動測 試用例中提供您的斷言。

在 Ruby 中測試與在 Java 語言中測試的對比

現在可以開始查看集成框架中的集成測試有什麼不同了。對於這個示例,可以使用 fixture,它們在 集成測試框架中工作。斷言和表示想法的方式(例如請求和響應)都有統一的形式。

基本 Ruby 語言中的某些功能讓 Rails 的測試更強大。可以使用 Ruby 做類似 mock 和存根所做的事 。在編寫這篇文章時,我正在使用 Rails 進行一些自動集成測試。我有一個依賴於當前日期的類。我只 是打開了用於 Date 的現有 Ruby 類,並重新定義了 today 方法,讓它返回 Date.civil(2, 2, 2006), 如清單 10 所示:

清單 10. 用 Rails 創建存根

require "#{File.dirname(__FILE__)}/../test_helper"
class Date
  def self.today
   return Date.civil(2006, 2, 2)
  end
end
class NameOfTest ...continue test case here...

對於我的測試用例,我什麼都不需要做。現在,不論測試用例什麼時候運行,today 都會是美國的假 日土拔鼠日。只使用了五行代碼,我就有了一個可工作的存根。在這個示例中,這個 mock 對象只能用於 測試用例。如果需要將這個 mock 對象用於多個測試用例,那麼可以給這個 mock 對象添加測試和模擬的 代碼,並重新使用它。

總之,我對 Ruby 的測試體驗的評價是:非常必要(因為動態語言容易出錯的特性),並且更強大。 其中部分力量來自通過 Rails 使得代碼生成、斷言、數據庫支持,以及診斷工具無縫地在一起工作的集 成體驗。

但是 Java 技術確實有自己的優勢。在將測試集成到開發環境方面它做得更好,它還有更好的持續集 成工具。也可以找到模擬最常見企業特性的更多框架。Java 開發人員有另一個理論優勢:他們可以在沒 有數據庫支持的情況下,更容易地運行應用程序。沒有數據庫支持就測試 Rails 應用程序幾乎沒有意義 ,因為許多 Rails 值是通過元編程(metaprogramming)把 SQL 特性編織起來而得到的。所以,Java 測 試套件通常運行得更快,因為套件中的測試用例不需要訪問數據庫。

如果使用 Java 代碼生成,Rails 可以為您提供一些關於如何使用測試生成增強您的代碼生成的好主 意。如果正在補充自己的測試框架,那麼 Rails 的測試 API 既簡單又漂亮。如果對超越 Java 編程語言 感興趣,那麼 Rails 可以為輕量級的、數據庫支持的應用程序提供一些真正的價值。

在這個系列的下一篇文章中,我將不再介紹 Rails,而是查看基於 Web 的建模策略。您將看到如何將 代碼生成用於動態語言。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved