构建一个 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 的文件结构的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容