TDD Anti-patterns catalogue at Stack Overflow を簡単に訳してみた

Stack OverflowのTDD Anti-patterns catalogueというスレがとても面白かったので訳してみた。


Stack Overflowのvoting機能でアンチパターンへの投票を行っている感じ。
上から投票の多い順になっている。


得票数はこの記事執筆時点(2013.7.9)のもの。
SQLアンチパターンっぽく、パターン名はそのまま片仮名にしてみた。
また、内容がかなり被っているとか、状況がかなりレアじゃないかと思うものは、一部省略しました。

(ブコメで訳間違ってるよ、って教えてもらったので、一部修正しました 2013.7.10)

フリーライド (テストのただ乗り) 50pt

新しいテストケースを書くのではなく、他の機能のテストに新しいアサーションを追加して既存のテストケースに乗っかる。

セカンドクラス シティズン (二等市民) 47pt

プロダクションコードのようにリファクタリングされないテスト。大量の重複やメンテしづらいテスト達。

ハッピーパス 42pt

正常系のハッピーパスだけテストされており、例外や境界値のテストが無い。

ローカルヒーロー (御当地の英雄) 42pt

特定の開発環境に依存したテストケースが存在し、誰かが別の環境で実行するとテストは失敗する。

ヒドゥン ディペンデンシー (隠された依存関係) 42pt

ユニットテストがいくつかのデータが存在する事を前提に書かれており、前提条件が名言されていない。もしデータが適切に準備されていないとテストが落ちる上、適切なエラーメッセージが表示されない。

チェイン ギャング (ギャングの序列) 41pt

あるテストケース同士の組み合わせを特定の順序で実行しなければならない。一つのテストがグローバルな状態(グローバル変数、データベースのレコード)を変更してしまい元に戻さない。
データベースのロールバック漏れ等で良く見られるケース。その他、グローバルな状態を操作する時にtry/catchでラップする事を忘れている場合等にも発生する。

ザ・モックリー (モック過剰) 37pt

テストケースが大量にモックやスタブを含んでおり、実際の挙動では無くモックが返す値をテストしている状態。
(コメントで、レイヤー同士の依存関係が多過ぎる事が原因と考えられるので、リファクタリングしなければいけない状態であると指摘されている)

サイレント キャッチャー (沈黙のキャッチ) 32pt

もし例外が投げられた時に通過して欲しいテストケースがあった場合、その例外が開発者の意図と異なる形で発生してもテストが通ってしまう状態。例えば、期待する例外の型を広く取り過ぎて、意図していない例外もキャッチしてしまっている場合。

アナル プローブ (ケツの穴の検査) 25pt (← Good Naming!!)

あるテストが普通ではなく非常に不健康な方法で実行されている場合。例えば、プライベートなフィールドを読むためにJavaのsetAccesibleを利用したり、クラスを拡張したりテストコードを特定のパッケージに組込む等。このパターンが見られる時は、データを過剰に隠蔽し過ぎている可能性がある。

インスペクター (観察者) 24pt

コードカバレッジを100%にするためにカプセル化の原則を破壊している。

イクセッシブ セットアップ (準備過剰) 23pt

テストケースの事前条件を準備するために、数百行のコードや環境の準備をしなければならない状態。テストにノイズが多くなり本当に検証したい事が分からなくなる。

テスト ウィズ ノーネーム (名無しのテスト) 19pt

バグトラッカー等から書き起こされたテストケースに、具体的な名前が付けられていない状態。例えば'testForBug123'のように。時間が経ってからテストが落ちた時、バグチケットを見るまでその意図が分からなくなる。

スロウ ポーク (のろまなツッコミ) 17pt

ユニットテストの実行が信じられないぐらい遅い状態。開発者はテスト開始をキックした後、トイレに行ったり煙草を吸いにいったりする。ひどい場合は、帰宅する前のその日の終わりにテストを実行する事になる。

インアプロプリエイトリー シェアード フィクスチャー (不適切なフィクスチャーの共有)

複数のテストケースがsetup/teardown処理に必須ではないフィクスチャーを共有している。新しいフィクスチャーを用意するのをサボっている状態。

ザ・ジャイアント (巨人テスト) 15pt

あるユニットテストがめちゃめちゃでかい。実質的に大量のテストケースを含んでしまっている。これは所謂「神クラス」の兆候でもある。テストコード自体にバグが入り込む可能性も高くなる。

ザ・バタフライ (バタフライエフェクト) 14pt

頻繁に変更されるデータを含んだものをテストしなければならないのに、結果を固定値に落とし込む方法が無い状態。例えば現在時刻等を含む構造。出力結果に注意を払わないテストケースになってしまう。

フリッカリング テスト (ちらつき) 13pt

落ちたり落ちなかったりするテスト。特定の時刻ではなく、状態競合等によるもの。典型的には非同期処理に対するテストで発生する。

ウェイト アンド シー (待機、そして観測) 12pt

実行結果を取得するために、一定期間のウェイト処理を挿入し、その後に値を取得するテスト。例えば「Thread.sleep」で待ってから値を確認する等。低スペックの環境や、利用の激しいCIサーバー等でテストが失敗する可能性がある。

フォーティ フット ポール 10pt

テストしようとしているクラスとの結び付きを恐れる余り、過剰な抽象化やレイヤーでロジックを分離しようとする。そのせいで、とても脆く壊れやすいテストになってしまっている状態。

ビリーブ イット ウェン アイ シー (見るまで信じない) 9pt

本当のユーザーのようにテストしようと、無理やりGUI経由でテストを実行しようとする。ビジネスロジックのテストがGUIと結びついていると、GUIの変更で数多くのテストケースが破壊されてしまう事になる。

エンヴァイラメンタル ヴァンダル (環境の侵略者) 9pt

テスト実行のために、環境変数や特定のTCPポート等が必要になる場合。利用できないポートがある時はテストが失敗する。テストで利用するポート番号等がハードコードされていると、同時に複数環境向けのテストを実行した時にテストが失敗する事になる。

スリーパー (休火山) 9pt

未来のある日時が訪れた時に失敗してしまうテスト。日付やカレンダーに関わるロジックで発生する。
「このコードは2000年より前に書き直されるだろう」 1960年代の多くのプログラマーの言

カッコー (カッコウの鳴き声) 9pt

あるテストケースが他のいくつかのテストケースに依存している状態。他のテストと同じsetupメソッドを使っているにも関わらず、その成果物を捨てている。
インアプロプリエイトリー シェアード フィクスチャーの一例。

チューリング テスト 8pt

なんらかの効果なツールが魔法のように自動生成したテストケースが多数存在する。

デッド ツリー 8pt

スタブが生成されているが、そのコードの実装が無い。

ウェット フロア 5pt

テストが生成したデータがどこかに保存され、テストが終了した時に適切にクリーンアップされていない状態。テストを続けて実行すると失敗する可能性がある。例えば、tempディレクトリにファイルを保存するようなテストを別々の実行ユーザーで実行した時、パーミッションの問題で適切にデータが削除されない事があり、テストが壊れてしまう。

自宅サーバをHaswell機に更改 VT-dとかを試す

夏前にPCが二台ぶっ壊れてしまい、流石にそろそろ新しいマシンを組もうと久々にパーツまとめて買って自作する事にした。


せっかくなので出たてのHaswellを使ってみることに。


CPU: Core-i7 4770 (4770KはVT-dが無効になってるので)
MB: ASUS Z87-PRO
MEM: 32GB (8GBx4)
Intel SSD 520 240GB
まとめ買いの割引で大体総額10万弱ぐらいかな。


後はヤフオクで、IBM ServeRAID M1015を落札して購入。
以前買ってOpenIndianaでの稼動実績があるし、オクなら1万円しないので。
SASの口二つあって、SATA8台直で刺せるしSolarisでも動く。
新品でLSIのHBA買うと3、4万ぐらいかかるので、かなりお得。
とりあえず、ITファームウェアに書き換えて利用する。


その後、稼動してた自宅のストレージサーバを解体して、HDDを全部繋げ替えた。
12台を抜いて繋ぎ替えるのにめっちゃ疲れた。


以前からVMWare ESXiを利用していたので今回もESXiを入れる事にする。
KVMとかで運用しようかとも考えたが移行と管理が面倒だった。


ファームウェアを書き換えたServeRAIDを二枚刺して、VMWare ESXiのPCIパススルーの設定を行う。
これでRAIDカードが直接VM上のOpenIndianaに見えるはず。
HDDの認識は簡単に上手くいったので、後はzfsをimportすればほぼ移行は完了。
今回はこれが一番やりたかった。


別々になってたストレージサーバとVMサーバを集約したのでPC1台で済むようになった。
しかも動画のエンコード速度が倍ぐらいになって、最近のCPUのパワーを思い知った。
後、GentooVMが10秒ぐらいで起動するようになった。IntelSSDすげー。


実は作ってる途中メモリ16Gで動作確認し、後から16G追加したらマザボがぶっ壊れてしまい、初期不良の交換に行くというハイパー面倒なことになってしまった。
既にケースに入れてネジ止めしちゃってるしHDDも13台繋いだ後だったのに…。
おかげで、かなりの肉体労働をする羽目になったし、移行がちゃんと完了するのに2週間ぐらいかかった…。
やっぱ、自作は結構面倒だなーと思う。
とは言え、極端なマシン構成だと自作で組むのが一番安いのでどうしようもない。

RubyhirobaでのLTの謝罪と、表現の自由と不快感と社会性について

ちょうど、この一つ前の記事について書いた事を余りにも正直にLTで話してしまった事についてです。

Rubykaigi本編でのジェンダー発言については、私は聞いておらず良く分かりませんので、そちらの話はしません。

私の話は、非常に幼稚で多くの人に不快感を与えたことについては、完全にその通りであり、謝罪するしかありません。
申し訳ありませんでした。


当事者として、多少思う事があるので、いくつかツイートしましたが、Twitterで書いてると、断片的になって誤解を生んでしまうので、ちゃんと文章としてまとめておきます。
意図とは違う受け止め方をしているかもしれませんが、ただ謝罪するだけで何も言わない事はむしろ不誠実だと思うので、自分なりに考えた結果を書いています。


私は、どういう内容を語ろうが、どういう表現方法を使おうが、人を不快にさせる時は不快にさせるし、傷つけることもあると思っています。
現実的に損害を被る事もあるかもしれません。
ただ、人が不快に感じるからというだけでは、発言を他の誰かが差し止めることはできない。そんな事やってると、誰も一言も喋れなくなります。
本来は、人は何をどういう口調で語ってもいいものです。


とは言え、表現内容によっては実際に辛い思いをし、不利益を被る人が居るわけで、「自由」が衝突している状態になることもあります。
上手くやるには、どちらかの「自由」を制限しなければならない。
表現内容によって損害を被る人が一定割合を越えた時、社会の要請によって構成員の代表である国家がそれを規制することもあります。
人は、それぞれが「自由」であり、人として尊重されるべき存在であるが、その多様性のためにお互いが上手くやっていけない事が沢山ある。
その中で、より多くの人が自由である世界を実現するために、共通の価値観に基いて自由が制限されていきます。


人はその多様性によって衝突するため、自分の立場を肯定する話は、それが何かを蔑ろにするものであっても、その事に気付かない事があると思います。
ある種のジョークもそういう要素を持ち合わせています。
難しいのは、自分自身の立場やその場に存在している価値観が、社会的な規範と常に一致するとは限らないことです。
人間のコミュニケーションが全て社会の規範に則った内容であるなんてことはありえないし、そうである必要もないと思います。
そういう意味で、全ての表現は社会の価値観にそぐわないものである可能性があり、場の状況と表現内容次第でどうにでも変化する問題なのだと思っています。


一方で、今日の社会において最も尊重されるべき考えというものがあります。その一つが性別に関わる事です。
そうなっているのは、過去に多くの問題があり、実際に多くの権利が奪われるまでに至ったものだからです。
だからこそ、最も重要なものとして考えなければならないものです。
とりわけ国際的な場で繊細に扱われるのは、文化や価値観の違いが色濃く出る側面があるからだと思います。
政治、法律が身近な国では、我々から見れば過剰とも言える程の配慮が求められています。
こういった権利を蔑ろにする表現は、あらゆる場で不適切とは限りませんが、公の場で許容する事を決して認めてはならないものです。
私は、それを破ってしまった。許容するべきでないのは当然の事だと思います。
敢えてそういった価値観に言及する事で、議論を進めたり、対象を絞ったジョークとする事も世の中にはあると思いますが、それには事前に状況をしっかり構築しておく必要があります。


一つ言っておきたいのは、表現の内容そのものが問題ではないということです。私はそう思っていません。
私が、自分自身で最も問題だったと思っているのは、内容そのものよりも誰の承認も得ずに実行した事です。
非常に卑怯であり、危険な行為だったと思います。
最悪、訴訟沙汰になる可能性もあるでしょう。
その場の判断で行われる事こそ、最も注意しなければならないことです。
人には娯楽を享受する権利もあるし、不快なものから目を背ける権利もある。
でも、それを完全に行う事はできない。
どうやっても世の中には自分の許しがたいものが存在して、それを見なきゃならない事がある。
自分が楽しみにしていたものが、ある日突然禁止されて弾圧の対象になる事がある。
お互いがそれぞれの立場を想像して、現実的な判断で交渉する事が必要です。
私は、そのための努力を欠いた行いをしました。
自分の世界だけが全てじゃないことを忘れ、傷付く必要の無い人を傷付けた事は恥ずべきことです。


本来、人は何を思ってもいいし、何を喋ってもいいはずです。
ただ、それが適切かどうかは、社会やその空間が決める事です。
極端な話、500年前の世界で人種差別的な発言や性差別的な発言をした所で、大した影響はなかっただろうと思います。そんな共通の概念も無かったしょう。
今迄の歴史の中で獲得してきたものによって、たまたま今日ではそうなっているという事です。
大事な事は、人は共同体の中で生きている事を忘れないでいることです。
共通の認識として存在する価値観をちゃんと知って、それを守って生きていくのが、この社会の構成員として成熟した一人であるということです。
私は、いただいた指摘をそう受け止めました。
発してくれた人が、そういう考えをしてるかは分かりませんが。


ポルノ云々の捉え方については、世の中には男性が消費される側にまわる事もあるわけで、実際にそれについて私が言及できるかっていうと、中々難しいと思う事もあります。
全体としてどっちが辛い思いをする事が多いかは言うまでもないことですが、最終的には多い少ないの話です。
ジェンダーに関わる事だからどうこう、ポルノに関わるからどうこう、ってわけではなくて、その他のマイノリティはどうなんだ、って事をちゃんと考えなきゃいけない。
女性の中にだって、そういう話題を語る権利も場所も存在するわけで、場が適切であるかないか、という問題です。
常にどちらか一方が弱者であるとは限らない。自分自身の今いる状況と関係無く評価される表現もありえない。
今までの人生で、一度もその場に居ないマイノリティの存在を貶める発言をした事無い人なんて、ほぼ居ないでしょう。
本当に常に一切言及する事が許されない話なんてものは、世の中にない。
最終的には状況が決める事、そういう意味で濃淡の問題なんだと私は思っています。
もちろん、誰もそこまで言ってるわけじゃないのは分かってます。


私が馬鹿だったのは、表現内容ではなくあの場でそれを行ったことです。
公の場で話してよい内容かどうかを自分勝手に判断した事です。
自分の知らない誰かの感情を真剣に考えなかったことです。他者との関わりを忘れたことです。
その結果として、対等の人間を排斥される弱者に貶めた事です。恥ずべきことだと思います。


長々と色々書いたのは、自分なりに真摯に受け止めて、色々考えてみた結果です。
考え方の相違を感じる点はありますが、全面的にご指摘いただいた方が正しいと思っていますし、敬意を持って受け止めています。


私は非常に愚かな子供だったと、改めて認識しました。反省しています。
多くの関係者の方にご迷惑をおかけしたかもしれないことについて、改めて謝罪いたします。
本当に申し訳ありませんでした。

Amazon Glacierを楽に利用するためのRailsアプリを作った

AWSのGlacierは、1GBでおよそ月に1円ぐらいなので、1TBバックアップしても月に1000円ぐらい。
これぐらいなら、まあDropboxのプレミアムぐらいの感覚で、1TBをAmazonさんにバックアップしてもらえますね。
ただ、Glacierは結構利用の手順が面倒なので、それを簡易化するためのアプリを書きました。


GitHub - joker1007/savant_time: Amazon Glacier Backup Wrapper


大体、以下のような機能があります。

  • ディレクトリを閲覧し、任意のファイルをバックアップするジョブを実行する
  • 複数のファイルを選択し、バックアップジョブの実行キューに追加する
  • インベントリ取得ジョブを実行する
  • インベントリ取得ジョブ完了時のSNSメッセージを受け取り、Glacierにちゃんとバックアップされているファイルの情報を保存する
  • アーカイブ取得ジョブを実行する
  • アーカイブ取得ジョブ完了時のSNSメッセージを受け取り、自動で所定のディレクトリにダウンロードする

要は簡易なファイルブラウザと、ポチっとやったら非同期でファイルをアップロードしたり、アーカイブ取得ジョブを実行して後は数時間待ってたら完了メッセージを受け取って勝手にダウンロードしといてくれる、って感じのツールです。


RubyからGlacierを利用する場合は、fogというgemでそれなりに利用できるので、sidekiqあたりと組み合わせて、簡単に作れます。
ただ、作りは簡単なんですが、一つの機能の動作確認が1日がかりなので、作るのにえらい時間がかかってますw

名前の由来は、まあ分かると思うし、ネーミングから用途も分かると思います。
氷河とクールダウンした脳をかけた感じですね。


API通信と非同期処理だらけなので、面倒でテスト全然書いてない…。
まあ、エロ動画のバックアップさえ、ちゃんと出来りゃいいので、後は適当ですよ。

Refinementsとクラスの継承を組み合わせた動作を確認する

大した話ではないが、Refinementsについてちょっと実験してみたので、結果をまとめておく。


まず、現状のRefinementsについて整理する。
今のRefinementsはファイルスコープという、微妙に分かりづらいスコープで適用される。とりあえずサンプルコードで確認してみる

# str_refine.rb
module StrRefine
  refine String do
    def hoge
      "str_hoge" + " " + fuga
    end

    def fuga
      "str_fuga"
    end
  end
end
# main.rb
require_relative "str_refine"

using StrRefine

p "".hoge # => "str_hoge str_fuga"


refineとusingはRefinementSpecによるとModule#refineメソッドとmain.usingメソッドとして定義されている。

この、main.usingが結構曲者で、ファイルのトップレベルでしか基本的に利用できないようになっている。そしてusingを実行したファイルでしか、そのRefinementsは効果が無い。自分は最初、感覚的に分かりづらかったが、そのRefinementsで定義されているメソッドを呼んでいるファイルのトップレベルでusingするということ。


更に、るびまの記事によると以下の機能は削られてしまっている。

  • モジュール単位で refinement を有効にする機能。
  • using されたモジュールの refinement を継承する機能。
  • refine で (クラスではなく) モジュールを拡張する機能。
  • using された複数の refinement 間で super を順に呼び出す機能。
  • module_eval や instance_eval で refinement を有効にする機能。


とは言え、中々ピンと来なかったので、継承周りでいくつか実験してみた。


以下のようにStringを継承したサブクラスを定義する。

# my_string.rb
class MyString < String; end


StrRefineにMyStringを追記する

# str_refine.rb
module StrRefine
  refine String do
    def hoge
      "str_hoge" + " " + fuga
    end

    def fuga
      "str_fuga"
    end
  end

  refine MyString do
    def fuga
      "mystr_fuga"
    end
  end
end


main.rbから呼び出してみる。

# main.rb
require_relative "my_string"
require_relative "str_refine"

using StrRefine

p "".hoge           # => "str_hoge str_fuga"
p MyString.new.hoge # => "str_hoge mystr_fuga"
p MyString.new.fuga # => "mystr_fuga"


これはイメージ通りの動作だと思う。fugaがサブクラスでオーバーライドされているし、親クラスのメソッドも呼ぶことができている。


ここで、refineを定義しているモジュールを分けると、微妙にややこしい事になる。

MyStringを拡張しているrefineを別モジュールに定義してみる。

# mystr_refine.rb
module MyStrRefine
  refine MyString do
    def fuga
      "mystr_fuga"
    end
  end
end


main.rbから呼び出してみる。

# main.rb
require_relative "my_string"
require_relative "str_refine"
require_relative "mystr_refine"

using StrRefine
using MyStrRefine

p "".hoge           # => "str_hoge str_fuga"
p MyString.new.hoge # => "str_hoge str_fuga"
p MyString.new.fuga # => "mystr_fuga"


という感じになって、「おや、fugaのオーバーライドが出来ていない」となる。ちなみにこれは、同一ファイル内で複数モジュールを記述しても同様の動作になる。つまりモジュールを分けると、Refinementsは独立した空間になる。

MyString#hogeが呼んでいるのはメソッド探索チェーンにあるrefineされたStringであって、そのRefinementsの世界にはMyString#fugaは存在しない。

これは、逆も然りで、MyString#fugaでsuperを使うと以下のようになる。

module MyStrRefine
  refine MyString do
    def fuga
      "mystr_fuga" + super
    end
  end
end
p MyString.new.hoge # => "str_hoge str_fuga"
p MyString.new.fuga # => super: no superclass method `fuga' for "":MyString (NoMethodError)


MyString#fugaを定義しているRefinementsの世界には、String#fugaは定義されていないため、superclassにメソッドが無いよ、と怒られてしまう。

「using された複数の refinement 間で super を順に呼び出す機能。」ってこういう事なのかな。

ファイルスコープ云々とは、どうも別物のようで、StrRefineを定義しているファイル上で、MyStrRefineをusingしても、モジュールが別だとお互いのメソッドには触れられないようになっている。

一方で、モジュールの再オープンはオッケーみたいなので、ファイルを別々に分けても、モジュール名さえ同一なら、適切に処理できるようだ。


まあ、何でこんなことやりたいかと言うと、やたらと柔軟性を求められるシステム作っていて、サブシステムごとに親クラスの動作を使いまわしつつ、Refinementsで要所だけ上書きできんかなー、と思って色々実験してみたからです。


やっぱ、ややこしいなあ…。どこのメソッド呼ばれてるのか、ちゃんと気を付けないといけない。振舞いを特定の関心事の世界に閉じ込められるのは結構ありがたいんだけど。


多分、今やりたい事は、同一の関心事に対するRefinementsを一つのモジュールにまとめて、個別に拡張したい時にはモジュールを再オープンして定義を追加すればいけそうな気がする。モジュールの定義とusingまでのロード順はちゃんとやらないとハマりそうだけど。


追記: 5/7 15:30
想像通りといえば想像通りなんだけど、ActiveSupport::Concernなんかを使って、こういう風に書けばそれっぽく名前分けたりとか出来そう。

# mystr_refine.rb
module MyStrRefine
  extend ActiveSupport::Concern

  included do
    refine MyString do
      def fuga
        "mystr_fuga" + super
      end
    end
  end
end
# main.rb
require_relative "my_string"
require_relative "refine"

StrRefine.send(:include, MyStrRefine)

using StrRefine

p "".hoge
p MyString.new.hoge
p MyString.new.fuga

|

Capybaraが2.1.0になって、また微妙に挙動が変わったので調べてみた

久々にブログ書く。


何気なくbundle updateをしたらcapybaraが2.1.0になってて、テストが落ちるようになった。
また挙動が変わったらしいので、ググって確認してみる。
Introducing Capybara 2.1を参照すると大体分かるけど、一応日本語でざっくり書いておく。

セレクタによるマッチの厳密性が変更になった

capybara2.0から、findメソッドでDOMを探した時に複数マッチすると例外を吐くようになったが、
2.1.0からはマッチングのルールをconfigureで指定できるようになった。

Capybara.configure do |config|
  config.match = :prefer_exact
end


マッチングの種類は以下の四つ

  • :one
  • :first
  • :prefer_exact
  • :smart
:one

2.0と同じルール。複数マッチすると例外になる。

:first

ゆるふわなマッチ。マッチした中で最初の一つを使う。

:prefer_exact

1.x系と同じルール。複数マッチした場合、正確にマッチしているものを優先する。

:smart

2.1からのデフォルト。後述するexactオプションに依存して挙動が変化する。

  • exactオプションがtrueなら、:oneと同じ挙動になる。
  • exactオプションがfalseなら、最初に完全一致で検索し、見つからなければ部分一致で検索する。その時、もし複数の要素が見つかるとAmbiguous例外が発生する。

exactオプション

元々、capybaraでfindやらclick_linkで文字列を元にマッチングをかけると、部分一致でもヒットしていた。
2.1からはデフォルトでは完全一致でないとマッチしないようになっている。
find, click_link, fill_inは、:exactオプションで挙動を制御できるようになっているし、
グローバルなコンフィグを設定する事で、デフォルトの挙動を2.0以前と同様に設定できる。

Capybara.configure do |config|
  config.exact_option = true
end

# or

Capybara.exact = true

可視性による挙動の変化

元々、hiddenな要素を無視するオプションがCapybaraには用意されていたが、それはデフォルトでfalseになっていた。
hiddenな要素が見えるのは混乱を呼ぶらしいので、2.1からはデフォルトの設定がtrueに変わった。つまり、設定してないとhiddenな要素は引っ掛からなくなる。
これ、どうなんだろう。linkとかならそうなんだけど、DOMの状況を調べる場合は見えた方がいいこともあるんで、どっちが良いとも言えないと思うんだが。
元の挙動に戻す時には以下のようにする。

Capybara.ignore_hidden_elements = false

可視性によるtextメソッドの挙動の変化

1.x系だと、そもそもルールが決まっていなくて、driverによって挙動が異なっていた。
2.0系になって、hiddenなDOMに含まれるtextは何も返さなくなった。
2.1系からは、ignore_hidden_elementsに挙動が依存するようになった。
また、textメソッドに引数が渡せるようになり、個別に挙動を上書きできるようになっている。

find("#thing").text           # depends on Capybara.ignore_hidden_elements
find("#thing").text(:all)     # all text
find("#thing").text(:visible) # only visible text


ignore_hidden_elementsの設定に関わらず、2.0と挙動を同じにできるように、
Capybara.visible_text_onlyというオプションが用意された。trueに設定すると、常にvisibleなtextを返す動作になる。

titleの値が取れるようになった

titleの値を取るためのAPIとマッチャーが追加された。

page.title # => "The title"
page.has_title?("The title") # => true
page.should have_title("The title")

disabledなフィールドにアクセスするオプションの追加

find_field, has_field?などのメソッドが、:disabledオプションを取れるようになった。
:disabledをtrueにセットすると、disabledなフィールドのみが検索対象になる。


というわけで、ざっくりまとめて見た。
正確に知りたい人は原文を読んでください。

大江戸Ruby会議03にレポート班スタッフとして参加してきた

3/16(土)に開催された大江戸Ruby会議03にスタッフとして参加させてもらうことができました。
以前、一緒にみなとRuby会議でレポート班だったhokkai7goさんに誘ってもらう形で、今回もレポート班としての参加です。
憧れのAsakusa.rbが主催する大江戸Ruby会議にスタッフとして関わる事が出来て、とても嬉しく思っています。
Yokohama.rbに参加するようになるまでは、完全にぼっちだった私が、今Rubyで仕事をしつつこんなイベントに関わる事が出来ているのも、Rubyistコミュニティのヌクモリティのおかげです。
巻き込んでくれたほっかいさんに感謝!


大江戸Ruby会議03は、Asakusa.rbのコンテンツ力の高さが全面に出ていて素晴らしいイベントだったと思います。
技術的な話、泥臭い舞台裏の話、スピリチュアルな話と、かなり幅広い内容を楽しむ事ができました。
クックパッドのレガシーコードマイグレーションの裏側の話では、あのクックパッドでも昔のコードには色々残っているんだなー、という事が知れて少しほっとしたりw
そして、トリをつとめたnari3による「桐島」の衝撃的な面白さ。
本当に、とても濃い1日でした。
その分、レポートのためのメモ取るのに、結構疲れましたがw


イベントは無事終了しましたが、レポート班としての仕事は、当日からイベント終了後が本番です。
次号か次々号のるびまでリリースできるように、早めに書き上げてしまわなくては…。
後、togetterによるまとめの準備は大体出来たので、今晩中には広く公開する予定。


さて、頑張ろう。後、「桐島」を見なければッ!