その他
    ホーム 技術発信 DoRuby 絶対に笑ってはいけないRSpecの現場24時

    絶対に笑ってはいけないRSpecの現場24時

    この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

    こんにちは、SHIMADAです。 今日は現場の泥臭いRSpec話を書きます。

    新しいフィーチャーを追加するとき、specがなかなか通らない、実装が思い通りに進まない、ということがありますよね。
    そんなとき、自分の書いたコードが実際にはどう動いているのか確かめる方法が欲しいです。

    一番簡単で広く使われているのはデバッグプリントという手法です。
    コードの中に p 変数名 と書くと、specの実行途中にその変数の中身がどうなっているか確認できます。

    ここで問題になるのが、specがたくさん書いてあるとデバッグプリントが一杯出てきて肝心の調べたいケースが埋もれてしまうという点です。

    ちなみに今携わっているプロジェクトのspecを見てみると、examplesの数が1,000を超えてました。

    これが一般的な水準で多いのか少ないのか分かりませんが、ここまできてしまうとデバッグプリントがどかどかっと表示されてあっという間に押し流されてしまうので大変です。

    そういう状況でもなんとかしようと編み出した泥臭いテクニックを以下に紹介します。

    説明のために簡単なバグ入りのコードと、それを検出するテストを用意しました。

    コード

    
    class Calculator
      attr_reader :answer
    
      def plus(a, b)
        @answer = a + a
      end
    end
    

    テスト

    
    describe Calculator do
      subject do
        Calculator.new
      end
    
      it "1 + 1 = 2" do
        subject.plus(1, 1)
        subject.answer.should == 2
      end
    
      it "2 + 2 = 4" do
        subject.plus(2, 2)
        subject.answer.should == 4
      end
    
      it "2 + 3 = 5" do
        subject.plus(2, 3)
        subject.answer.should == 5
      end
    end
    

    こんな感じでコードとテストを書いたとします。
    バグがあるので “2 + 3 = 5” は成功しません。

    Calculator#plusにデバッグプリントを埋め込んで、引数と計算結果を調べてみましょう。

    
      def plus(a, b)
    p :chk1
    puts "b = #{b.inspect}"
    puts "b = #{b.inspect}"
        @answer = a + a
      end
    

    まず、デバッグ用のコードは通常のコードではないことを強調するために、インデントを無視して行頭から書くようにしています。
    こうすることでデバッグが終わってコミットする前に、無駄なコードを消し忘れることを防ぎます。

    それから、先頭の p :chk1 は僕が個人的に使っているデバッグ用のマーカーです。
    RSpecはピリオドがたくさん出力されます。
    デバッグ出力が行の途中から始まると折り返しが読みにくいので、:chk1という目印を使って強制的に改行します。

    
    ..........:chk1
    a = 1
    b = 1
    ......
    

    当然、いろんなところにデバッグプリントを入れていくときは :chk1, :chk2, :chk3… と数字を増やしていきます。

    ところで、このままだと全部のテストケースでデバッグプリントが出力されてしまいます。
    できれば、失敗するspecの時だけデバッグプリントを出力したいですね。
    そこで、specの側に一時的にこういうものを埋め込みます。

    
      it "2 + 3 = 5" do
    $dbg=true
        subject.plus(2, 3)
    $dbg=false
        subject.answer.should == 5
      end
    

    はい。ご覧のとおり $dbg は禁断のグローバル変数です。
    グローバルなのでspec内で定義して本番コードの中から参照できます。無敵です。

    ちなみにコミットする前に除去する一時的なコードなので、この変数は本来のコードと被らなければどんな名前でも構いません。

    使い方は、Cでよくある

    
    #define DEBUG
    

    
    #ifdef DEBUG
    

    のペアと同じです。

    
      def plus(a, b)
    p :chk1 if $dbg
    puts "b = #{b.inspect}" if $dbg
    puts "b = #{b.inspect}" if $dbg
        @answer = a + a
      end
    

    これで、調べたいテストケースの時だけデバッグプリントを出力させることができるようになりました。
    特定のケースで、どの行でどんな挙動が起こっているか確認できると、デバッグはかなり捗ります。

    あとそれから、デバッグプリントだけでは足りなくてどうしてもうまくいかない難しいケースのとき、

    
    debugger if $dbg
    

    としたこともあります。specの実行中この箇所に到達すると、Rubyのデバッガが起動するのでステップ、トレースしながらコードを追いかけてなんとか問題を解決することができました。

    最後に、コードのバグがとれてspecが通るようになったらこれらは全部消してきれいにしてからコミットします。

    タネを明かせばどうっていうこともない簡単なことですが、なかなか役に立つので今までずっと使っています。みなさんもバグに手こずった時、試してみることをお薦めします。

    記事を共有