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

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

編輯:關於JAVA

捕獲 bug

我還記得當我第一次得到自動測試的 bug 時的情況。在一次大會上,當我做完叫做 Bitter Java 的 演講之後,Mike Clark(Java 社區的自動測試大師,性能調整工具 JUnitPerf 的作者(請參閱 參考資 料),現在是 Ruby on Rails 專家)走近我。Mike 告訴我有一種方法可以通過自動測試改進我的演講。 在那次大會的剩余時間裡,我跟著他四處走,看到了我能看到的盡可能多的他的測試會議。我開始使用他 推薦的技術,並對把紅條(代表測試失敗)變成綠條(代表測試通過)上了瘾。自動測試改變了我思考軟 件開發的方式。

Java 社區絕對有自動測試的 bug。坦白地說,我們別無選擇。競爭壓力迫使許多公司編寫越來越多的 代碼,而測試人員越來越少,同時每個開發人員的又必須有更高的生產率。如果不進行自動測試,得到測 試的內容就會更少,面對現代應用程序不斷增長的復雜性,較少的測試不是一個可行的選擇方案。

在過去十年中,我們已經看到了對測試工具和技術的研究。JUnit 和 TestNG 都是支持自動單元測試 的優秀工具,而且由日常的開發人員所驅動。Selenium 是改進集成和功能測試的工具。一套稱作敏捷技 術 的新開發過程告訴人們要更加重視自動測試,不要太多地依賴正式的設計工具,將它們作為提高質量 的惟一工具。Java 社區已經走了很長的路。 (請參閱 參考資料,獲得這裡討論的工具與技術的附加信息 。)

其他編程社區也有 bug 工具, 其中一些社區使用的自動測試要比 Java 開發人員還有多,他們使用 自動測試經驗有完全不同的原因:

Smalltalk 程序員使用自動測試已經幾乎有 30 年的時間了,所以通過動態類型化語言使用的一些技 術更加先進。

集成框架的開發人員的優勢是了解框架元素的結構和組合。有些框架,例如 Ruby on Rails,能夠生 成測試用例,而且在默認情況下提供測試特性。

具有高級元編程(metaprogramming)能力的語言,例如 Ruby and Lisp,允許使用其他語言不支持的 一些測試技巧,例如更容易訪問 mock 對象。

在這一篇和下一篇文章中,將全面理解在 Ruby on Rails 集成開發框架中的測試方式。第 1 部分側 重於測試模型對象,並提供一些從 Rails 獲得啟發的策略,可以用這些策略使 Java 單元測試更有效。 第 2 部分把更多時間花在功能測試和集成測試上。作為 Java 程序員,您對一些概念可能比較熟悉,特 別是在測試的時候,而其他一些概念可以拓展您的理解。

補漏

在這個系列的 前一期 中,了解了動態類型化會帶來某些 bug 種類,靜態類型化語言將在編譯時捕捉 到這些 bug。清單 1 的 Ruby 代碼片段包含四個不同的 bug,這四個 bug 在運行時之前都不會顯露出來 :

清單 1. 帶 bug 的 Ruby 代碼

position = "2"        #string, where a number was intended
position = positoin + 4   #position is misspelled, evaluates to 0
puts "The position is:" +
    position.to_string   #The method should be to_s

如果編譯器能夠捕捉 bug,那麼這類 bug 解決起來是小菜一碟,但是如果依賴解釋器,那麼管理這些 bug 就困難得多。為了處理這些微妙的錯誤,動態語言的用戶長期以來一直依賴於自動測試。在進行測試 的時候,比起其他語言,動態語言及其集成環境在一般意義和特殊意義上都具有顯著的優勢:

語言更簡潔。測試基本上是腳本編程,許多最好的腳本語言都是動態類型化的。

集成環境支持的假設可以讓集成測試更容易,也可能更強大。在 Rails 環境中將看到一些示例。

動態語言允許使用更松散的耦合,使一些測試格式更容易實現。

在了解動態語言開發人員為什麼這麼熱衷於測試之後,現在是構建一個需要一些真正測試的實際應用 程序的時候了。

構建一個快速 Rails 應用程序

為了進展得快些,我采用了一個保存山地摩托車路線數據庫的 Rails 應用程序。我將模型的幾個測試 放在一起。如果想和我一起編寫代碼,那麼所有需要的工具就是一個數據庫引擎(我使用的是 MySQL)和 Ruby on Rails 1.1 或更新版本(請參閱 參考資料)。第一步是創建 Rails 項目。在命令提示符下輸入 rails trails 命令,清單 2 顯示了命令和結果:

清單 2. 構建 Rails 應用程序

> rails trails
create 
create app/controllers
create app/helpers
create app/models
create app/views/layouts
...partial results deleted...
create test/fixtures
create test/functional
create test/integration
create test/mocks/development
create test/mocks/test
create test/unit
create test/test_helper.rb
...partial results deleted...
create config/environment.rb
create config/environments/production.rb
create config/environments/development.rb
create config/environments/test.rb
...partial results deleted...
create log/server.log
create log/production.log
create log/development.log
create log/test.log

Rails 除了生成空項目什麼都沒做,但是可以看到它正在為您工作。清單 2 創建的目錄中包含:

應用程序目錄,包括模型、視圖和控制器的子目錄

單元測試、功能測試和集成測試的測試目錄

為測試而明確創建的環境

測試用例結果的日志

因為 Rails 是一個集成環境,所以它可以假設組織測試框架的最佳方式。Rails 也能生成默認測試用 例,後面將會看到。

現在要通過遷移創建數據庫表,然後用數據庫表創建新數據庫。請鍵入 cd trails 進入 trails 目錄 。然後生成一個模型和遷移(migration),如清單 3 所示:

清單 3. 生成一個模型和遷移

> script/generate model Trail
      exists app/models/
      exists test/unit/
      exists test/fixtures/
      create app/models/trail.rb
      create test/unit/trail_test.rb
      create test/fixtures/trails.yml
      create db/migrate
      create db/migrate/001_create_trails.rb

注意,如果使用 Windows,就必須在命令前加上 Ruby,這樣命令就變成了 ruby script/generate model Trail。

如清單 3 所示,Rails 環境不僅創建了模型,還創建了遷移、測試用例和測試 fixture。稍後將看到 fixture 和測試的更多內容。遷移讓 Rails 開發人員可以在整個開發過程中處理數據庫表中不可避免的 更改(請參閱 跨越邊界:研究活動記錄)。請編輯您的遷移(在 001_create_trails.rb 中),以添加 需要的列,如清單 4 所示:

清單 4. 添加列

class CreateTrails < ActiveRecord::Migration
  def self.up
    create_table :trails do |t|
     t.column :name, :string
     t.column :description, :text
     t.column :difficulty, :string
  end
end
  def self.down
    drop_table :trails
  end
end

您需要創建和配置兩個數據庫:trails_test 和 trails_development。如果想把這個代碼投入生產, 那麼還需要創建第三個數據庫 trails_production,但是現在可以跳過這一步。請用數據庫管理器創建數 據庫。我使用的是 MySQL:

清單 5. 創建開發和測試數據庫

mysql> create database trails_development;
Query OK, 1 row affected (0.00 sec)
mysql> create database trails_test;
Query OK, 1 row affected (0.00 sec)

然後編輯 config/database.yml 中的配置,以反映數據庫的優先選擇。我的配置看起來像這樣:

清單 6. 將數據庫適配器添加到配置中

development:
  adapter: mysql
  database: trails_development
  username: root
  password:
  host: localhost
test:
  adapter: mysql
  database: trails_test
  username: root
  password:
  host: localhost

現在可以運行遷移,然後把應用程序剩下的部分搭建(scaffold)在一起:

清單 7. 遷移和搭建

> rake migrate
...results deleted...

> script/generate scaffold Trail Trails
...results deleted...
   create app/views/trails
   ...results deleted...
   create app/views/trails/_form.rhtml
   create app/views/trails/list.rhtml
   create app/views/trails/show.rhtml
   create app/views/trails/new.rhtml
   create app/views/trails/edit.rhtml
   create app/controllers/trails_controller.rb
   create test/functional/trails_controller_test.rb
   ...results deleted...

再次注意,Rails 已經為您創建了測試用例。框架不僅為這個簡單的小程序生成了視圖和控制器,而 且還生成了有助於測試用戶界面的功能性測試。

對 Rails 應用程序進行單元測試

現在是運行一些測試的時候了。請看第一個測試,它已經在 test/unit/trail_test.rb 中寫好了:

清單 8. 第一個測試

require File.dirname(__FILE__) + '/../test_helper'
class TrailTest < Test::Unit::TestCase
  fixtures :trails
  # Replace this with your real tests.
  def test_truth
    assert true
  end
end

確實,這個測試用例算不了什麼,但您可以從中看出如何構架測試代碼,而且自己的測試用例的模板 也已經就位。請運行測試,如清單 9 所示(包括結果):

清單 9. 運行第一個測試

> ruby test/unit/trail_test.rb
   Loaded suite test/unit/trail_test
   Started
   EE
   Finished in 0.027314 seconds.
    1) Error:
   test_truth(TrailTest):
   ActiveRecord::StatementInvalid: Mysql::Error: #42S02Table
    'trails_test.trails' doesn't exist: DELETE FROM trails
...results deleted...

測試用例失敗,但是請看輸出。第一行執行測試。第三行 EE 顯示測試的結果。如果測試用例通過, 會得到 “.” 字符。如果測試用例產生錯誤,會看到 E。如果某個斷言不是 true,那麼將看到 F。接下 來,可以看到所請求的全部測試都將完成,以及完成這些測試需要的時間。最後,將看到每個失敗的詳細 原因。在這個示例中沒有表,這是有一定原因的,因為在測試數據庫中還沒有創建任何表。通過將開發方 案復制到測試環境,再重新運行測試,可以修復錯誤,如清單 10 所示:

清單 10. 復制方案,重新運行測試

> rake clone_schema_to_test     (in /Users/batate/rails/trails)
> ruby test/unit/trail_test.rb
   Loaded suite test/unit/trail_test
   Started
   .
   Finished in 0.038578 seconds.
   1 tests, 1 assertions, 0 failures, 0 errors

這樣更好。但是測試還是太簡單,所以是構建一個真正的測試用例的時候了。請添加下面這個新測試 用例 test_truth,如清單 11 所示:

清單 11. 添加測試用例

def test_truth
    assert true
   end
   def test_new
    trails = Trail.find_all
    Trail.new do |trail|
     trail.name = "Barton Creek"
     trail.description = "A little water in the Spring. You'll get wet."
     trail.difficulty = "medium"
     trail.save
    end
    bc = Trail.find_by_name("Barton Creek")
    assert_equal "medium", bc.difficulty
    assert_equal trails.size + 1, Trail.find_all.size
   end

這個代碼驚人的緊湊。只需要鍵入上述代碼以及兩個斷言,就可以操縱持久模型。這種經濟的投入正 是腳本語言在其他環境中如此流行的原因。測試也是需要經濟投入的地方。

現在可以運行測試用例,您將看到兩個新斷言顯示在測試報告中。使用 Ruby 時,只需保存並編譯測 試即可。清單 12 顯示了測試運行的結果:

清單 12. 測試結果

> ruby test/unit/trail_test.rb
   Loaded suite test/unit/trail_test
   Started
   .
   Finished in 0.038578 seconds.
   1 tests, 1 assertions, 0 failures, 0 errors
   bruce-tates-computer:~/rails/trails batate$ ruby test/unit/trail_test.rb
   Loaded suite test/unit/trail_test
   Started
   ..
   Finished in 0.182043 seconds.
   2 tests, 3 assertions, 0 failures, 0 errors

Fixture 和回滾

有三個問題影響了對數據庫支持代碼的測試。它們都與兩個特性有關:性能和重復性。與內存中的操 作相比較,數據庫調用的性能是非常低的。如果測試運行需要太長時間,那麼您可能就不想運行它們了。 另一個問題是一個測試用例對另一個測試用例的影響。因為數據庫調用在性質上是持續的,所以要把一個 測試在數據庫中的變化與另一個數據庫中的隔離開。最後的問題是前兩個問題的組合。為了讓數據庫測試 用例可重復而增加設置和拆卸的負擔時(為每個新的測試用例添加記錄、運行測試並刪除這些記錄),帶 來的開銷可能是讓人無法接受的。與這種開銷相比,測試用例開銷簡直是小巫見大巫。

Ruby on Rails 用 fixture 和事務回滾來幫助解決這些問題。在 Rails 中,一個 fixture 就是一個 包含測試用例數據的文件。在創建這個簡單應用程序時,同時還創建了一個開發數據庫和一個測試數據庫 。創建開發數據庫是很正常的;但是您可能不想讓生產代碼和開發環境共享同一個數據庫。而創建測試數 據庫因為另一個原因也很重要。每個測試都在測試用例開始時裝入 fixture 中的測試數據。然後,測試 用例對數據庫進行修改,並測試這些修改的結果。最後,Rails 回滾這些變化,將數據庫返回到測試方法 運行之前的狀態。

現在要制作一個測試 fixture 並為它編寫一個測試。請編輯 test/fixtures/trails.yml 文件,添加 一個記錄,如清單 13 所示:

清單 13. 添加記錄

first:
    id: 1
    name: "Emma Long"
    description: "A real bike breaker."
    difficulty: "hard"
   another:
    id: 2
    name: "Bear Creek"
    description: "Too many downed trees."
    difficulty: "easy"

清單 13 使用叫做 YAML 的語言,這個語言描述結構化的數據(請參閱 參考資料)。此文件對空格很 敏感,所以該當用空格代替制表符並完全按原樣鍵入數據項時,請確保刪除了所有尾部空格。

同樣,還要把這個測試用例添加到 trails_test.rb 中:

def test_find
    assert_equal "Emma Long", Trail.find(1).name
    assert_equal "easy", Trail.find(2).difficulty
   end

同樣,可以用 5 個 passing 斷言運行這些測試。如果您願意,還可以按名稱引用每個 fixture。例 如,要根據名為 first 的 fixture 來創建對象,可以使用 Ruby 代碼 trails[:first]。讓 fixture 對 所有測試用例或只對需要它們的測試用例可用,這極大地簡化了創建或毀壞數據庫數據所需要的代碼。

在 Java 編程中測試

知道了測試在其他語言中如何發生,就可以改進在 Java 平台上進行測試的方式。具體地說,使用這 些想法中的一項或多項可以對測試產生顯著而直接的影響:

可以把測試用例的生成添加到任何現有代碼生成當中。Ruby on Rails 通過在默認情況下創建一些簡 單的測試用例來取得了巨大優勢,您也可以這麼做。

可以用事務-回滾技術讓數據支持的測試運行得更快。Spring 框架有一些現有的攔截器,可以讓這項 技術易於使用。

實際上可以用動態語言驅動測試。Jython、Ruby 和 Groovy 是三個實際可能。

如果覺得願意采用其他語言進行測試,那麼可以使用某種 JVM 語言,例如 JRuby(請參閱 參考資料 )。JRuby 還沒有高級到可以運行 Ruby on Rails,但是它是 Java 應用程序卓越的測試平台。只是作為 嘗試,JRuby 的開發人員 Charles O'Nutter 提供了以下測試 EJB 的示例:

清單 14. 用 JRuby 測試 EJB 組件

require 'test/unit'
   require 'java'
   include_class "my.pkg.EJBHomeFactory"
   class TestMyBean < Test::Unit::TestCase
    def test_finder
     wh = EJBHomeFactory.widget_home
     w = wh.find_by_color("blue")
     assert_not_nil(w)
    end
    def test_widget
     wh = EJBHomeFactory.widget_home
     w = wh.find_by_name ("superWidget")
     assert_equal("blue", w.color)
     assert_equal(14, w.id)
    end
   end

可以看到,用 Ruby 編寫執行 Java 代碼的測試用例實際上非常容易。在這個示例中,Ruby 代碼發現 一個 EJB 組件,並為用戶返回的 bean 提供了一些斷言。測試用例當然比多數 Java 測試都容易,使用 Ruby 編寫測試用例是一個獲得更高的生產率和速率的一種好方法。我還看到針對 Jython 或 Groovy 的 類似策略(請參閱 參考資料)。

第 2 部分將進一步深入查看 Rails 的測試,包括運行更高層次測試(叫做功能測試和集成測試)的 代碼。

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