Rails內建可進行單元測試、功能測試、整合測試與效能測試的方式,使用rails產生模型、控制器或郵件處理時,都會一併產生相關測試檔案,以 觀摩 Scaffold 中的例子來看:
~gossip\$ rails g scaffold Message name:string title:string content:text
invoke active_record
...略
invoke test_unit
create test/unit/message_test.rb
create test/fixtures/messages.yml
...略
invoke scaffold_controller
...略
invoke test_unit
create test/functional/messages_controller_test.rb
invoke helper
...略
invoke test_unit
create test/unit/helpers/messages_helper_test.rb
...格
invoke active_record
...略
invoke test_unit
create test/unit/message_test.rb
create test/fixtures/messages.yml
...略
invoke scaffold_controller
...略
invoke test_unit
create test/functional/messages_controller_test.rb
invoke helper
...略
invoke test_unit
create test/unit/helpers/messages_helper_test.rb
...格
可以觀察到,測試相關檔案,基本上都產生在test目錄中,在這個目錄中可以看到:
~gossip/test\$ ls
fixtures functional integration performance test_helper.rb unit
fixtures functional integration performance test_helper.rb unit
unit、functional、integration與performance目錄,分別讓你放置單元測試、功能測試、整合測試與效能測試的程式,測試過程中要使用到的範例資料,可以將之整理在fixures目錄,test_helper.rb預設的定義是:
- test_helper.rb
ENV["RAILS_ENV"] = "test"
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
class ActiveSupport::TestCase
# Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
#
# Note: You'll currently still have to declare fixtures explicitly in integration tests
# -- they do not yet inherit this setting
fixtures :all
# Add more helper methods to be used by all tests here...
end
預設產生的單元測試案例,通常會require 'test_helper'。例如unit目錄中的message_test.rb:
- message_test.rb
require 'test_helper'
class MessageTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end
由於繼承了ActiveSupport::TestCase,因此在test_helper.rb中ActiveSupport::TestCase類別定義的內容,在每個測試案例中都可以使用,你可以在test_helper.rb中定義相關輔助方法。
在message_test.rb中看到了定義測試案例的基本方式,就是繼承ActiveSupport::TestCase,Rails提供的測試框架,實際上以Ruby的 Test::Unit 為基礎,在Rails中定義了test方法,可接上測試的字串描述。
舉個例子來說,若你新建了一個專案,在還沒有產生任何模型之前,可自定義一個rational_number_test.rb:
- rational_number_test.rb
require 'test_helper'
class RationalNumberTest < ActiveSupport::TestCase
test "rational number addition" do
r1 = RationalNumber.new(1, 2)
r2 = RationalNumber.new(2, 3)
expected = RationalNumber.new(7, 6)
result = r1 + r2
assert_equal expected.numer, result.numer
assert_equal expected.denom, result.denom
end
end
如上定義了一個單元測試,test方法會依字串描述,產生一個test_rational_number_addition方法,assert_equal斷言兩個物件是否相等,一個執行測試的例子如下:
~gossip\$ ruby -Itest test/unit/rational_number_test.rb
Loaded suite test/unit/rational_number_test
Started
RationaNumberlTest:
ERROR rational addition (1.75s)
NameError: uninitialized constant RationaNumberlTest::RationalNumber
test/unit/rational_number_test.rb:5:in `block in <class:RationaNumberlTest>'
Finished in 1.762090 seconds.
1 tests, 0 assertions, 0 failures, 1 errors, 0 skips
Loaded suite test/unit/rational_number_test
Started
RationaNumberlTest:
ERROR rational addition (1.75s)
NameError: uninitialized constant RationaNumberlTest::RationalNumber
test/unit/rational_number_test.rb:5:in `block in <class:RationaNumberlTest>'
Finished in 1.762090 seconds.
1 tests, 0 assertions, 0 failures, 1 errors, 0 skips
這是個用ruby指令直接啟動測試的例子,由於要require的test_helper.rb是在test目錄下,因此要使用-I指定test目錄。目前實際上沒有撰寫RationalNumber的定義,因此測試流程根本無法完成,因此測試結果為ERROR。如果定義了RationalNumber如下:
- rational_number.rb
class RationalNumber
attr_accessor :numer, :denom
def initialize(n, d)
@numer = n
@denom = d
end
def +(that)
self
end
end
這次順便示範一下如何指定測試方法:
~gossip\$ ruby -Itest -Itest/unit test/unit/rational_number_test.rb -n test_rational_number_addition
Loaded suite test/unit/rational_number_test
Started
RationaNumberlTest:
FAIL rational number addition (1.02s)
<7> expected but was
<1>.
test/unit/rational_number_test.rb:9:in `block in <class:RationaNumberlTest>'
Finished in 1.025792 seconds.
1 tests, 1 assertions, 1 failures, 0 errors, 0 skips
Loaded suite test/unit/rational_number_test
Started
RationaNumberlTest:
FAIL rational number addition (1.02s)
<7> expected but was
<1>.
test/unit/rational_number_test.rb:9:in `block in <class:RationaNumberlTest>'
Finished in 1.025792 seconds.
1 tests, 1 assertions, 1 failures, 0 errors, 0 skips
可以看到斷言失敗,因此顯示FAIL,這是因為RationalNumber的+方法沒有正確撰寫,補上+方法內容順便加上-方法:
- rational_number.rb
class RationalNumber
attr_accessor :numer, :denom
def initialize(n, d)
@numer = n
@denom = d
end
def +(that)
RationalNumber.new(self.numer * that.denom + that.numer * self.denom,
self.denom * that.denom)
end
def -(that)
RationalNumber.new(self.numer * that.denom - that.numer * self.denom,
self.denom * that.denom)
end
end
再度執行測試:
~gossip\$ ruby -Itest -Itest/unit test/unit/rational_number_test.rb
Loaded suite test/unit/rational_number_test
Started
RationaNumberlTest:
PASS rational number addition (1.83s)
Finished in 1.831376 seconds.
1 tests, 2 assertions, 0 failures, 0 errors, 0 skips
Loaded suite test/unit/rational_number_test
Started
RationaNumberlTest:
PASS rational number addition (1.83s)
Finished in 1.831376 seconds.
1 tests, 2 assertions, 0 failures, 0 errors, 0 skips
這次執行測試成功,因此顯示PASS。你可以在一個測試案例類別中加上數個測試。例如:
- rational_number_test.rb
require 'test_helper'
class RationaNumberlTest < ActiveSupport::TestCase
test "rational number addition" do
r1 = RationalNumber.new(1, 2)
r2 = RationalNumber.new(2, 3)
expected = RationalNumber.new(7, 6)
result = r1 + r2
assert_equal expected.numer, result.numer
assert_equal expected.denom, result.denom
end
test "rational number subtraction" do
r1 = RationalNumber.new(1, 2)
r2 = RationalNumber.new(2, 3)
expected = RationalNumber.new(-1, 6)
result = r1 - r2
assert_equal expected.numer, result.numer
assert_equal expected.denom, result.denom
end
end
可以看到兩個測試中都準備了r1與r2,對於每個test執行開始前要準備的資源,你可以定義setup,對於每個test執行後要回收的資源,你可以定義teardown。例如:
- rational_number_test.rb
require 'test_helper'
class RationaNumberlTest < ActiveSupport::TestCase
def setup
@r1 = RationalNumber.new(1, 2)
@r2 = RationalNumber.new(2, 3)
end
def teardown
@r1 = nil
@r2 = nil
end
test "rational number addition" do
expected = RationalNumber.new(7, 6)
result = @r1 + @r2
assert_equal expected.numer, result.numer
assert_equal expected.denom, result.denom
end
test "rational number subtraction" do
expected = RationalNumber.new(-1, 6)
result = @r1 - @r2
assert_equal expected.numer, result.numer
assert_equal expected.denom, result.denom
end
end
除了定義setup與teardown方法之外,ActiveSupport::TestCase本身也擁有setup與teardown,可以接受程式區塊、Symbol或lambda。