引き続きgolangのtestingパッケージをRubyに翻訳したRgotを作っていて、 Testing、Benchmarkは実装したので、Example機能を実装してみた。
機能はgolangのtesting packageにあるExampleとおなじを目指した。
Example機能が何かというと、プログラムのサンプルコードを書いたときに、 「ここの時点ではこう出力されるよね」みたいなことをよくコメントで書くと思う。 Example機能は、Exampleを提示しつつ、このコメントが本当にあっているのかついでにテストしてくれる機能だ。
例えばRubyのArray#shiftのサンプルコードを書きたいとすると、
module FooTest def example_Array_shift a = [1, 2, 3] p a.shift p a.shift(2) p a.shift # Output: # 1 # [2,3] # nil end end
このように書けばよい。 これでもし、Kernel#pやArray#shiftの仕様が変わって出力が変わったとしても、テストが落ちてすぐ気づけるという仕組み。
例えば先程のコードは、配列の出力が微妙に違う(スペースの有無)のでテストが落ちる。
$ rgot foo_test.rb === RUN example_Array_shift got: 1 [2, 3] nil want: 1 [2,3] nil FAIL
これをRuby実装するためには、どのメソッド内でどのコメントが書かれているか解析しなければならない。
そこで今回はRipperを使ってみた。
RipperはRubyに標準添付されているRubyのコードを解析するライブラリ。
RipperはCRubyの構文解析器のparse.yに直接埋め込まれているのでこれ以上正確なRubyパーサーはないだろう。
require 'ripper' class Parser < Ripper attr_accessor :output def initialize(code) super @output = "" end def on_comment(comment) @output << comment end def on_kw(key) @output << key end end parser = Parser.new(code) parser.parse parser.output
このようにRipper classを継承してon_xxxとイベントドリブンな書き方でRubyのコードを解析できる。
今回はメソッドを定義したときにメソッド名が取得できるon_def
と、コメントを見つけた時取得できるon_comment
と、予約語を見つけたとき取得できるon_kw
を使った。
on_kw
でdef
キーワードを見つけたらコメント取得開始、on_comment
でコメントをためて、on_def
でメソッドの定義が完了したらメソッド名と関連付ける。
さらに、Module#instance_method
で取得しておいたUnboundMethodオブジェクトからメソッドが定義されているファイル名を取り出して、
ファイルを丸ごと読み込んで、先ほどのRipperにかけて、コメントを取得しておいて、UnboundMethodからメソッドを実行させて出力をStringIOでキャプチャーして比較すれば出来上がり。
詳しくはコードでどうぞ。
もしくは、
$ gem install rgot
で、すぐに試せるようになっています。
Example機能はv0.0.3から。
本当はgodocみたいに、rdocにExampleを表示させてとかできればいいんだけど、 それは今後の課題ということで。