配置 測試/調試 環(huán)境
一本沒有測試相關內容的 Ruby 的書不是完整的舔株。如果你對發(fā)布和貢獻開源項目感興趣的話威酒,社區(qū)會更嚴肅的對待你的代碼如果它們是被測試覆蓋的并且測試通過的話鹤竭。
測試驅動開發(fā)(TDD) 是一種實戰(zhàn),在你寫代碼之前先寫測試胎源。實踐 TDD 幫助我們只寫必要的能讓測試通過的代碼耻卡。這也能減少過度工程的可能性(注意到這里有一個模式了嗎?)
在 ??Ruby 測試社區(qū)有兩種觀點吉执。一種喜歡 Minitest (Ruby標準庫內置的), 另一個更喜歡 Rspec。我喜歡后者并且每天都使用 Rspec塔拳。我發(fā)現(xiàn)它很適合我而且我喜歡用 DSL 來組織我的測試鼠证。
依賴
為了加入 Rspec,讓我們打開 mega_lotto.gemspec
文件然后加入下面的依賴:
spec.add_development_dependency "rspec"
注意: 由于我們不想強制讓 rspec
被我們的宿主應用加載靠抑,我們使用 add_development_dependency
方法量九,而不是 add_dependency
。
現(xiàn)在, 讓我們切換到終端颂碧,在我們的 gem 的根目錄下運行:
$ bundle install
這會安裝在 gemspec 里列出的所有的依賴(包括 Rspec)荠列。輸出結果就像下面那樣:
Fetching gem metadata from https://rubygems.org/.........
Fetching gem metadata from https://rubygems.org/..
Resolving dependencies...
Resolvingdependencies...
Using rake (10.1.0)
Using bundler (1.3.5)
Using diff-lcs (1.2.5)
Using mega_lotto (0.0.1) from source at . Using rspec-core (2.14.7)
Using rspec-expectations (2.14.4) Using rspec-mocks (2.14.4)
Using rspec (2.14.1)
Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.
不要過于在意版本號,它們會經常更變载城。正如你所看到的肌似,bundler
安裝了 rake
,rspec
(由多個 gem 組成)诉瓦,和我們的 gem, mega_lotto
川队。
為了完成 Rspec 的安裝,在 gem 的根目錄下運行下面的命令:
$ rspec --init
輸出的結果如下:
create spec/spec_helper.rb
create .rspec
一個 spec/
目錄在我們的項目中被創(chuàng)建睬澡,并且里面有一個 spec_helper.rb
文件固额。
我們需要在 spec_helper
中加點東西。因為我們想要測試我們的 gem 的代碼煞聪,我們需要從 spec_helper.rb
中加載它: 在 spec/spec_helper.rb
文件的頭部加上 require "mega_lotto"
require "mega_lotto"
# This file was generated by the `rspec --init` command. Conventionally, all
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
# Require this file using `require "spec_helper"` to ensure that it is only
# loaded once.
#
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
RSpec.configure do |config|
config.treat_symbols_as_metadata_keys_with_true_values = true
config.run_all_when_everything_filtered = true
config.filter_run :focus
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# the seed, which is printed after each run.
# --seed 1234
config.order = 'random'
end
注意:不同版本的 rspec
在 spec_helper.rb
中生產的內容也可能不同斗躏。如果你使用了不同版本的 rspec
,你的 spec_helper.rb
文件可能看上去不一樣昔脯。
現(xiàn)在啄糙,我們可以在命令行運行 rspec spec
然后得到下面的信息:
No examples found.
Finished in 0.00007 seconds
0 examples, 0 failures
注意:rspec
是用來運行 rspec
測試的命令,不內涵云稚。它接受文件路徑作為參數(文件或目錄)來確定要執(zhí)行哪個測試隧饼。這確保了我們的測試基礎設施被正確的設置了。
通常來說静陈,spec/
目錄(當使用 rspec
時我們的測試被安置的地方)燕雁,是我們的 gem 的 lib/
目錄的鏡像。我們之后會看到這是如何工作的, 假如模塊 lib/meag_lotto/drawing.rb
被加入到我們的 gem 中, spec/mega_lotto/drawing_spec.rb
將是相符的 spec 文件.
正如我們上面看到的窿给,gem 可以有依賴贵白。有時候當你安裝一個 gem 時率拒,幾個其他的 gem 也會被安裝崩泡。這是因為被定義在 gemspec
里的依賴。如果你以前在一個 rails 應用中使用 unicorn
gem猬膨,你可能注意到安裝 unicorn
導致 gemfile.lock
中多了幾行角撞。這幾行就是在 unicorn
的 gemspec
中定義的依賴呛伴。
Rake 任務
為了運行通過 rspec spec/
命令來執(zhí)行我們的 specs,我們可以更新我們的 Rakefile
來包含一個 spec
任務并且設置為默認:
require "bundler/gem_tasks"
require "rspec/core/rake_task"
RSpec::Core::RakeTask.new(:spec)
task :default => :spec
現(xiàn)在我們可以使用我們的終端來運行 rake
來看看和上面一樣的輸出:
$ rake
No examples found.
Finished in 0.00007 seconds
0 examples, 0 failures
我們要加入的另一個任務是一個快捷鍵來進入一個終端會話谒所。如果你熟悉 Rails热康,你應該知道 rails c
是一個很牛逼的工具。我們可以給我們的 gem 類似的功能劣领。如果我們的系統(tǒng)中有 Ruby姐军,我們可以打開一個命令行使用 irb
命令來進入一個 Ruby 交互環(huán)境:
$ irb
irb(main):001:0> 2 + 2 => 4
irb(main):002:0> exit
非常好,只不過解釋器沒有加載任何 Ruby 標準庫以外的東西尖淘。這對我們沒什么幫助奕锌,但是幸運的是 irb
命令接受的一些參數可以幫我們一些忙:
$ irb --help
Usage: irb.rb [options] [programfile] [arguments]
-f Suppress read of ~/.irbrc
-m Bc mode (load mathn, fraction or matrix are available)
-d Set $DEBUG to true (same as `ruby -d')
-r load-module Same as `ruby -r'
-I path Specify $LOAD_PATH directory
-U Same as `ruby -U`
-E enc Same as `ruby -E`
-w Same as `ruby -w`
-W[level=2] Same as `ruby -W`
-I
參數允許我們加入一個特定的目錄到 Ruby 的 load path。
記得嗎當我們討論 lib/mega_lotto
目錄時看到的 mega_lotto.gemspec
文件的頭部
...
lib = File.expand_path('../lib', ___FILE___)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
...
當 bundler 加載我們的 gem 時村生,它也加載了 lib/
目錄, 所以我們可以使用我們的庫惊暴。
所以通過使用 -I
,我們可以指定 lib/
目錄來保證 irb
可以使用我們的代碼趁桃。
另外辽话,在通常狀況下,我們看到 irb
不會加載 Ruby 標準庫以外的庫卫病。所以即使我們加上了我們的 lib/
目錄到 load path油啤,我們不得不指定調用 require "mega_lotto"
來加載我們的代碼當會話開始時。所以忽肛,選項 -r
被我們使用了村砂。它允許我們當會話開始時去加載一個指定庫,所以我們不用手動去做這件事了屹逛。
把這些參數組合起來我們就得到了一個牛逼的工具來檢驗我們的 gem 的代碼:
$ irb -r mega_lotto -I ./lib
irb(main):001> MegaLotto
=> MegaLotto
我們可以看到 MegaLotto
模塊在使用合適的參數的irb會話中被使用了础废。
更進一步,我們可以把這行命令加入到我們的 Rakefile
罕模,這樣我們就能更容易的調用了评腺。
require "bundler/gem_tasks"
require "rspec/core/rake_task"
RSpec::Core::RakeTask.new("spec")
task :default => :spec
task :console do
exec "irb -r mega_lotto -I ./lib"
end
這讓我們可以快捷的從命令行運行 rake console
$ rake console
irb(main):001:0> MegaLotto
=> MegaLotto
調試
無論我們怎么不愿意承認,我們沒有寫出完美的代碼淑掌。Ruby 有很多調試工具蒿讥,但是 pry
是我的選擇。
既然 mega_lotto.gemspec
負責根據環(huán)境加載依賴抛腕,我們可以加入 pry
到開發(fā)列表中:
...
spec.add_development_dependency "bundler", "~> 1.3"
spec.add_development_dependency "rake"
spec.add_development_dependency "rspec"
spec.add_development_dependency "pry"
運行 bundle install
我們可以看到 pry
gem 被列出來了:
Resolving dependencies...
Using rake (10.1.0)
Using bundler (1.3.5)
Using coderay (1.1.0)
Using diff-lcs (1.2.5)
Using mega_lotto (0.0.2) from source at . Using method_source (0.8.2)
Using slop (3.4.7)
Using pry (0.9.12.3)
Using rspec-core (2.14.7)
Using rspec-expectations (2.14.4)
Using rspec-mocks (2.14.4)
Using rspec (2.14.1)
Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.
注意:pry
gem 已經安裝在我的系統(tǒng)中了芋绸,所以在輸出中有 "Using pry (...)". 如果 pry
之前沒有被安裝,輸出應該是 "Installing pry(...)"
現(xiàn)在 pry
安裝好了担敌,我們需要去加載它摔敛。正如前面所提到的,lib/mega_lotto.rb
文件是加載其他代碼到 gem 里的主要入口全封。
通常情況下马昙,我們可以在頭部引入 pry
桃犬。然而,記住我們只在開發(fā)環(huán)境下使用 pry
行楞。這意味著當我們的 gem 被宿主應用加載時攒暇,它會嘗試去加載 pry
,有可能會因為沒有 pry
而拋出 Ruby LoadError
異常子房。
知道了這個可能發(fā)生的異常形用,我們可以使用 rescue 然后不做任何處理:
# lib/mega_lotto.rb
require "mega_lotto/version"
begin
require "pry"
rescue LoadError
end
module MegaLotto
end
注意我們是如何使用 rescue LoadError
代碼塊來捕獲異常的,但是現(xiàn)在我們不做任何處理证杭。 如果不這樣做尾序,LoadError
異常就會被拋出然后我們的代碼就不能被執(zhí)行下去了。
一旦 pry
被加載躯砰,我們就可以使用 binding.pry
方法來停止代碼在那個點上并且開啟一個 REPL 會話來調試每币。讓我們在模塊里加入:
require "mega_lotto/version"
begin
require "pry"
rescue LoadError
end
module MegaLotto
binding.pry
end
$ rake console
Frame number: 0/7
From: /Users/bhilkert/Dropbox/code/mega_lotto/lib/mega_lotto.rb @ line 12 :
7: require "pry"
8: rescue LoadError
9: end
10:
11: module MegaLotto
=> 12: binding.pry
13: end
[1] pry(MegaLotto)>
完美!
注意: 使用 exit
命令來退出 pry
會話琢歇。
我們就不繼續(xù)下去了兰怠,我們會從入口文件移除 binding.pry
,但是保留加載的代碼這樣我們以后還可以使用:
require "mega_lotto/version"
begin
require "pry"
rescue LoadError
end
module MegaLotto
end
使用 pry
的詳細內容不是本書的范圍李茫。這是一個牛逼的 gem 并且值得去探索如果你經常使用 Ruby揭保。
我們就到此為止了,因為我們的測試框架和調試工具都已經被正確的安裝和配置了魄宏。
總結
我們在本章花費了時間來配置很多工具秸侣。它們并不都是必要的,但是在我的開發(fā)過程中我發(fā)現(xiàn)了它們的價值宠互。一旦你多試幾次這個過程味榛,就只需要花上幾分鐘就能完成。有了這些工具能幫助你解決一些很麻煩的 bug予跌。
如果你更喜歡 minitest
而不是 rspec
搏色,配置起來會容易些因為 minitest
已經內置在 Ruby 標準庫中了,所以不需要額外的 gem券册。
最后频轿,gem 的依賴會很快的變得復雜。如果一個依賴的定義沒有被維護烁焙,就可能給你留下很多 bug 和坑航邢。當然,依賴是有價值的骄蝇,沒有理由去復制功能如果已經有了可靠的解決方案膳殷。只要知道,隨著你增加依賴乞榨,你的 gem 的復雜度就會上升秽之。
在下一章,我們會使用測試驅動的方式來研究和探索 Ruby 的命名空間是如何管理 gem 的文件結構的吃既。