Crystalのテスト周りについての覚え書き

Crystalでテストどうやって書くんだろうと調べたメモ。https://github.com/veelenga/awesome-crystal にいろいろ載っていたのでいくつか試した。
テスト機能としては、言語組み込みでrspecっぽい構文のspecが使える。よりrspecに近い機能を追加してある外部ライブラリのspec2もある。
これにPowerAssertを組み合わせる感じか。
MiniTestもあるけど試してない。

組み込みのSpec

Rubyのrspecのような仕組みが言語に組み込まれていて、「crystal spec」で実行できる。
http://crystal-lang.org/api/Spec.html を参照。
rspecと比べるとcontextやletがなかったり、before/afterもSpec.before_each、Spec.after_eachと書く必要があったりとRubyの感覚で書こうとして結構ハマった。

Crystalの場合

サンプルとしてspec.cr を作成。shouldを使う。

[ruby]
require "spec"

describe "Foo" do
Spec.before_each do
puts "Foo before_each"
end

Spec.after_each do
puts "Foo after_each"
end

describe "Bar" do
Spec.before_each do
puts "Foo::Bar before_each"
end

Spec.after_each do
puts "Foo::Bar after_each"
end

it "hoge" do
puts "hoge"
"hoge".should eq "hoge"
end

it "huga" do
puts "huga"
"huga".should eq "piyo"
end
end
end
[/ruby]

実行結果は以下の通り。

[text]
$ crystal spec spec.cr
Foo before_each
Foo::Bar before_each
hoge
.Foo after_each
Foo::Bar after_each
Foo before_each
Foo::Bar before_each
huga
FFoo after_each
Foo::Bar after_each

Failures:

1) Foo Bar huga
Failure/Error: "huga".should eq "piyo"

expected: "piyo"
got: "huga"

# ./spec.cr:28

Finished in 0.49 milliseconds
2 examples, 1 failures, 0 errors, 0 pending

Failed examples:

crystal spec ./spec.cr:26 # Foo Bar huga
[/text]

ネストの浅い箇所で定義したbefore_each/after_eachから順に実行されている。
rspecの場合はbeforeは浅い順に、afterは深い順に実行されるので最初混乱してしまった。
バグだろうか?

参考:Rubyの場合

上で書いたCrystalのspec相当のコードは以下のようになる。

[ruby]
require ‘rspec’

describe "Foo" do
before(:each) do
puts "Foo before_each"
end

after(:each) do
puts "Foo after_each"
end

describe "Bar" do
before(:each) do
puts "Foo::Bar before_each"
end

after(:each) do
puts "Foo::Bar after_each"
end

it "hoge" do
puts "hoge"
expect("hoge").to eq "hoge"
end

it "huga" do
expect("huga").to eq "piyo"
end
end
end
[/ruby]

実行結果。
beforeは浅いところから、afterは深いところから順に実行されている。

[text]
$ rspec ./spec.rb
Foo before_each
Foo::Bar before_each
hoge
Foo::Bar after_each
Foo after_each
.Foo before_each
Foo::Bar before_each
Foo::Bar after_each
Foo after_each
F

Failures:

1) Foo Bar huga
Failure/Error: expect("huga").to eq "piyo"

expected: "piyo"
got: "huga"

(compared using ==)
# ./spec.rb:27:in `block (3 levels) in <top (required)>’

Finished in 0.01156 seconds (files took 0.06919 seconds to load)
2 examples, 1 failure

Failed examples:

rspec ./spec.rb:26 # Foo Bar huga
[/text]

spec2

外部ライブラリ。よりrspecに近づいていて、なじみのある雰囲気。
ドキュメントは https://github.com/waterlink/spec2.cr を参照。
上で書いたspec.crをspec2で書き直すとこうなる。GlobalDSLをincludeすることでいい感じの見た目になってよい。letやsubject、expect等も使えるようになる。

[ruby]
require "spec2"
include Spec2::GlobalDSL

describe "Foo" do
before do
puts "Foo before_each"
end

after do
puts "Foo after_each"
end

context "Bar" do
before do
puts "Foo::Bar before_each"
end

after do
puts "Foo::Bar after_each"
end

it "hoge" do
puts "hoge"
expect("hoge").to eq "hoge"
end

it "huga" do
expect("huga").to eq "piyo"
end
end
end
[/ruby]

実行結果。afterの実行順序はspecと同様(rspecと逆)のようだ。

[text]
$ crystal spec spec2.cr
Foo before_each
Foo::Bar before_each
hoge
Foo after_each
Foo::Bar after_each
.Foo before_each
Foo::Bar before_each
F

In example: Foo Bar huga
Failure: Expected to be equal:
Expected: "piyo"
Actual: "huga"

[4407663170] *CallStack::unwind:Array(Pointer(Void)) +82
[4407663073] *CallStack#initialize<CallStack>:Array(Pointer(Void)) +17
[4407663032] *CallStack::new:CallStack +40
[4407776169] *Spec2::ExpectationNotMet@Exception#initialize<Spec2::ExpectationNotMet, String, Nil>:CallStack +41
[4407776092] *Spec2::ExpectationNotMet::new<String>:Spec2::ExpectationNotMet +108
[4407773796] *Spec2::Expectation(String)@Spec2::Expectation(T)#to<Spec2::Expectation(String), Spec2::Matchers::Eq>:Nil +68
[4407771919] *Spec2__Foo::Spec2__Bar::Spec2__Huga#run<Spec2__Foo::Spec2__Bar::Spec2__Huga>:Array(( -> Void)) +95
[4407769475] *Spec2::Runners::Default#run_context<Spec2::Runners::Default, Spec2::Reporters::Default, Spec2::Orders::Default, Spec2::Context>:Array(Spec2::Context) +355
[4407769950] *Spec2::Runners::Default#run_context<Spec2::Runners::Default, Spec2::Reporters::Default, Spec2::Orders::Default, Spec2::Context>:Array(Spec2::Context) +830
[4407769950] *Spec2::Runners::Default#run_context<Spec2::Runners::Default, Spec2::Reporters::Default, Spec2::Orders::Default, Spec2::Context>:Array(Spec2::Context) +830
[4407764660] *Spec2::HighRunner#run<Spec2::HighRunner>:Int32? +388
[4407765765] *Spec2@Spec2::run:Int32? +21
[4407652288] ~proc(Int32 -> Void)@./libs/spec2/spec2.cr:62 +16
[4407753648] *AtExitHandlers::run<Int32>:Array((Int32 -> Void))? +320
[4407652154] main +74

Finished in 1.16 milliseconds
Examples: 2, failures: 1
[/text]