Ruby 程式設計/單元測試
單元測試 是一種在開發過程早期發現錯誤的好方法,前提是您投入時間編寫適當且有用的測試。與其他語言一樣,Ruby 在其標準庫中提供了一個框架,用於設定、組織和執行名為 Test::Unit 的測試。
還有其他非常流行的測試框架,rspec 和 cucumber 就是其中之一。
具體來說,Test::Unit 提供了三種基本功能
- 一種定義基本透過/失敗測試的方法。
- 一種將相關測試收集在一起並作為一組執行的方法。
- 執行單個測試或整組測試的工具。
首先建立一個新類。
# File: simple_number.rb
class SimpleNumber
def initialize(num)
raise unless num.is_a?(Numeric)
@x = num
end
def add(y)
@x + y
end
def multiply(y)
@x * y
end
end
讓我們從一個例子開始,測試 SimpleNumber 類。
# File: tc_simple_number.rb
require_relative "simple_number"
require "test/unit"
class TestSimpleNumber < Test::Unit::TestCase
def test_simple
assert_equal(4, SimpleNumber.new(2).add(2) )
assert_equal(6, SimpleNumber.new(2).multiply(3) )
end
end
它產生
>> ruby tc_simple_number.rb
Loaded suite tc_simple_number
Started
.
Finished in 0.002695 seconds.
1 tests, 2 assertions, 0 failures, 0 errors
這裡發生了什麼?我們定義了一個類 TestSimpleNumber,它繼承自 Test::Unit::TestCase。在 TestSimpleNumber 中,我們定義了一個名為 test_simple 的成員函式。該成員函式包含一些簡單的 斷言,這些斷言會執行我的類。當我們執行該類時(請注意,我沒有在它周圍放置任何包裝程式碼——它只是一個類定義),測試會自動執行,我們會被告知我們已經運行了 1 個測試和 2 個斷言。
讓我們嘗試一個更復雜的例子。
# File: tc_simple_number2.rb
require_relative "simple_number"
require "test/unit"
class TestSimpleNumber < Test::Unit::TestCase
def test_simple
assert_equal(4, SimpleNumber.new(2).add(2) )
assert_equal(4, SimpleNumber.new(2).multiply(2) )
end
def test_typecheck
assert_raise( RuntimeError ) { SimpleNumber.new('a') }
end
def test_failure
assert_equal(3, SimpleNumber.new(2).add(2), "Adding doesn't work" )
end
end
>> ruby tc_simple_number2.rb
Loaded suite tc_simple_number2
Started
F..
Finished in 0.038617 seconds.
1) Failure:
test_failure(TestSimpleNumber) [tc_simple_number2.rb:16]:
Adding doesn't work.
<3> expected but was
<4>.
3 tests, 4 assertions, 1 failures, 0 errors
現在類中有三個測試(三個成員函式)。函式 test_typecheck 使用 assert_raise 檢查異常。函式 test_failure 被設定為失敗,Ruby 輸出愉快地指出這一點,不僅告訴我們哪個測試失敗,還告訴我們它如何失敗(預期 <3> 但實際為 <4>)。在這個斷言中,我們還添加了一個最終引數,這是一個自定義錯誤訊息。它是嚴格可選的,但對於除錯可能很有用。所有斷言都包含自己的錯誤訊息,這些訊息通常足以進行簡單的除錯。
Test::Unit 提供了一套豐富的斷言,這些斷言在 Ruby-Doc 中有詳細的文件記錄。以下是一些簡短的摘要(斷言及其否定被分組在一起。文字描述通常針對列出的第一個斷言——名稱應該有一定的邏輯意義)
| assert( boolean, [message] ) | 如果 boolean 為真,則返回真。 |
| assert_equal( expected, actual, [message] ) assert_not_equal( expected, actual, [message] ) |
如果 expected == actual,則返回真。 |
| assert_match( pattern, string, [message] ) assert_no_match( pattern, string, [message] ) |
如果 string =~ pattern,則返回真。 |
| assert_nil( object, [message] ) assert_not_nil( object, [message] ) |
如果 object == nil,則返回真。 |
| assert_in_delta( expected_float, actual_float, delta, [message] ) | 如果 (actual_float - expected_float).abs <= delta,則返回真。 |
| assert_instance_of( class, object, [message] ) | 如果 object.class == class,則返回真。 |
| assert_kind_of( class, object, [message] ) | 如果 object.kind_of?(class),則返回真。 |
| assert_same( expected, actual, [message]) assert_not_same( expected, actual, [message] ) |
如果 actual.equal?( expected ),則返回真。 |
| assert_raise( Exception,... ) {block} assert_nothing_raised( Exception,...) {block} |
如果程式碼塊引發(或不引發)列出的異常之一,則返回真。 |
| assert_throws( expected_symbol, [message] ) {block} assert_nothing_thrown( [message] ) {block} |
如果程式碼塊丟擲(或不丟擲)expected_symbol,則返回真。 |
| assert_respond_to( object, method, [message] ) | 如果物件可以響應給定方法,則返回真。 |
| assert_send( send_array, [message] ) | 如果使用給定引數傳送到物件的方法返回真,則返回真。 |
| assert_operator( object1, operator, object2, [message] ) | 使用給定運算子比較兩個物件,如果 true,則透過。 |
特定程式碼單元的測試被分組到一個 測試用例 中,它是一個 Test::Unit::TestCase 的子類。斷言被收集在 測試 中,測試用例的成員函式,其名稱以test_開頭。當執行或需要測試用例時,Test::Unit 將迭代測試用例中的所有測試(使用反射查詢所有以 test_ 開頭的成員函式),並提供適當的反饋。
測試用例類可以被收集到 測試套件 中,測試套件是需要其他測試用例的 Ruby 檔案
# File: ts_all_the_tests.rb
require 'test/unit'
require 'test_one'
require 'test_two'
require 'test_three'
這樣,相關的測試用例可以自然地分組。此外,測試套件可以包含其他測試套件,從而允許構建測試層次結構。
這種結構提供了對測試的相對細粒度控制。可以從測試用例執行單個測試(見下文),可以單獨執行完整的測試用例,可以執行包含多個用例的測試套件,也可以執行多個套件,跨越多個測試用例。
Test::Unit 的作者 Nathaniel Talbott 建議使用tc_作為測試用例名稱的開頭,使用ts_
作為測試套件名稱的開頭。
執行特定測試>> ruby -w tc_simple_number2.rb --name test_typecheck
Loaded suite tc_simpleNumber2
Started
.
Finished in 0.003401 seconds.
1 tests, 1 assertions, 0 failures, 0 errors
可以從完整的測試用例中執行一個(或多個)測試
>> ruby -w tc_simple_number2.rb --name /test_type.*/
Loaded suite tc_simpleNumber2
Started
.
Finished in 0.003401 seconds.
1 tests, 1 assertions, 0 failures, 0 errors
也可以執行名稱與給定模式匹配的所有測試
設定和拆卸[編輯 | 編輯原始碼]在許多情況下,需要在每個測試之前和/或之後執行一小段程式碼。Test::Unit 提供了setup和teardown
# File: tc_simple_number3.rb
require "./simple_number"
require "test/unit"
class TestSimpleNumber < Test::Unit::TestCase
def setup
@num = SimpleNumber.new(2)
end
def teardown
## Nothing really
end
def test_simple
assert_equal(4, @num.add(2) )
end
def test_simple2
assert_equal(4, @num.multiply(2) )
end
end
>> ruby tc_simple_number3.rb
Loaded suite tc_simple_number3
Started
..
Finished in 0.00517 seconds.
2 tests, 2 assertions, 0 failures, 0 errors
成員函式,它們會在每個測試(成員函式)之前和之後執行。
練習實現一個具有公共方法的類,該方法可以解決以下問題:使用者有一個任意周長的橡膠腳踏車輪胎。當一側被切開,腳踏車輪胎被拉伸成一條直線時,它的長度被測量為任意長度。根據該長度,確定腳踏車輪胎最初的半徑。