構建一個 Ruby Gem 第三章 配置 測試/調試 環境

配置 測試/調試 環境

一本沒有測試相關內容的 Ruby 的書不是完整的。如果你對發布和貢獻開源項目感興趣的話,社區會更嚴肅的對待你的代碼如果它們是被測試覆蓋的并且測試通過的話。

測試驅動開發(TDD) 是一種實戰,在你寫代碼之前先寫測試。實踐 TDD 幫助我們只寫必要的能讓測試通過的代碼。這也能減少過度工程的可能性(注意到這里有一個模式了嗎?)

在 ??Ruby 測試社區有兩種觀點。一種喜歡 Minitest (Ruby標準庫內置的), 另一個更喜歡 Rspec。我喜歡后者并且每天都使用 Rspec。我發現它很適合我而且我喜歡用 DSL 來組織我的測試。

依賴


為了加入 Rspec,讓我們打開 mega_lotto.gemspec 文件然后加入下面的依賴:

 spec.add_development_dependency "rspec"

注意: 由于我們不想強制讓 rspec 被我們的宿主應用加載,我們使用 add_development_dependency 方法,而不是 add_dependency

現在, 讓我們切換到終端,在我們的 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 安裝了 rakerspec(由多個 gem 組成),和我們的 gem, mega_lotto
為了完成 Rspec 的安裝,在 gem 的根目錄下運行下面的命令:

$ rspec --init

輸出的結果如下:

create   spec/spec_helper.rb
create   .rspec

一個 spec/ 目錄在我們的項目中被創建,并且里面有一個 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

注意:不同版本的 rspecspec_helper.rb 中生產的內容也可能不同。如果你使用了不同版本的 rspec,你的 spec_helper.rb 文件可能看上去不一樣。

現在,我們可以在命令行運行 rspec spec 然后得到下面的信息:

No examples found.
Finished in 0.00007 seconds
0 examples, 0 failures

注意:rspec 是用來運行 rspec 測試的命令,不內涵。它接受文件路徑作為參數(文件或目錄)來確定要執行哪個測試。這確保了我們的測試基礎設施被正確的設置了。

通常來說,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 中多了幾行。這幾行就是在 unicorngemspec 中定義的依賴。

Rake 任務


為了運行通過 rspec spec/ 命令來執行我們的 specs,我們可以更新我們的 Rakefile 來包含一個 spec 任務并且設置為默認:

require "bundler/gem_tasks"
require "rspec/core/rake_task"
RSpec::Core::RakeTask.new(:spec)

task :default => :spec

現在我們可以使用我們的終端來運行 rake 來看看和上面一樣的輸出:

$ rake
No examples found.
Finished in 0.00007 seconds
0 examples, 0 failures

我們要加入的另一個任務是一個快捷鍵來進入一個終端會話。如果你熟悉 Rails,你應該知道 rails c 是一個很牛逼的工具。我們可以給我們的 gem 類似的功能。如果我們的系統中有 Ruby,我們可以打開一個命令行使用 irb 命令來進入一個 Ruby 交互環境:

$ 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 負責根據環境加載依賴,我們可以加入 pry 到開發列表中:

...
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 已經安裝在我的系統中了,所以在輸出中有 "Using pry (...)". 如果 pry 之前沒有被安裝,輸出應該是 "Installing pry(...)"

現在 pry 安裝好了,我們需要去加載它。正如前面所提到的,lib/mega_lotto.rb 文件是加載其他代碼到 gem 里的主要入口。

通常情況下,我們可以在頭部引入 pry。然而,記住我們只在開發環境下使用 pry。這意味著當我們的 gem 被宿主應用加載時,它會嘗試去加載 pry,有可能會因為沒有 pry 而拋出 Ruby LoadError 異常。

知道了這個可能發生的異常,我們可以使用 rescue 然后不做任何處理:

# lib/mega_lotto.rb

require "mega_lotto/version"

begin
  require "pry"
rescue LoadError
end

module MegaLotto
end

注意我們是如何使用 rescue LoadError 代碼塊來捕獲異常的,但是現在我們不做任何處理。 如果不這樣做,LoadError 異常就會被拋出然后我們的代碼就不能被執行下去了。

一旦 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 會話。

我們就不繼續下去了,我們會從入口文件移除 binding.pry,但是保留加載的代碼這樣我們以后還可以使用:

require "mega_lotto/version"

begin
  require "pry"
rescue LoadError 
end

module MegaLotto 
end

使用 pry 的詳細內容不是本書的范圍。這是一個牛逼的 gem 并且值得去探索如果你經常使用 Ruby。

我們就到此為止了,因為我們的測試框架和調試工具都已經被正確的安裝和配置了。

總結

我們在本章花費了時間來配置很多工具。它們并不都是必要的,但是在我的開發過程中我發現了它們的價值。一旦你多試幾次這個過程,就只需要花上幾分鐘就能完成。有了這些工具能幫助你解決一些很麻煩的 bug。

如果你更喜歡 minitest 而不是 rspec,配置起來會容易些因為 minitest 已經內置在 Ruby 標準庫中了,所以不需要額外的 gem。

最后,gem 的依賴會很快的變得復雜。如果一個依賴的定義沒有被維護,就可能給你留下很多 bug 和坑。當然,依賴是有價值的,沒有理由去復制功能如果已經有了可靠的解決方案。只要知道,隨著你增加依賴,你的 gem 的復雜度就會上升。

在下一章,我們會使用測試驅動的方式來研究和探索 Ruby 的命名空間是如何管理 gem 的文件結構的。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容