RailsCasts中文版,#11 Refactoring User Name Part 2 重構實戰2

上一篇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類對應的測試用例代碼。

  1. 視頻是基於Rails 1錄製的。代碼使用Rails 2.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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章