【翻訳】RSpecのリードメンテナだけど何か質問ある?

  • 162
    Like
  • 0
    Comment

はじめに

先日、Redditでこんな記事が載っていました。

AMA: The authors of "Effective Testing with RSpec 3", Myron Marston and Ian Dees : ruby

この記事は書籍「Effective Testing with RSpec 3」の筆者であるMyron Marston氏とIan Dees氏が、書籍に関する質問に何でも答えます、という企画です。

この2人のうち、Myron Marston氏はRSpecの開発者(リードメンテナ)です。
Q&Aを読んでいると、RSpecの開発者ならではの意見だなと思うところがたくさんあり、なかなか興味深い議論になっていました。

というわけで、この記事では先ほどのQ&Aから「これは日本のRubyプログラマにも役立ちそう」と思ったやりとりをピックアップして翻訳してみます。

ピックアップしたQ&Aの一覧

この記事でピックアップしたのは以下の7つのやりとりです。

  • RSpecの中で必要最小限のDSLをおすすめするなら?
  • シンプルなスタイルと複雑なスタイル、選ぶならどっち?
  • RSpecのDSLは全員覚えるべき?
  • RSpecとMinitestをどう比較する?
  • 追加して後悔している機能は何?
  • power-assertってどう思う?
  • RSpecで今後チャレンジしたいことは?

備考

  • ここでピックアップしたのはすべてMyron Marston氏による回答です。
  • Myron Marston氏から翻訳の許可はもらっています。
  • 翻訳で怪しいところがあれば、コメントや編集リクエストでやさしく指摘してやってください。

ではここからがQ&Aの翻訳です!


Q. RSpecの中で必要最小限のDSLをおすすめするなら?

RSpecのDSLには数多くの選択肢があり、そのためにチームメンバーが一貫した構成とスタイルを維持しながらきれいなコードを書くことを困難にしています。
あなたがおすすめする何か必要最小限のDSLはありますか?

A. 何はともあれ、describeとit

あなたがRSpecのどんな側面を便利で有用だと思うのかによって答えは変わってきますが、一般論としてはこうなるでしょう。

  • describeit。本当に必要なのはこれだけです。それと、失敗を報告するために例外を発生させるexampleのいくつか。残りのRSpecのAPIは無視できますし、そうしても便利なCLIコマンドを活用することができます。

  • 私はネストしたコンテキスト(describecontextを使うコード)が、テストを整理するのに便利だと感じています。そして、それをよく使います。

  • expectとマッチャを組み合わせることでもたらされる、失敗時のメッセージとその柔軟性も便利だと思います。しかし、これは人によって意見が異なるかもしれません。もしシンプルなAPIがお好みなら、設定ファイルに1行加えるだけで(config.expect_with :minitest)Minitestのアサーションを使うことも可能です。

  • beforeletも特定の状況下では便利です。しかし、残念なことに現場では誤用/濫用されるケースもよくあります。十分なメリットがあるときにだけ、beforeletを使うというのが私の経験則ですが、それがspecを書くときのデフォルトの方法だとは伝わっていないようです。

  • 私はsubjectやワンライナー構文(例: it { is_expected...})を使うことはほとんどありません。特定の分野のプロジェクト(例:mustermann)ではワンライナー構文はうまく機能しますが、大半のプロジェクトではあまり有益なテクニックではないと考えています。

というわけで、私からの一般論としてのアドバイスは、あなた自身をあまり制限しようとせずに、まずはシンプルな機能から始めて、十分なメリットがあるときにだけその他の機能を使うようにすることです。


Q. シンプルなスタイルと複雑なスタイル、選ぶならどっち?

最近私はspecを書く2つの異なるアプローチについて考えてきました。
1つは「シンプルな」スタイルです。これはRSpecの最小限の機能を使います。

RSpec.describe FoodFactory, '#make_food' do
  it 'makes bananas for monkeys' do
    factory = FoodFactory.new(:monkey)
    expect(factory.make_food).to eq(:banana)
  end

  it 'makes hay for horses' do
    factory = FoodFactory.new(:horse)
    expect(factory.make_food).to eq(:hay)
  end
end

もうひとつは「複雑な」スタイルです。こちらはRSpecの機能をたくさん使います(contextsubjectis_expected、等)。

RSpec.describe FoodFactory, '#make_food' do
  subject { factory.make_food }
  let(:factory) { FoodFactory.for(animals) }

  context 'for monkeys' do
    let(:animals) { :monkeys }
    it { is_expected.to eq(:banana) }
  end

  context 'for horses' do
    let(:animals) { :horses }
    it { is_expected.to eq(:hay) }
  end
end

あなたはどちらかひとつを選びますか?それとも両方をおすすめしますか?その理由は何ですか?

A. 絶対にシンプルな方です

私が確実に好きなのは、そして絶対におすすめするのは、あなたが最初に提示したシンプルなスタイルの方です。

subjectis_expectedといった機能には適切なユースケースがあります。しかし、残念ながらユーザーの中にはそれが「RSpecらしいスタイル」と考えて、すべてのspecをそういう書き方にしようとします。

あなたが書いてくれた複雑なスタイルの方は、RSpecの正しい理解が必要になりますし、制御フローもあっちこっちに飛びます。シンプルなスタイルはコード量も少なく、何よりずっと単純です。

specファイルが大きくなるにつれて、複雑なスタイルはだんだんと付き合っていくのが困難になります。あるexampleとそれが依存するいくつかの宣言文(例:subjectlet(:factory))の距離はどんどん離れていくでしょう。

加えて、ドキュメント文字列を手書きするのではなく、RSpecに出力させている場合は、ドキュメント文字列を使って(--exampleを使って)exampleをフィルタリングできなくなります。なぜならRSpecはそのexampleを実行するときにドキュメント文字列を生成するからです。なので、実行前にフィルタリングすることはできません。

この点については書籍「Effective Testing with RSpec 3」の中でもいくつかの場所で議論しています。


Q. RSpecのDSLは全員覚えるべき?

あなたはRSpecが複雑だと思いますか?そして人々はテストにフォーカスする代わりに、RSpecのDSLを学ぶ必要があると思いますか?

A. 必ずしもRSpecを選ぶ必要はありません

はい、RSpecは間違いなく複雑です。テストをするためのもっとシンプルな方法は他に存在します。

いいえ、テストをうまくやるためにRSpecを学ぶ必要はありません。Minitestのようなシンプルなテストライブラリを使うことはまったく問題ありません。

RSpecはとても魅力的な機能をたくさん持っていると私は考えています。そうした機能はテストによって開発を加速させたいと考える開発者にとって、たいへん便利なものです。ただし、学習曲線は確実に存在します。

Minitestを使った方がいいケースは確実にあります。たとえば、もし締め切りが厳しく、チームメンバーが他の言語でxUnit系のフレームワークを使い慣れており、RSpecを一度も使ったことがないのであれば、私は絶対そのチームにRSpecよりもMinitestを使うことをおすすめします。


Q. RSpecとMinitestをどう比較する?

あなたはRSpecとMinitestをどのように比較していますか?

A. どっちも便利。哲学が異なるので対立させるべきでない

どちらもとても便利です。開発の生産性が向上するなら、どちらを使っても構いません。

ですが、2つのライブラリは異なる哲学を持っています。Minitestは「ミニ」であることに焦点を置いており、それはそれで成功しています。小さくてシンプルなテスティングフレームワークになっていることは、本当に素晴らしいことです。フレームワーク本体のコードだって一気に読み上げることができます。

私は過去にMinitestを使ったときにMinitestに不足していてときどき不満に思った機能を、RSpecに実装し、RSpecを便利にしています。

RSpecでは第一級クラスによる抽象化も提供しています。これは複雑さを増す原因になりますが、一方で柔軟性も提供してくれます。

たとえば、exampleは(単なるメソッドではなく)オブジェクトであり、メタデータを追加できます。そのため、設定ファイルを通じてフィルタリングや挙動の変更を行うことができます。エクスペクテーションは(検証メソッドの代わりに)マッチャオブジェクトを利用します。このオブジェクトはコンポーザブル(組み合わせ可能)です。

ですが私は、Minitestの開発者にはRSpecをMinitestに対立するものとして議論の対象にしてほしくありません。私は2つのフレームワークをRailsとSinatraのようなものだと考えています。RailsとSinatraはどちらも便利ですし、それぞれに有益な利用シーンがあります。この2つを対立するフレームワークと見なす必要はないのです。


Q. 追加して後悔している機能は何?

RSpec 3でなくした機能以外で、追加して後悔している機能は何ですか?また、それはなぜですか?

A. any_instance、ワンライナー構文、before(:all/:context)です

私が考える主な機能は以下のとおりです。

any_instanceを使ったモックとスタブ

この機能をサポートするためのコードは極めて複雑で、必ずしもRubyの全機能とうまく協調しません(たとえば、スタブ化されたメソッドがprependされたモジュールの中で定義されている場合はうまく動きません)。

加えて、これを使うとテスト対象のコードが内部でやりとりしている複雑性を隠してしまいますし、RSpecの機能がユーザーに対して設計上の問題点を回避するのを促すことになってしまいます。これを使ってもコードの設計は改善しないのです。

ワンライナー構文(例:it { is_expected.to blah }it { should blah }

この機能は機会と場所を選びます(たとえば、mustermannのspecにはピッタリです)。ですが、この機能はRSpecのユーザーを不幸な方向へ導いてしまいます。

何年にもわたって、私たちは数多くの質問やリクエストを受けています。それらはRSpecのDSLに機能を追加して、ワンライナー構文でもっと複雑なことをできるようにしたい、というものです。ですが、大半のケースでは単にdescribeitを使う方がよいと私は考えています。

そして、できればsubjectやワンライナー構文のサポートは、恩恵を受けることができる数少ないプロジェクトのために拡張gemにしたいと考えています。

before(:all)before(:context)

この機能は理論上はすばらしく、とてもシンプルに見えますが、実際には問題を引き起こす原因になりがちです。なぜなら、RSpecとその周辺のエコシステムではexampleごとのライフサイクルを想定しているものが多いからです。

たとえば、exampleごとにDBのトランザクションをラップし(大半のプロジェクトがそうしているでしょう)、before(:cotext)フックを使ってDBレコードをいくつか作成すると、トランザクションの外部でレコードを作ったことになり、データが残ってしまって他のexampleが失敗する原因になります。

もし実行速度が気になっていて、一度の実行で一気にたくさんのアサーションを実行したいと考えているのであれば、aggregate_failuresを使う方がいいでしょう。


Q. power-assertってどう思う?

power-assertについてはどう考えていますか?

A. 便利そう。ElixirのExUnitはとても良かった

あなたが言っているのはこのライブラリ(訳注:https://github.com/k-tsj/power_assert)ですか?
使ったことはありませんが、便利そうですね。

私は最近ちょっとだけElixirを使ったんですが、ExUnitは同じようなアプローチをとっていました。
assertはマクロになっていて、渡された式のAST(抽象構文木)を受け取ります。さらにそれを分割し、異なる部分を評価し、とても役立つ失敗メッセージを提供します。個別の検証メソッドやマッチャは必要ありません。
あれはとても良かったですね。

検証APIが小さくなればなるほど、人々はテストを簡単に始めることができます。
これはとても素晴らしいことです。


Q. RSpecで今後チャレンジしたいことは?

RSpecで今後やってみたい大きなチャレンジは何かありますか?

A. やれることはやりきった。APIの大きな変更もないだろう

私が考えている大きなチャレンジというのは特にありません。その理由の一つは、私がRSpecのことをほとんど「やりきった」と考えているからです。

RSpecは引き続きサポートされ、改善されますが、APIの大きな変更はもうないと思います。
RSpecの機能はすでに十分豊富です。
正直なところ、次のメジャーリリースで魅力的かつ大きな新機能を提供するのは難しいだろうと考えています。

(翻訳ここまで)

まとめ

というわけで、この記事ではRedditに載っていたRSpecに関するQ&A企画を翻訳してみました。

Myron Marston氏がsubjectやワンライナー構文の追加を後悔していたり、これ以上のAPIの大きな変更は考えていなかったりする点が個人的にはすごく興味深かったです。

こうした筆者の考えやアドバイスはみなさんがRSpecを書くときに役立つ物も多いと思うので、ぜひ参考にしてみてください。

あわせて読みたい

【書評】RSpecの初心者から上級者まで役立つ!「Effective Testing with RSpec 3」を読みました - give IT a try

このQ&A企画の元になっている書籍「Effective Testing with RSpec 3」を読んだ感想をまとめています。
今年出版されたばかりの洋書ですが、RSpecを書くときに参考になる考え方がたくさん載っていました。

【アンチパターン】Arrange、Act、Assert(AAA)を意識できていないRSpecのコード例とその対処法 - Qiita

Q&Aの中でMyron Marston氏も「ワンライナー構文は濫用すべきでない」と語っていましたが、この点は僕も同意見です。
このQiita記事の中では僕も同じようなことを書いています。

サヨナラBetter Specs!? 雑で気楽なRSpecのススメ - Qiita

複雑なスタイルよりもシンプルなスタイルの方が良い、というのもやはり同意見です。
僕も最近は複雑なスタイルを避けるようにしています。
このQiita記事では具体的なコード例を挙げて、複雑なスタイルとシンプルなスタイル(雑なスタイル)をメリット・デメリット比較しています。