有頂天Ruby

ビールを飲みながらRubyについて書きます。

RSpec 3に向けての計画(日本語訳)

Myron Marston » The Plan for RSpec 3の微妙訳です。(翻訳最中なう)だいたい翻訳しました。訳がうんこなのは勘弁(ご指摘いただけると助かります)。

2013/7/23 21:25 id:kakutani さんのツッコミをもとに、誤訳等を修正しました。ありがとうございます(〃・ิ‿・ิ)ゞ

RSpec 3に向けての計画

RSpec 2.0は2010年10月にリリースされました。 リリースされてから今までの3年間、後方互換性を保ったままRSpecを継続的に改善してきました。 しかし、RSpecの2.xより古いリリースとの後方互換性を保つために残しているひどいコードの蓄積は限界点に達しています。 RSpec 2.14RSpec 2の最後のリリースになるでしょう(今後も多分bugfixのリリースすることはあるでしょう)。

我々はRSpec 3に取り掛かっています、私は、私たちがRSpecをどのような方向に進めていくかについて、考えを共有したいと思います。

もちろん、ここに書かれているものの中に、既に確定していて絶対に変えらないようなトピックはありません。 結局のところ皆さんが使ってくれていることがRSpecプロジェクトが成功している理由です。 我々がRSpec 3で取ろうとする方向について、あなたに何か考えがあるのなら、ぜひ発言してください!

何が取り除かれるか

Ruby 1.8.6と1.9.1のサポートが終了します

RSpec 2.xではMRIチームがサポートを終了してからも、長い間Ruby 1.8.6のサポートを続けてきました。 Rubyのテストインフラのエコシステムにおいて我々が重要だと感じていることは、gemの作者がいつ古いRubyのサポートをやめるか決められること、 そして、gem作者が使用することを選択したテストフレームワークが彼らがサポートするRubyのバージョンをサポートしなくなることによって彼らがgemのサポート終了を早めるのを強制されないことです。 Ruby 1.8.6と1.9.1はここ2年間Travisで利用できなくなっており、またCIサーバーのセイフティネットなしでは、我々がこれらのバージョン上でビルドを実行し、古いバージョンをサポートすることは非常に難しくなっています。

実際には我々はここ2年間これらのRubyバージョンを「セミサポート」しています。 もしもユーザーがこれらのRubyバージョンについての問題を報告した際には、修正はしますが、それ以上のサポートはしません。

それに、これらのバージョンのサポートを終了する時期に来ています。 我々はRSpec 3では1.8.7と1.9.2、それより新しいRubyのバージョンについて、継続的にサポートする計画です。 1.8.7が既にレガシーであることを考えると、我々はおそらくRSpec 4で1.8.7のサポートを終了するでしょう。 とはいえ、もしもTravisがそれ以前に1.8.7のサポートを終了した場合は、我々に出来るのは1.8.6と同様な「セミサポート」のみです。

Core: itsは外部gemに移動します

前にこれについて書いたので、ここでは長々と書きません。 我々はitsrspec-coreから、外部のgemへと移動させようと計画しています。

Expectations: have(x).itemsマッチャ―が外部gemに移動します

RSpecはCucumber/Gherkinが生まれる前に作られました、そして早期の目標の1つが、プロジェクトのステークホルダーが理解出来るような自然言語のように表現できることでした。 これらの初期の日々では、team.should have(9).playersのような表現がプロジェクトの目標を示していました。 その後、ステークホルダーにフォーカスしたテストとしてCucumber/Gherkinが現れ、今日ではRSpecステークホルダー向けのテストを書く目的で使われることは珍しくなりました。 have(x).itemsとして知られるマッチャ―(have_at_least(x).itemshave_at_most(x).itemsのような兄弟マッチャも含む)は、expect(team.players.size).to eq(9)のようにシンプルでうまく動く表現に比べると、必要以上に複雑です。

我々はこれらのマッチャをrspec-expectationsから、外部のgemに移動させようと計画しています。

Core: 明示的なデバッガーのサポートをやめます

RSpecは長い間ruby-debug gemによるデバッガーを有効にするための-d/--debugコマンドラインオプションをサポートしてきました。 しかし、今日ではruby-debugRubyデバッグするのに使うただ1つのgem(あるいはメインのgem)というわけではありません。 debuggerはMRI 1.9.2+でデファクトスタンダードになっています、そして多くの開発者がpryデバッグに好んで使うようになっています。他のRubyインタプリタ、たとえばRubniusは自身にデバッガの機能を持っています

我々はRSpec 3で明示的なデバッガーのサポートをやめ、取り除こうと計画しています。 コマンドラインオプションを取り除くのに加えて、我々はruby-debugがロードされていない際にKernerlにモンキーパッチするのをやめます。 そんでもって、debuggerがロードされてないのにdebuggerと書いているとNoMethodErrorが飛ぶようになるでしょう。 もしあなたが引き続きコマンドラインオプションを使ってdebuggerをロードしたいなら、require flag(-r)が使えます、-rdebuggerのように使います。

Core: rcovとの統合をやめます

RSpec::Core::RakeTaskは長い間rcov向けのオプションを持っていました。 RCovはMRI 1.8でのみ動作し、最近のRuby開発者のほとんどはコードカバレッジが必要なときはSimpleCovを使うようになっています。 SimpleCovは非常にシンプルで、RSpec自身からの明示的なサポートなしで、RSpec(あるいは他のフレームワーク)と一緒にうまく動きます。

Core: Autotestとの統合は外部のgemに移動します

Autotestは主に継続的にRubyのテストを実行するために使われています。 今日では、guardがより人気になったので、RSpecがAutoTestとの統合をrspec-coreの中で行う理由はありません。

Core: TextMateフォーマッターがTextMateのbundleに移動する

長い年月、TextMateRuby開発者が使うもっとも人気のあるエディタでした。 RSpecTextMate向けのformatterを長い年月もっていましたが、今日ではTextMateはRubyの開発者の間で人気のエディタだとは言えなくなっておりTextMateフォーマッターをrspec-coreにいれておく説得力のある理由がありません。

大量のDeprecations

RSpec 2.14はdeprecatedになって2年以上たつようなものを大量に含んでいました。 我々はdeprecatedになったほぼすべてのAPIを取り除く計画です。

古いexpectation/mock記法についてはどうなるの?

RSpec 2.11では新しいexpectベースの記法がrspec-expectationsに入りました。 RSpec 2.14では、我々はrspec-mockをアップデートし、似たような記法を使うようにしました。 新しい記法を紹介してから、私は「どれぐらい近いうちにshouldベースの古い記法をdeprecatingにするの?/とりのぞくの?」という質問を受けました。 私は「絶対に取り除くことはない」とは言いませんが(未来のことは誰にもわかりません), 今のところ我々に古い記法を取り除く計画はありません。

ユーザーは長年にわたり、古い記法でコードを書いてきました。私たちは(新規プロジェクトなら尚のこと)新しい記法を使うことを推奨していますが、かといって今すぐ古い記法を取り除いてしまうことはユーザーにとって非常に不親切です。それに、古い記法のサポートのメンテナンスはものすごく大変というわけでもありません。

RSpec 3では、我々は古い記法をデフォルトで無効にし、ユーザーがオプトインでそれを使うよう強制することを検討しました。 しかし、私はそうするのは新しくRSpecを使うユーザーにとって、不親切になると考えました。

初めてRSpecを試してみる人たちにとっては、チュートリアルからコピーしてきたコードでNoMethodErrorがでるのは非常にがっかりすることになるでしょう。 経験豊かな人はかんたんに古い記法を無効にできるのに対して、新人がチュートリアルにある古い記法を有効にして使うためのRSpecの知識を十分に持っているとは言いがたいです。

とは言うものの、我々は人々が新しい記法にスイッチするのを奨めたいので、我々はRSpec 3で、should記法を明示的に有効にせずに古い記法のメソッド(should, should_not, should_receive, etc...)を使うと警告メッセージを出すように計画しています。

これは、新規ユーザーへの優しさを保ちながら、新しい記法に向かって人々を前進させ、RSpec 4で古い記法を無効にするのをデフォルトにするための道を開くでしょう。

新機能について

ゼロモンキーパッチモード!

歴史的に、RSpecは読みやすい記法を作るため、広範囲にわたってモンキーパッチを行って来ました、describeshared_examples_forshared_contextshouldshould_notshould_receiveshould_not_receive、そして全てのオブジェクトで使えるstubなどです。

2.x系のリリースの最後のいくつかで、我々はRSpecによるモンキーパッチングの量を減らすように働いてきました:

  • rspec-core 2.11では、describeはすべてのオブジェクトに追加されなくなりました、代わりに、トップレベルのmainオブジェクトとModuleに追加されるようになりました(つまり全てのclassとmodule内で使用可能です)。
  • rspec-expectation 2.11では、我々はexpect記法を追加しshould記法を無効にするための設定オプションを提供しました 。これはshouldshould_notを全てのオブジェクトから取り除くものです。

  • rspec-core 2.12では、shared_examples_forshared_contextは、全てのオブジェクトに追加されなくなりました。そしてdescribeのようにそれらはトップレベルのmainオブジェクトとModuleにのみ追加されるようになりました。

  • rspec-mocks 2.14では、我々はrspec-mocksがexpectベースの記法をサポートするようアップデートしました、そしてmockするための古い記法を無効にするオプションを提供しました。これはshould_receiveshould_not_receivestubをすべてのオブジェクトから取り除くものです。

上述したように、3.0で我々はRSpecのモンキーパッチをあてたKernel#debuggerを削除します。 また、我々はmainオブジェクトとModuleのトップレベルDSLメソッド(describeshared_examples_for、などなど)を取り除く設定オプションを提供することを計画しており、代わりにこれらのメソッドを呼ぶときにRSpec.のプレフィックスをつけることを求めます

RSpec.describe MyClass do
  # exampleグループの中ではまだ`describe`を使用することが可能です:
  describe "#some_method" do
  end

  # `shared_examples_for`を使うことも可能です
  shared_examples_for "something" do
  end
end

RSpec.shared_examples_for "some behavior" do
end

最終的には3つの設定オプションにより決まります(1つはrspec-expectations、1つはrspec-mocks、そして1つはrspec-core)、それはRSpecにゼロモンキーパッチモードを提供します(我々はこれら3つを一括して設定出来るオプションを提供する予定です)。 我々はこれら3つのオプションをRSpec 4.0でデフォルトにする予定で、RSpec 4.0は何もしなくてもゼロモンキーパッチモードで使えるようになります。

Mocks: テストダブルのインターフェースの検証

残念ながら、テストダブルが置き換えているインターフェースと実際のインターフェースはかんたんに噛み合わなくなります。

たとえばあなたがメソッドをリネームしたり、期待される引数の数をかえたとき、変更したクラスのテストダブルを更新するのはかんたんに忘れがちです。

私は長い間、この問題を解決するrspec-fireのファンでした。 そこで私はrspec-mocksにそれをポートすることを計画しています。

詳しくは、githubのissueでのディスカッションを見て下さい。(これらの機能のAPIとセマンティックはまだ固まっていません、githubのチケット上でぜひあなたの考えを聞かせてください!)

Expectations: 完全に組み合わせ可能なマッチャ―

RSpec 2.13では、我々はincludeマッチャーがマッチャ―の配列を受け入れるようなサポートを追加しました。 このような組み合わせ方はとても使えるので、我々はこれをRSpec3のすべてのマッチャ―で出来るようにしようと計画しています。 例えばあなたはこんな感じの構文を使うことができます:

expect { |b|
  some_object.do_something(&b)
}.to yield_with_args(include(match(/foo/), match(/bar/)))

何を期待するかを詳細に表現しています: 「私はsome_objectがdo_somethingしたとき/foo/と/bar/にマッチする文字列を含んだ配列をyieldすることを期待する。」

我々はまた、このスタイルでマッチャ―を組み合わせたときにより読みやすいように、マッチャ―のエイリアスを追加することを検討しています。 そんで、あなたはこのように書けるでしょう:

expect { |b|
  some_object.do_something(&b)
}.to yield_with_args(a_collection_including(a_string_matching(/foo/),
                                            a_string_matching(/bar/)))

より詳細な情報についてはGitHubのissueを見てください。

Make all matchers composable for 3.0 · Issue #280 · rspec/rspec-expectations

Core: FormatterのAPI改善

formatterにテストの状態を通知するための現在のAPIは、新しく通知を追加したり、既存の通知を変更する際に、ちょっとばかり柔軟性に欠けることが分かっています。

我々はこれを2つの方法で変えようとしています:

  • formatterにすべての通知メソッドを実装することを要求するよりも、formatterが特定の通知だけをサブスクライブする方法。これであなたはformatterで必要なほんのちょっとの通知メソッドだけ実装すればよくなります、そして既にあるformatterを壊す心配なしに、新しい通知を追加することが出来るようになります。
  • 通知の引数は順番にならんだ引数列から、1つの値オブジェクトに変わります。 メソッドのシグネチャを変更せずに、追加のデータを特定の通知に加えることが可能になります。

これらの変更により2.xのリリースで出来なかったさらなる改善が出来るようになります。 我々は古いAPIで書かれたformatterをラップして互換性をもたせる層をRSpec3で提供する予定なので、ユーザーが古いformatterを使っていても、かんたんにアップグレードすることが出来ます。

詳細についてはGitHubのissueを見て下さい。

Core: DSLメソッドがexampleをyieldするようになる

RSpec 2では現在実行中のexampleをexampleで見ることが出来ます。 これはexampleのメタデータにアクセスするために使うことができます。

これはときどき、ユーザーがうっかり独自のexampleメソッドを定義した際に、問題を引き起こします

RSpec 3ではexampleメソッドを削除し、DSLメソッドにyieldでexampleを渡します:

describe MyClass do
  before(:each) { |example| }
  subject       { |ex| }
  let(:user)    { |ex| User.find(ex.metadata[:user_id]) }

  # ※before(:all) はexampleをyieldしません

  it "現在のexampleにブロックローカル変数でアクセス出来る" do |example|
    # do something with `example`
  end
end

我々は、この変更によりexample APIを使うgem(例えばCapybaraのこと)に頼っているユーザーが、アップグレードに頭を抱えることを承知しています。 我々はアップグレードをスムーズにする方法について、gemの作者とともに議論しています。 詳しくはGitHub issueをみてください。

Expectations: マッチャ―のプロトコルと、カスタムマッチャ―APIの変更

RSpecはshouldベースの記法からやり方を変えていってますが、その一方でマッチャープロトコルとカスタムマッチャーのAPIはその変更に追従していませんでした。

マッチャ―のプロトコルは未だにfailure_message_for_shouldfailure_message_for_should_notに依存しており、カスタムマッチャ―APImatch_for_shouldmatch_for_should_notのようなメソッドに依存しています。

RSpec 3では、マッチャ―プロトコルとカスタムマッチャ―APIを変更し、shouldが使われなくなるまでの間、既存のマッチャ―がうまく動くよう後方互換性のレイヤーを残しておき、RSpec4でそれらの互換性を取り除こうと計画しています。 我々は新しいAPIがどうなるべきか未だ確証を得ていません。もし考えがあったら、GitHub issueで知らせて下さい。

Mocks: any_instanceがブロック実装にreceiverをyieldするようになる

any_instanceを使ってメソッドをスタブするとき、通常のメソッドスタブと同様に、実装をブロックで渡すことが出来ます。

一方、あなたがブロックの中からreceiverにアクセスしたいとき(つまりインスタンスからメッセージを受け取りたいとき)、それをする方法はありません。

RSpec 3では、これらを直し、receiverをブロックの第一引数として渡します:

allow_any_instance_of(User).to receive(:age) do |user|
  ((Date.today - user.birthdate) / 365).floor
end

後方互換性のために、我々はこの挙動を無効にする設定オプションを追加する予定です。

アップグレードのみちすじ

RSpec 3.0は、2010年以降はじめて意図的に破壊的な変更を加えることが出来るメジャーリリースとなるにも関わらず、既存のテストスイートが可能な限り楽にアップグレード出来るようなアップグレードのみちすじをたてることが我々にとって重要です。

移行を終わらせるために、我々は純粋にユーザーのアップグレードを助けるための2.99をリリースすることを計画しています。

これが我々が思い描いていることです:

  • RSpec 2.99はRSpec 2.14に3.0で取り除かれることについていくつかのdeprecation warningを加えたものになります。
  • RSpec 2.xで書かれた既存のテストコード一式は何の変更も加えずに2.99にアップグレードできるので、2.99であなたのテストコード一式を実行してください。
  • RSpec 2.99は、3.0で取り除かれたり壊れてしまう変更点の警告を出しますから、この警告に対応してください。2.99で警告が出なくなったら、そこからは何も変更を加えることなく3.0にアップグレードできます。

2.99は重要なステップなので、3.0へのアップグレード手順としては省略しないでください。 それはあなたのテストがどのようにRSpecを使っているかに応じて、あなたに向けにあつらえられたアップグレードのチェックリストです、changelogをあさってRSpec 3で何が変わったか探そうとするよりも、あなたによりシンプルで効率的なアップグレード方法を提供してくれるでしょう。

開発とリリースの計画について

我々は既にそれぞれのmasterブランチでRSpec 3に向けての取り組みを開始しています。 我々はまた、2.14での変更用に2-14-maintenanceブランチ(パッチリリースを可能にするため)、2.99での変更に向けて2-99-maintenanceブランチをもっています。 我々は最終的な3.0のリリースに向けて複数のRelease Candidates版(といくつかのβ版)のリリースを計画しています。

RSpec3のリリースがいつ頃になるかを予想するような危険を冒すのはやめておきます。 経験からいうとリリース日のみつもりはたいてい間違っています :(

How can i help?

(訳者より: 手伝いたい方はきっと英語も読めるだろうと思うのでここは訳しませんლ(╹◡╹ლ)