在上一篇《RailsCasts中文版,#10 Refactoring User Name Part 1 重構實戰1》我們討論了重構,並演示了一個通過將代碼移動到模型中以達到去除冗餘的重構實例。不過這段代碼依然還有優化的餘地。
class User < ActiveRecord::Base
def full_name
name = first_name + ' '
name += "#{middle_initial}. " unless middle_initial.nil?
name += last_name
end
end
User
類在上一節結束時的狀態。
引入測試
在着手進一步重構User
之前,有必要了解一下測試。爲了保證重構過程不對代碼功能產生破壞,重構往往需要伴隨着測試的介入。好的測試用例爲重構質量保駕護航。通過rails
generate
生成模型類的同時,也會生成對應的測試類。與User
類對應的是位於/test/unit
目錄[1]的user_test.rb
文件。
require 'test_helper'
class UserTest < ActiveSupport::TestCase
# Replace this with your real tests.
test "the truth" do
assert true
end
end
Rails缺省生成的測試類。
由於是自動生成的,代碼邏輯知識簡單的用true == true
做了空實現。要用我們自己編寫的測試邏輯代替。創建一個測試用例:新建沒有中間名的用戶,檢查程序的輸出是否和我們期待的一樣。
test "full name without middle initial" do
user = User.new(:first_name => "John", :last_name => "Smith")
assert_equal 'John Smith', user.full_name
end
用於檢測不含中間名用戶的測試用例。
可以使用命令行rake test
運行用例,但在這裏我們準備使用自動測試工具AutoTest[2]。安裝ZenTest
gem(命令是sudo gem install ZenTest
)。使用自動測試的好處是他會適時的執行測試,以便當程序工作不正常時立刻就能被我們發現。運行autotest查看返回結果
Laa-Laa:ep11 eifion$ autotest
loading autotest/rails
/usr/local/bin/ruby -I.:lib:test -rtest/unit -e "%w[test/functional/users_controller_test.rb test/unit/user_test.rb].each { |f| require f }" | unit_diff -u
Loaded suite -e
Started
.
Finished in 0.046945 seconds.
1 tests, 1 assertions, 0 failures, 0 errors
AutoTest的輸出
好消息!測試通過了。我們再往UsetTest
類中添加一個用來檢測包含中間名這種情況的用例。
test "full name with middle initial" do
user = User.new(:first_name => "Paul", :middle_initial => "P", :last_name => "Hughes")
assert_equal 'Paul P. Hughes', user.full_name
end
當代碼保存後,AutoTest會自動執行用例。現在這兩個用例都應該是執行成功的。
重構開始
在保證full_name
方法工作正常的基礎上,我們準備開始重構了。局部變量name
其實可以去掉;字符串連接也可以被別的方法代替。取而代之的是將名字的每一部分分別放到數組中,調用數組的join
方法將他們用空格連接。
class User < ActiveRecord::Base
def full_name
[first_name, middle_initial, last_name].join(' ')
end
end
對User
類的第一次重構。
保存代碼後,自動測試的結果顯示兩個用例全都執行失敗了。
1) Failure:
test_full_name_with_middle_initial(UserTest)
[./test/unit/user_test.rb:11:in `test_full_name_with_middle_initial'
/usr/local/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in `run']:
--- /var/folders/yD/yDkhXjIsHAqkCTKsBbUlC++++TI/-Tmp-/expect31666.0 2009-01-04 11:23:35.000000000 +0000
+++ /var/folders/yD/yDkhXjIsHAqkCTKsBbUlC++++TI/-Tmp-/butwas31666.0 2009-01-04 11:23:35.000000000 +0000
@@ -1 +1 @@
-Paul P. Hughes
+Paul P Hughes
2) Failure:
test_full_name_without_middle_initial(UserTest)
[./test/unit/user_test.rb:6:in `test_full_name_without_middle_initial'
/usr/local/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in `run']:
--- /var/folders/yD/yDkhXjIsHAqkCTKsBbUlC++++TI/-Tmp-/expect31666.1 2009-01-04 11:23:35.000000000 +0000
+++ /var/folders/yD/yDkhXjIsHAqkCTKsBbUlC++++TI/-Tmp-/butwas31666.1 2009-01-04 11:23:35.000000000 +0000
@@ -1 +1 @@
-John Smith
+John Smith
2 tests, 2 assertions, 2 failures, 0 errors
AutoTest輸出顯示兩個用例執行失敗。
第一個用例失敗的原因是,輸出的中間名後面沒有帶上點號。第二個失敗的原因是,當沒有中間名時,名和姓之間放入了兩個空格。先來改正第二個錯誤,這個錯誤產生的原因是無論是否有中間名,都會將各個名字用空格相連。修改這個問題,是在調用join
方法之前調用compact
方法。(compact
方法會從列表中移除掉所有的nil
值。)
def full_name
[first_name, middle_initial, last_name].compact.join(' ')
end
重新執行用例後,只剩第一個問題了。我們重建一個叫
middle_initial_with_full_stop
的方法,根據是否有中間名,返回適當的字符串。
def full_name
[first_name, middle_initial_with_full_stop, last_name].compact.join(' ')
end
def middle_initial_with_full_stop
"#{middle_initial}." unless middle_initial.blank?
end
修改之後,兩個用例都能跑過。但仍然有一種情況漏測了。那就是如果輸入的中間名是一個空字符串的時候。寫一個用例覆蓋一下這種場景。
test "full name with empty middle initial" do
user = User.new(:first_name => "John", :middle_initial => "", :last_name => "Jones")
assert_equal 'John Jones', user.full_name
end
也能通過,結果正確。原因是blank?
方法對於空字符串和nil
都會返回true
,所以可以在調用compact
方法時被移除。
這樣一來User
類的代碼比以前更可讀了。不過現在測試用例的代碼有點重複了,關於測試用例的優化,下回分解。
重構後的狀態
class User < ActiveRecord::Base
def full_name
[first_name, middle_initial_with_full_stop, last_name].compact.join(' ')
end
def middle_initial_with_full_stop
"#{middle_initial}." unless middle_initial.blank?
end
end
重構後的
User
類代碼。
require 'test_helper'
class UserTest < ActiveSupport::TestCase
test "full name without middle initial" do
user = User.new(:first_name => "John", :last_name => "Smith")
assert_equal 'John Smith', user.full_name
end
test "full name with middle initial" do
user = User.new(:first_name => "Paul", :middle_initial => "P", :last_name => "Hughes")
assert_equal 'Paul P. Hughes', user.full_name
end
test "full name with empty middle initial" do
user = User.new(:first_name => "John", :middle_initial => "", :last_name => "Jones")
assert_equal 'John Jones', user.full_name
end
end
與User
類對應的測試用例代碼。
注
- 視頻是基於Rails 1錄製的。代碼使用Rails 2.2編寫。
- http://rubyforge.org/projects/zentest/
作者授權:Your welcome to post the translated text on your blog as well if the episode is free(not
Pro). I just ask that you post a link back to the original episode on railscasts.com.
原文鏈接:http://railscasts.com/episodes/11-refactoring-user-name-part-2