blog.yujinakayama.me

Previous pagePage 2 of 2

RSpecの最新の動向・RSpec 3へのアップグレードガイド

Dec 6th, 2013

この記事はRuby Advent Calendar 2013の6日目の記事です。

概要

Rubyのデファクトスタンダードなテストフレームワークと言えるRSpecですが、現在バージョン3.0のリリースへ向けて開発が進められており、先日2013年11月8日には3.0.0.beta1がリリースされました。

この記事ではRSpec 3における変更点と、3.0.0.beta1へのアップグレード手順、また既存のspecを最新の記法に変換するツールを紹介します。

追記

RSpec 3は2014年6月2日に正式リリースされました。この記事は2013年12月6日に書かれたものですが、正式版においても通用する内容になっています。

正式版における主要な変更点は、以下のページが参考になるでしょう。

RSpec 3での変更

RSpec 3は、2010年10月の2.0リリース以来3年ぶりのメジャーバージョンアップとなるため、多くのdeprecatedな機能が削除されます。

The Plan for RSpec 3

RSpec 3がどのようなものになるかは、RSpecプロジェクトのリードメンテナMyron Marston氏が今年2013年7月に記事を書いています。

その後、基本的にはこの計画通りに開発は進んでおり、3.0.0.beta1ではこのうちの一部が実装されています。具体的には、 What’s Being Removed で挙げられている項目は全て実装(というか削除)が完了しています。また What’s New の項目に関しては以下の3つが実装されています。

その後の変更

The Plan for RSpec 3はあくまでも7月時点での計画だったため、その後追加された変更があります。

be_true/be_falseマッチャのリネーム

be_true/be_falseマッチャが、be_truthy/be_falsey(またはbe_falsy)にリネームされ、3.0ではbe_true/be_falseは利用できなくなります。この変更は既に3.0.0.beta1に取り込まれています。

この理由ですが、

  • be_trueは、その名前にもかかわらず、テスト対象が真(false, nil以外)であればパスしており、trueとの同一性はテストされていなかった。
  • be_falseも同様に、テスト対象が偽(false, nil)であればパスしており、falseとの同一性はテストされていなかった。

といったように、名前と挙動が一致していませんでした。

今回の変更は、これまでの挙動に名前を合わせた形になります。実際のところbe_true/be_falseはpredicate method(?で終わるメソッド)のテストに使われることが多く、それらは直接if文などの条件部に置かれることが多いため、この挙動が問題になるケースはあまりなかったように思います。

既存のspecをどうするべきかですが、この挙動を以前から知っていて敢えて使っていた、もしくは知らなかったけど特にそこまでの厳格さを求めない、ということであれば、be_truthy/be_falseyに書き換えれば良いでしょう。

一方、そんな挙動だったなんて知らなかった、これを機に厳格にテストしたい、という場合はbe true/be falseを利用することが推奨されています。これらは新しい記法ではなく、従来からあるbeマッチャとtrue/falseリテラルの組み合わせです。beequalマッチャのaliasであり、オブジェクトの同一性がテストされます。

rspec-mocksへの一部のexpect記法の追加

should記法の、ハッシュを引数に取ったstubと、stub_chainに対応するexpect記法が追加されます。

# should記法
obj.stub(:foo => 1, :bar => 2)
obj.stub_chain(:foo, :bar, :baz)

# expect記法
allow(obj).to receive_messages(:foo => 1, :bar => 2) # 3.0.0.beta1から利用可能
allow(obj).to receive_message_chain(:foo, :bar, :baz) # 現時点で未リリース、おそらく3.0.0.beta2から利用可能

2012年7月にRSpec 2.11で導入されたexpect記法ですが、この時点ではrspec-expectationsのみへの導入でした。その1年後の2013年7月にRSpec 2.14でrspec-mocksにもexpect記法が導入され、expect(obj).to receive(:message)allow(obj).to receive(:message)といった記述が可能になりました。この記法は2.11での導入時ほど話題にならなかったため、知らない方も割といるのではないでしょうか。

しかしRSpec 2.14時点でのrspec-mocksのexpect記法は、should記法すべてに対して代替手段が用意されている訳ではありませんでした。これは単なる実装漏れではなく、いわゆるcode smellがするため一旦導入を見送っていたなどの理由があったのですが、最終的には前述の2つについては代替記法が導入されることになります。ちなみにunstubは未だexpect記法は存在せず、RSpecコアチームの見解を見る限り、おそらく今後も導入されることは無いものと思われます。

ワンライナーshouldのexpect記法

ワンライナーshouldのexpect記法としてis_expected.toが追加されます。

it { should be_empty }
it { is_expected.to be_empty } # 現時点で未リリース、おそらく2.99.0.beta2から利用可能

expect記法が導入された経緯として、既存のshould記法はshould/should_receive/stubなどのメソッドをBasicObjectクラスにモンキーパッチで追加するため、delegate/proxyなオブジェクトで正常にテストができないという問題がありました。しかしワンライナーshouldBasicObjectクラスにモンキーパッチされたものではなく、その実体はRSpec::Core::ExampleGroup#shouldであり、前述の問題は発生しません。そのためワンライナーshouldはこれまでexpect記法が用意されておらず、RSpec.configureshould記法が無効化されていても利用することができました。

しかしコードの見た目上expect記法と混在させた時に一貫性がなかったり、「ワンライナーshouldのexpect記法は?」といったユーザからの声が多かったため、is_expected.toが代替記法として追加されます。

ちなみにモンキーパッチshouldはRSpec 3.0からdeprecated扱いになりますが(ただし明示的にshould記法を利用する宣言をすればdeprecation warningは表示されない)、ワンライナーshouldは3.0でも現役なため、is_expected.toが冗長だと感じる場合はワンライナーshouldを使い続けても問題ありません。

RSpec 3へのアップグレード

ここでは既存のプロジェクトをRSpec 3.0.0.beta1にアップグレードする際の手順を解説します。

ベータ版ではありますが、今のところ大きなバグもなく、deprecatedな機能が一掃されたバージョンなので、今のうちにアップグレードしておくと正式リリースの際にスムーズな移行ができます。アグレッシブな方はこの機会にアップグレードしてしまいましょう。

RSpec 2.99

RSpec 2.99は、バージョン2系との後方互換性を保ちつつ、3.0で非互換になる全ての機能に対してwarningを表示する、アップグレードの通過点となるバージョンです。

既存のspecをまずバージョン2.99で実行することで、そのspecを3.0に対応させるにあたって必要な変更を知ることができます。3.0のchangelog全ての項目に目を通して、どれが自分のプロジェクトに影響を与えるかをいちいち調べる必要はありません。

Transpec

しかし何を変更すれば良いかはわかっても、その書き換え作業は自分で行う必要があります。正直言ってこれはかなり面倒くさいし、世界中のRSpecユーザがそれぞれ正規表現のワンライナーなどでちまちま置換作業をするのは非合理的です。

という訳で、既存のspecを最新の記法に書き換えるツール、Transpecを作りました。Transpecを既存のプロジェクトに対して実行すると、静的解析と動的解析によってspecファイルを最新の記法に書き換えます。現時点で、RSpec 2.0から3.0にかけてdeprecatedになった大半の記法の変換をサポートしています。詳細はREADME - Supported Conversionsを参照して下さい。

プロジェクトによってはTranspecによる変換だけでRSpec 3対応が完了する場合もあるでしょうし、追加で手作業が必要な場合もごくわずかな書き換えで済むかと思います。また、TranspecはRSpecプロジェクトそのものでも利用されています。

手順

RSpec公式のアップグレードガイドがあるので、この手順に従います。

  1. 現状のままspecを実行し、all greenなことを確認します。
  2. Gemfilerspecのバージョンに~> 2.99を指定し、bundle update rspecを実行します。
  3. 再度specを実行し、all greenなことを確認します。RSpecはSemantic Versioning準拠で開発されているため、RSpec 2.0時代のspecであっても2.99で正常に動作するはずです。動作しないのであれば、それはRSpecのバグです。必要であればこの時点でコミットをします。
  4. spec実行後、deprecation warningが表示されるので内容を確認します。これらが3.0対応にあたって変更が必要な点になります。
  5. gem install transpecでTranspecをインストールします。これは日常的に使うツールではないので、通常はプロジェクトのGemfileに追加する必要はありません。
  6. プロジェクトのルートディレクトリでtranspecを実行します。Transpecはデフォルトで可能な限り最新の推奨された記法に変換しますが、必要であればTranspecのREADMEを参照し、コマンドラインオプションで変換の挙動をカスタマイズして下さい。例えばhave(n).itemsitsはRSpec 3.0で削除されますが、外部gem化されたrspec-collection_matchersrspec-itsを利用することで3.0以降も使い続けることができるため、もしその方針であればこれらの変換を--keep have_items,itsオプションで無効化できます。
  7. Transpecによる変換が完了したら、一旦コミットします(Transpecは自動的にコミットメッセージを生成するのでそれを利用すると良いでしょう)。
  8. 再度specを実行します。まだdeprecation warningが表示されるようであれば、手作業で対処します。all greenかつdeprecation warningがなくなったらコミットします。
  9. Gemfileで、rspecのバージョンに~> 3.0を指定し、bundle update rspecします。
  10. RSpec 3.0でspecを実行します。all greenなはずですが、もし失敗するのであればそれはRSpecのバグです。問題なければコミットします。
  11. RSpec 3.0から新たにdeprecated扱いになる機能もあり、場合によってはwarningが表示されるかもしれません。また、2.99の時点では代替となる記法が存在せず、3.0になってからでないと変換ができないものもあります(前述のreceive_messagesなど)。その場合は再度transpecを実行するとそれらを変換できます。
  12. 再度specを実行します。問題なければコミットします。
  13. 完了!

おわりに

本記事で解説したRSpec 3での変更は確定した訳ではなく、今後も新たな変更がある可能性があります。ちなみにGitHubのRSpecのマイルストーンを見る限り、3.0.0.beta2は2013年内のリリースを目指している模様です。

iTunes AirPlay Control for Alfred

Sep 14th, 2013

Alfred 2 から iTunes の AirPlay スピーカーを操作可能にする Workflow を作りました。

Screenshot

Workflow は Alfred の有償拡張パック Powerpack の一機能なため、Powerpack を購入しておく必要があります。

仕組み

AppleEvent(Open Scripting Architecture)を使って、iTunes から AirPlay スピーカー一覧を取得したり、設定変更をしています。実際には AppleEvent そのものが表に顔を出すことはほぼ無く、AppleEvent を記述するためのスクリプト言語 AppleScript について語られることが多いかと思います。

AppleScript で Mac のアプリケーションを操作するには、操作される側のアプリケーションがその操作のための API を提供している必要があります。「このアプリのこの操作は AppleScript に対応している」とか「AppleScript を使えばこのアプリのあの操作を実行できる」という表現の方が普通かもしれません。各アプリケーションが提供している AppleScript API は、AppleScript Editor(/Applications/Utilities/AppleScript Editor.app)にアプリケーションをドラッグ&ドロップすると閲覧できます。

今回の Alfred の Workflow は、AppleScript ではなく、Ruby から AppleEvent を触るための Scripting Bridge という仕組みを使っています。というのも、今回は iTunes だけでなく Alfred との入出力(ユーザからの入力文字列や処理結果のレスポンス XML 生成)も行う必要があり、その辺りは Ruby の方が断然楽だったので。

AirPlay スピーカーの切り替えって、iTunes ウインドウを表示して AirPlay ボタンをクリックしてスピーカーを選択、という手順ですごく面倒くさくて外部ツールによる切り替えをずっとしたかったんですが、AirPlay の操作はずっと AppleScript 非対応でした。iTunes バージョンアップの度に必死で対応チェックしていたくらい面倒でした。

が、今日久しぶりに Apple Script Editor で iTunes を開いてみたら AirPlay devices の記述があるじゃないですか。という訳で早速 Workflow を書きました。これで iTunes ライフが少し快適になりそうです。どのバージョンから対応したんだろう。

RuboCop 0.9.0

Jul 3rd, 2013

2013年7月1日、RuboCop 0.9.0 がリリースされました。といっても RuboCop をご存知ない方も多いかと思うので、まずは概要から。

RuboCop とは

RuboCop は Ruby のコーディングスタイルチェッカーです。Ruby コードを静的解析し、コーディングスタイル違反を検出します。Java で言う Checkstyle みたいなものですね。

RuboCop は、コーディングスタイルとして Ruby Style Guide を採用しています。むしろ、Ruby Style Guide の適用を押し進めるために RuboCop が作られたという方が正しいかもしれません。これは Bozhidar Batsov 氏が始めたコミュニティ主導のスタイルガイドプロジェクトで、GitHub 社内で使われている Ruby Styleguide のベースにもなっており、Rubyist ならご存知の方も多いでしょう。

Ruby のコードメトリックスツールとしては他に CaneReek などがありますが、これらはいわゆる「コードの臭い」を測定するものであり、コーディングスタイルを本格的にチェックするツールはこれまでなかったように思います。

RuboCop は、スタイル違反をチェックする cop(警官)達の集合体として構成されています。例えば、1行あたりの文字数をチェックする LineLength cop、メソッド名や変数名が snake_case になっているかチェックする MethodAndVariableSnakeCase cop、と言った形で、チェックする項目ごとに専任の cop が実装されています。

インストール

普通に gem コマンドでインストールできます。

$ gem install rubocop

実行

チェックしたい Ruby のソースファイルがあるディレクトリ内で rubocop コマンドを実行します。典型的にはプロジェクトルートディレクトリになるでしょう。

$ cd some-project
$ rubocop

この場合、カレントディレクトリ以下の、拡張子が .rb であるファイル、または拡張子が無く shebang(ファイル1行目の #! で始まる行)に ruby が含まれるファイルがチェック対象となります。

おそらく上記のコマンドを既存のプロジェクトに対して実行すると、大量のスタイル違反が検出されるかと思います。

設定

RuboCop の挙動は、設定を記述した .rubocop.yml ファイルを置くことで、ディレクトリ単位で変更することができます。

例えば .rubocop.yml に以下のような内容を記述すると、

Eval:
  Enabled: false

LineLength:
  Max: 100
  • Kernel.#eval メソッドの利用を警告する Eval cop を無効化
  • 1行あたりの最大文字数を100文字に設定(デフォルトは79文字)

といったカスタマイズができます。

既存のプロジェクトで RuboCop を使う場合、一旦最初は大半の cop を無効にしておき、一部 cop を有効化、違反箇所を修正、コミット、といった繰り返しで徐々にスタイルを適合させていくと良いのではないでしょうか。

バージョン 0.9.0 の新機能

さて、今回の 0.9.0 の新機能です。

Formatter

RSpec の formatter に似た仕組みを導入しました。-f/--format オプションで、結果の出力書式を切り替えられるようになっています。

これに伴い、標準の組み込み formatter もいくつか追加しています。

  • progress — RSpec の progress formatter ライクな、ドットによる進捗表示(デフォルト)
  • clang — 違反箇所を Clang の診断結果ライクに表示(後述)
  • json — 解析結果を JSON で出力

また、独自の formatter クラスを作成することで、カスタムフォーマッタを利用することも可能です。

違反箇所の詳細な表示

前述の clang formatter がこれにあたります。その名の通り Clang の診断結果 ライクに、違反箇所のハイライト表示をします。progress formatter も、解析完了後にサマリーとしてこの形式で表示をするようになっています。

features/support/helper.rb:6:3: W: Assigned but unused variable - stdin
  stdin, stdout, stderr, thread = Open3.popen3(*command)
  ^^^^^

Ruby の実装エンジン非依存の構文チェック

RuboCop はスタイルのチェックだけでなく、ruby -cw で警告されるような基本的な構文チェックも行います。例えば、利用していないローカル変数の検出なんかがこれにあたります。

バージョン 0.9.0 以前は、まさにそのまま ruby -cw コマンドを対象ファイル毎に実行してその出力をパースしていたのですが、この機能は MRI (CRuby) のみで有効で、JRuby や Rubinius ではスキップされていました(RuboCop は JRuby や Rubinius もサポートしています)。この理由として、JRuby は JVM 上で動くためプロセスの立ち上がりが非常に遅く実用に耐えないこと、また Rubinius は警告のバリエーションが MRI ほど充実していなかったことなどがあります。

RuboCop 0.9.0 では、MRI 2.0 で出力される警告のうち、一部をピュア Ruby で RuboCop 内に再実装し、依存 gem である Parser からの警告と合わせることで警告バリエーションの大半をカバーしており、JRuby や Rubinius 上でも同様に動作する構文チェックを実現しています。

違反コードの自動修正

これはまだ実験的な機能であり、ごく一部の cop でしかサポートされていませんが、-a/--auto-correct オプションによって違反箇所を自動的に修正することが可能となっています。

個人的にはこの機能の今後にはかなり期待しています。初めて RuboCop を使ってみて、大量の違反が検出された時点で修正する気をなくす人は多いでしょうし、チームでこういったスタイルチェッカーを導入する際にも「面倒くさい」といった心の声は確実に存在すると思います。

メソッドやクラスレベルのリファクタリングを要求するような違反(長過ぎるメソッドなど)の自動修正は難しいですが、書式の変換程度で済むような違反は、今後の実装でほぼ自動修正が可能になるのではないでしょうか。

Rails Cop

これも実験段階でまだ一つの cop しか実装されていませんが、Rails Style Guide をベースとした Rails 用 cop が追加されました。-R/--rails オプションを指定した場合のみ有効になります。

その他

多くの cop の追加やバグフィックスが含まれています。 詳細は Changelog をご覧下さい。

1.0 に向けて

次期バージョンの 1.0 では、前述の 0.9 で導入された機能のブラッシュアップを予定しています。

実は僕も少し前から RuboCop の開発に参加しています。 ファイル変更時に自動的に RuboCop を実行する Guard プラグイン、guard-rubocop も書いたので、Guard ファンの方は是非どうぞ。

あなたも RuboCop を使って、ナウでヤングな Rubyist になりませんか?