Startup Live! に登壇してきました

こんにちは、MUGENUP CTOの伊藤です。

12月1日の日曜日にリクルートキャリアアカデミーホールで開催された「Startup Live!」というイベントに、パネルディスカッションのパネラーとして登壇してきました。

f:id:mgnup:20131203162541j:plain

Startup Live!ってなに?

開催概要についてはatndなどに書いてあるので引用します。

【現在参加者100名突破!更に増席50席分はこちら!】12/1(日)開催、話題の急成長スタートアップ8社の経営者が登壇!Start up Live! : ATND

■開催背景 2010年以降ソーシャルメディア、スマートフォンなどの各領域の拡大や日本のベンチャーキャピタルの投資規模拡大により急速に立ち上がるベンチャー企業が増加して参りました。以前に比べ、大企業から起業する人材層や、大企業から数名から数十名のベンチャーの経営幹部となるケースも増加しており、それらの背景から人材大手のリクルートキャリアとシードアーリーステージ企業の支援に特化したベンチャーキャピタルのSkyland Venturesが組んでスタートアップに特化し、成長ベンチャーの社長、CTOと直接会えるリクルーティングセミナーStartup Live!を開催します。当日は成長著しいベンチャー企業の社長、CTOが8社登壇します。

というわけで、「成長ベンチャーのCTO」としてMUGENUPから乗り込んできました。

スタートアップの勢い

クラウドワークス代表吉田さんの講演から始まり、パネルディスカッション<ビジネス>、パネルディスカッション<エンジニア>と各社ともイキイキとした内容となっており、スタートアップならではの面が良く出たイベントでした。

また、イベント翌日の12月2日(月)には登壇企業の内2社が調達のリリースを出しており、その点も改めてスタートアップの勢いを感じる出来事でした。

f:id:mgnup:20131203191925j:plain

パネルディスカッションでは主に

  • 開発体制はどうなっているか
  • スタートアップのやりがい
  • 今後エンジニアはどうすればいいのか

といった内容について話してきました。

開発体制はやはり気になるところで結構イベントの時などでも話題になりますよね、 せっかくなので弊社の開発体制についてここで少しご紹介します。

MUGENUPシステムチーム開発体制

MUGENUPではクラウドのクリエイターさんと社内の人間をつなぐ「MUGENUP WORK STATION」という システムで全ての制作物を管理しており、システムチームはその開発、運用を行っています。

チームで利用しているサービス、ツールは主に以下のものになります。

  • PivotalTracker ストーリーの管理
  • GitHub コードの管理、プルリクのレビューなど
  • AWS サーバーサイドは全てAWSです
  • ChatWork チーム内や社内の人との情報共有
  • Skype チーム内の情報共有
  • Qiita:Team チーム内のノウハウの共有など
  • Toggl 見積もりの実測値計測用に

基本的に週に一回のタイミングでリリースを行うので、現在は週一で振り返りを行っています。

f:id:mgnup:20131203164754j:plain

(イーゼルパッドと付箋を用いてぺたぺたやっています)

話は戻って

さて、そんなこんなでベンチャー色が出まくったイベントだったんですが、当日はLINE株式会社の森川社長もいらっしゃっていてブログに書かれています。

ベンチャー企業のリクルートイベントに行ってきました : LINE株式会社 森川社長ブログ

私自身今後はイベント等にどんどん出て行きたいと考えているので、MUGENUPのこと、MUGENUPシステムチームのことで話したいことがある人はぜひお声をかけてもらえればと思います。

宣伝

MUGENUP では、アジャイルな開発に興味津々なエンジニアを募集中です。 無限流開発、ご一緒しませんか?

大きな裁量で自社サービス開発!Rubyエンジニアをウォンテッド! - 株式会社MUGENUPの求人 - Wantedly

f:id:mgnup:20131118023234p:plain

should を捨て expect を使うようになった、たった一つの理由

皆さんこんにちは。スーパーのビニール袋は濡れタオルを触ってから開ける方、MUGENUP の osada です。 今回はexpect について調査したことを書きたいと思います。 ターゲットは、rspecが上手く書けなくて、気が付くと3時間もテストを書いている、というようなrspec初心者です(実話)。

中間管理職 expect さん

では、本題のexpectです。まずは問1から。5秒で答えてください。

describe Person do
  it "should be a instance" do
    expect{Person.new}.to be_a(Person)
  end

  it "should be a instance" do
    expect(Person.new).to be_a(Person)
  end
end 

問1

  1. 両方テストが通る
  2. 上のテストが通らない
  3. 下のテストが通らない
  4. 両方通らない









できました?正解は

解答: 2. 上のテストが通らない

  1) Person should be a instance
     Failure/Error: expect{Person.new}.to be_a Person
       expected #<Proc:0x007fbcf91a75a8@/Users/osada/projects/ruby_test/rspec_test/spec/person_spec.rb:9> to be a kind of Person
     # ./spec/person_spec.rb:9:in `block (2 levels) in <top (required)>' 

では、問2

上のテストが通らない理由を説明してください

違いは、{}()です。しかし前回のchange マッチャで使っていたのは{}のブロックでした。今回はなぜダメなんでしょうか?

調べていきましょう。

ソースと共にあらんことを

ソースコードを読んで分かるように書くべきである、という考え方にあるように、Rubyist は常にソースと共にあるようです。 では、expect の深淵に足を踏み入れたいと思います。

私の環境では、ruby/2.0.0/gems/rspec-expectations-2.14.2/lib/rspec/expectations/syntax.rb が入り口のようです。

expect と to

          def expect(*target, &target_block)
            target << target_block if block_given?
            raise ArgumentError.new("You must pass an argument or a block to #expect but not both.") unless target.size == 1
            ::RSpec::Expectations::ExpectationTarget.new(target.first)
          end

block のときは、targetに追加してから、target.firstを委譲しています。ということは、引数とブロックを同時に渡せるのか?、とおもいきや、target.size==1以外受け付けないので、ちょっと不思議な構造です。

::RSpec::Expectations::ExpectationTargetは同じ階層のexpectation_target.rbにあります。

    # @example
    #   expect(something) # => ExpectationTarget wrapping something 

    class ExpectationTarget

      # @api private
      def initialize(target)
        @target = target
      end 

とあるように、expectはただのラッパーのようです。 そして target はそのまま @targetに代入されています。ブロックだからといって、特別扱いはしていませんね。

では、続いて toを見てみます。

      def to(matcher=nil, message=nil, &block)
        prevent_operator_matchers(:to, matcher)
        RSpec::Expectations::PositiveExpectationHandler.handle_matcher(@target, matcher, message, &block)
      end 

引数を見て驚きました。toにはmessageblockが渡せるのですね。そしてその先は、PositiveExpectationHandlerに委譲しているようです。

なんと expect がしているのはこれだけです!シンプルですね。 この先はhandlerを読まなければなりません。

PositiveExpectationHandler

PositiveExpectationHandler があるのは、同じ階層の handler.rb です。

ruby/2.0.0/gems/rspec-expectations-2.14.2/lib/rspec/expectations/handler.rb

    class PositiveExpectationHandler < ExpectationHandler

      def self.handle_matcher(actual, matcher, message=nil, &block)
        check_message(message)
        ::RSpec::Matchers.last_should = :should
        ::RSpec::Matchers.last_matcher = matcher
        return ::RSpec::Matchers::BuiltIn::PositiveOperatorMatcher.new(actual) if matcher.nil?

いきなり困りました。 コロン2つで始まる式なんてありましたっけ? そのことについては後述することにして先に進めます。

        match = matcher.matches?(actual, &block)
        return match if match

ここが判定の部分です。 前回 changeマッチャ で見ましたが、matchermatches? メソッドを呼びだしています。 こうゆうの(↓)ですね。 to からくる block を渡せる matcher と 渡せない matcher があるようです。change は渡せない方だったのですね。

         def matches?(event_proc)
          raise_block_syntax_error if block_given?

          @actual_before = evaluate_value_proc
          event_proc.call
          @actual_after = evaluate_value_proc

        (!change_expected? || changed?) && matches_before? && matches_after? && matches_expected_delta? && matches_min? && matches_max?
        end

結局のところ、expectで渡したtarget はマッチャーの所まで、何の加工もなくそのまま委譲されるということが分かりますね。そしてbe_aマッチャ(実体はbe_kind_ofマッチャ) は、こんな感じです。

      class BeAKindOf < BaseMatcher
        def match(expected, actual)
          actual.kind_of? expected
        end
      end

やっと{}で書くと失敗する理由がわかった気がします。 つまり、{Person.new}.kind_of? PersonProc.kind_of? Personですので、ProcオブジェクトはPersonインスタンスではない、といことで失敗しているのでした。===で評価しているマッチャであれば、成功するに違いありません。

ということで、expect に何を渡すかは、マッチャによるという身も蓋も無い話になってしまいましたが、ここを意識して書いていくことで、もっとスラスラと rspec が書けると良いなと思っています。

message は失敗したときの表示

せっかくなので続きを見ていきましょう。

match で true になったら、return するので、以降は match しなかった時の処理のようです。

        message = message.call if message.respond_to?(:call)

        message ||= matcher.respond_to?(:failure_message_for_should) ?
                    matcher.failure_message_for_should :
                    matcher.failure_message

to の第2引数、message はここで使われるようです。 オリジナルのエラーメッセージを返すことができるんですね。試してみます。

  it "should be a instance" do
    expect{Person.new}.to be_a(Person), "通らないヨーダ"
  end
  1) Person should be a instance
     Failure/Error: expect{Person.new}.to be_a(Person), "通らないヨーダ"
       通らないヨーダ
     # ./spec/person_spec.rb:10:in `block (2 levels) in <top (required)>' 

おお、本当に変わりました。

指定しないときはいつものエラーメッセージですが、 それらは matcher が返していたようです。

message.callMethodクラスのことのようですが、奥が深いのでスルーします(えっ)。 エラーメッセージを返すメソッドを作成して、 それを rspec の引数に渡すような使い方を想定しているのかもしれません。

diffable

ここで最後です。diffable? とは何でしょうか?

        if matcher.respond_to?(:diffable?) && matcher.diffable?
          ::RSpec::Expectations.fail_with message, matcher.expected, matcher.actual
        else
          ::RSpec::Expectations.fail_with message
        end
      end
    end

これを持っている matcher を探してみると、base_matcher.rbfalse, eq.rb, eql.rb, equal.rbtrue、およびinclude.rb はメソッド呼び出しがありました。

> rspec-expectations-2.14.2/lib/rspec/matchers/built_in/base_matcher.rb
      def diffable?
         false
      end 

> rspec-expectations-2.14.2/lib/rspec/matchers/built_in/eq.rb
        def diffable?; true; end 

> rspec-expectations-2.14.2/lib/rspec/matchers/built_in/include.rb
        def diffable?
          # Matchers do not diff well, since diff uses their inspect
          # output, which includes their instance variables and such.
          @expected.none? { |e| RSpec::Matchers.is_a_matcher?(e) }
        end 

比較可能な場合において、期待値と実測値を表示するのに使うようですね。

まとめ

今回の調査でわかったのは下記の2点です。

  1. expectmatcherにそのまま委譲する。expect{}.toで書けるのは、matcher 側がブロックを評価しているときのみ
  2. toにはmessage&blockが渡せる

ということで、matcher を知ることが、expect を書けるようになる条件なんですね。 基本の matcher はBuilt in matchers - RSpec Expectations - RSpec - Relishにありましたので、勉強していきたいと思います。

誤差をOKにするbe_with_in、範囲を比較する cover、他にも色々あるので、便利に使っていきたいです。 初めて知ったマッチャについてご紹介だけしておきます。

  • be_with_in 誤差をOKにするマッチャ

    should be_within(0.5).of(27.9)

  • exist exist? を判定するマッチャ。

    expect(obj).to exist

  • match 正規表現のマッチャ =~と同じ

    expect("a string").to match(/str/)

  • respond_to クラスのメソッドが呼び出せるかどうか。しかも引数の呼び出し個数までマッチできる!

    expect([1, 2, 3]).to respond_to(:take).with(1).argument

  • satisfy ブロックが渡せるマッチャ。何でもできちゃう。

    expect(10).to satisfy { |v| v % 5 == 0 }

  • throw throw で渡ってきたシンボルを確認するマッチャ

    expect { throw :foo }.to throw_symbol(:foo)

  • yield 一言では説明できないのでパス! yield matchers - Built in matchers - RSpec Expectations - RSpec - Relish
  • cover 範囲をマッチ。というか、range に cover なんてメソッドあったんですね。

    expect(1..10).to cover(5)

  • start_with, end_with。先頭か末尾をマッチ。というか、ファイル名がstart_and_end_with.rbなんですが

    expect("this string").to end_with "string"

使えそうなもの、使えなさそうなもの。色々あって、楽しいですね。 これからも良いrspecを書いていきたいと思います。

あ、表題shouldを捨てた理由、が書いてありませんでしたね。

  1. shouldself を対象にした handler 呼び出しでしかないから。
        syntax_host.module_eval do
          def should(matcher=nil, message=nil, &block)
            ::RSpec::Expectations::PositiveExpectationHandler.handle_matcher(self, matcher, message, &block)
          end

やってること同じじゃん、ということで、これなら expect で書いたほうがクラスを開く必要がない分が良いな、と思った次第です。文字数増えるのはイヤなんですけどね〜。

では皆さん。良いspec ライフをお送り下さい。

宣伝

MUGENUP では、rails を使いたいエンジニアを募集中です。 無限流開発、ご一緒しませんか?

大きな裁量で自社サービス開発!Rubyエンジニアをウォンテッド! - 株式会社MUGENUPの求人 - Wantedly

f:id:mgnup:20131118023234p:plain

(補講) ::って何よ?

さて、ちょっと後に回していた、::についての調査結果です。

        ::RSpec::Matchers.last_should = :should

あるクラスまたはモジュールで定義された定数を外部から参照する ためには::演算子を用います。 またObjectクラスで 定義されている定数(トップレベルの定数と言う)を確実に参照する ためには左辺無しの::演算子が使えます。 親クラスとネストの外側のクラスで同名の定数が定義されているとネストの外 側の定数の方を先に参照します。 http://docs.ruby-lang.org/ja/2.0.0/doc/spec=2fvariables.html#const

# -*- coding: utf-8 -*-
CONSTANT = "トップの定数だよ"

class Person
  CONSTANT = "内部の定数だよ"
  def top_level_constant
    ::CONSTANT
  end

  def my_class_constant
    CONSTANT
  end
end
describe Person do
  it do
    expect(Person.new.my_class_constant).to eq "内部の定数だよ"
  end

  it do
    expect(Person.new.top_level_constant).to eq "トップの定数だよ"
  end
end

(補講2) ボクの考えた最強のマッチャ

最後に皆さんに課題です。3秒で選んでください。

  it "should be true" do
    expect{Person.new}.to be_true
  end 
  1. 通る
  2. 通らない

探検!Changeマッチャ

皆さんこんにちは! 株式会社 MUGENUP 開発部の osada です。 弊社 で使っているフレームワークRuby on Rails ということで、 テストで使用する change マッチャ について調べたことを書きたいと思います。 ターゲットは change でテストを書きたい rails 初心者の方です。 かくいう私もrails歴は半年もなく、初心者の皆さんと一緒に勉強出来ればと思います。

ですがその前に、MUGENUP 開発部って何をやっているの?ということで、 簡単にご紹介したいと思います。

MUGENUP とは?

株式会社 MUGENUP は、イラストを必要とする企業さんから発注を受けて、クリエイターさん達とアートディレクターが連携して、イラストを作成しお届けしています。 それらの各工程「受注、工程分割、担当割り当て、イラストの修正や相談」などの全てを MUGENUP WORK STATION というWEBシステムで管理しています。掲示板、画像投稿に変換、リアルタイム通知、スケジュール管理などなど、機能てんこ盛りなWEBシステムです。これのお陰で、日本中、世界中のクリエイターさん達が、いつ、どこにいても一緒に働けるという、クラウドならではの共同作業を実現しています。

f:id:mgnup:20131117233603p:plain

開発部では、これら全てを、Ruby on Rails で開発しています。Rails 3, Ruby 2.0 (!) で動いています。

Change Matcher のブロックは2度評価される

さて、そんな MUGENUP では今 change 熱 が流行っています。現在、開発を株式会社KRAYさんにご協力いたただいているのですが、大変勉強になることばかりで、その一つが 良いテストを書く ということです。

綺麗なテストを書くためには、should...== を羅列するのではなく、change().from().to()を使おう、ということになるのですが、どう書いたら良いかわからないことが多かったです。

例えば、ユーザーの email を update すると、新しいメールアドレスになるということを下記のように書きました。

        expect{
          put :update, {id: @user.id, user: {email: new_email}}
        }.to change{@user.reload.email}.from(@user.email).to(new_email)

これとは別に下記の書き方もできると思います。

        expect{
          put :update, {id: @user.id, user: {email: new_email}}
          @user.reload
        }.to change(@user, :email).from(@user.email).to(new_email)

どんな書き方が良いのでしょうか?そもそも、なぜこれが動くのでしょうか?

ドキュメントがないならソースを読めばいいじゃない ということで、ソースを当たることにしました。

ソースは私の環境ではvendor/bundle/ruby/2.0.0/gems/rspec-expectations-2.14.2/lib/rspec/matchers/built_in/change.rb にありました。

インスタンス化 フェーズ

      class Change
        def initialize(receiver=nil, message=nil, &block)
          @message = message
          @value_proc = block || lambda {receiver.__send__(message)}
          @expected_after = @expected_before = @minimum = @maximum = @expected_delta = nil
          @eval_before = @eval_after = false
        end

インスタンス化はここです。ブロックを渡した時はそのままが評価されます。change(@user, :email) のように、receiverを渡した時は、lambda化されて、__send__ で渡されるようです。blockが先に来ていますので、change には blockを渡すのが王道なんでしょうか。

matcher フェーズ

        def matches?(event_proc)
          raise_block_syntax_error if block_given?

          @actual_before = evaluate_value_proc
          event_proc.call
          @actual_after = evaluate_value_proc

          (!change_expected? || changed?) && matches_before? && matches_after? && matches_expected_delta? && matches_min? && matches_max?
        end

        def evaluate_value_proc
          case val = @value_proc.call
          when Enumerable, String
            val.dup
          else
            val
          end
        end

match 判定 はこれです。インスタンス化で渡されたvalue_procが、event_proc の前後で発動し、評価されていることがわかります。

また、@value_procの返り値が Enumerable, String だった場合には、dup で返しているのも気が利いていますね。こうしないと破壊的操作がされたときに、追従してしまうことになり、テストが成立しないからだと考えられます。さらに言えばcase ... whenでは valEnumerable, Stringクラスのインスタンスかどうかを判定できることを利用した美しいコードです。

from と to と by と

match 判定の部分を見て行きましょう。

          (!change_expected? || changed?) && matches_before? && matches_after? && matches_expected_delta? && matches_min? && matches_max?

changedは実行前後の値の変化を、change_expectedby メソッドが呼ばれているかどうかで判定しています。

        def changed?
          @actual_before != @actual_after
        end

        def change_expected?
          @expected_delta != 0
        end

        def by(expected_delta)
          @expected_delta = expected_delta
          self
        end

matches_before?from メソッドが呼ばれた時に @eval_beforeフラグがセットされ、fromに渡した値と、@value_procの値が比較されます。

        def matches_before?
          @eval_before ? expected_matches_actual?(@expected_before, @actual_before) : true
        end

        def from (before)
          @eval_before = true
          @expected_before = before
          self
        end

matches_after? はその逆で、toメソッドが呼ばれた時に同じ挙動となります。

matches_expected_delta?byメソッドの判定で、実行前の実測値と差分の期待値が、実行後の実測値と同じかどうかを判定していました。

        def matches_expected_delta?
          @expected_delta ? (@actual_before + @expected_delta == @actual_after) : true
        end

これがおよそ、match 判定の部分です。 fromtobyも、フラグをセットし、実行前後の値の比較という挙動をしています。 およそ change マッチャの挙動を理解できたのではないでしょうか。 シンプルで素晴らしい構造だと思います。

そしてソースを読んだことで、知らなかったメソッドも知ることが出来ました。 それがby_at_leastby_at_mostです。

by_at_least と by_at_most

by_at_leastby_at_mostは下記となります。実行後と実行前の差分が、(少なくとも|多くとも) x である、ということのようです。いつ使うんでしょうか?ランダム要素があるときは、Hoge.stub(:rand){} で確定させて返してしまうのですが、その用途ではないのでしょうか。機会があれば、使ってみたいと思います。

        def by_at_least(minimum)
          @minimum = minimum
          self
        end

        def by_at_most(maximum)
          @maximum = maximum
          self
        end

        def matches_min?
          @minimum ? (@actual_after - @actual_before >= @minimum) : true
        end

        def matches_max?
          @maximum ? (@actual_after - @actual_before <= @maximum) : true
        end

まとめ

さて、長々と、Change class を見てきましたが、初めの疑問、change のブロックの中に reload を書くか、書かないか、はどちらが良いのでしょうか?

changeブロックが2回評価されることを考えると、1度目のreloadは無意味なのにDBアクセスを起こすので、changeの外にだすべきなのかな、と個人的に思います。ただ時間を計測したところそれほど差異が無かったため、もう少し突き詰めたいところです。

あと、今更で大変恐縮なのですが、changeマッチャについて知りたい人は、かの有名な Rubyist Magazine - スはスペックのス 【第 1 回】 RSpec の概要と、RSpec on Rails (モデル編)を読んでください。これだけ読めば、テストが書けるようになります!

では、最後に宣伝を。 MUGENUP では、rails を使いたいエンジニアを募集中です。 無限流開発、ご一緒しませんか?

大きな裁量で自社サービス開発!Rubyエンジニアをウォンテッド! - 株式会社MUGENUPの求人 - Wantedly

f:id:mgnup:20131118023234p:plain