36
34

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

RubyMineAdvent Calendar 2014

Day 19

あの娘(萩本欽一さん(73))ぼくがTDDでオールグリーン決めたらどんな顔するだろう

Last updated at Posted at 2014-12-18

まずは↓の動画をご覧いただこう。
この記事を読み終える頃、諸君の開発環境はこうなる。

全日本仮装大賞/ピンポン(YouTube)
全日本仮装大賞/ピンポン(YouTube)

では、ドンといってみよう!

すみません、何をおっしゃっているのやらサッパリです

ええっ! ウソだろ!
普通、わかるよね。
わかってくれよ! (壁ドン!)
チッ、壁、殴っちまった...。

仕方ないなぁ。じゃあ、ちょっとさ。テスト実行してみてよ。そうそう、そのRubyMineで。

RubyMine.png

そう。これこれ!
わかったよね。

では、ドンといってみよう!

ごめんなさい、本当に、一体何のことやら...

えっ! マジで!
なんでだよ!
わかってくれよ! 俺の気持ち! (壁ドンッ!)

しょうがないなぁ。
じゃ、今度はならべて見てみようか。そうそう、こんな風にね。

RubyMine2.png
pingpong2.png

そうそう!
そういうこと。

なるほど

  • 大将、ようやくわかりました!
  • そうか、ようやく気づいてくれたんだね、僕の気持ちに。嬉しいよ。
  • えっ、そんな。
  • 今夜は... 俺が合格点をあげるまで、帰さないぜ? (欽ドン!)
  • (キャッ、大将の欽ドン... 素敵...!)

欽ちゃん駆動開発手法(KDD)とは

一応、丁寧に説明しておくと、KDDとは次のようなものを指す。

  • 背景: 一般的なプログラミング環境では、せいぜい「視覚」「触覚」程度しか使われていない。これでは人間の潜在能力を最大限に引き出しているとは言いがたい。近年、例えば「聴覚」など、新たな刺激を加えることにより、能力の増幅を促すような開発手法が望まれている。
  • 欽ちゃん駆動開発手法(KDD)とは
    • TDD, BDDといった既存の開発手法をベースとし、聴覚などの他の感覚器官による刺激を加えることで能力の向上を実現するものである。
    • テストの進捗に応じてバーが伸び、さらに、テスト結果に応じて、適切なメロディが鳴り響くよう改良を施す。これにより(以下400文字略)
    • さらに、進捗状況に応じて、都度、音(採点音)が鳴らす。これにより(以下200文字略)

欽ちゃん駆動開発手法(KDD)をはじめる手順

ということで、さっそく具体的な説明に移っていきたい。
この記事では、とりあえず以下の環境を前提として説明する。

  • RSpec3
  • OS X
  • RubyMine (IDE)

ただし、ここで紹介している「欽ちゃん駆動開発手法(KDD)」の実現手法は、それぞれの環境にさほど強くは依存していない(はずだ)。次節以降で紹介している外部プログラムも、ほとんどマルチプラットフォーム対応のようである。おそらく、ちょっとくらい環境が違っていても、すぐに代替手段が見つかるだろう。

そういうわけで、「Windows Vista上の秀丸で開発を強いられている僕は『欽ちゃん駆動開発手法(KDD)』を導入することができず、従って死ぬしか無い」という程には自分を追い込む必要はない。むしろ、「やったー『欽ちゃん駆動開発手法(KDD)』を僕の環境にも移植できたよ! これでアドベントカレンダーの枠が1つ埋まるね!」程度の気楽さで構えていてほしい。

テスト用のSpecファイルを用意しよう

でははじめに、RubyMineを立ち上げて、テスト用のSpecファイルなどを作成しておく。なお、これは一応RubyMineのアドベントカレンダーの記事であるが、この程度の操作は触っていれば体で覚えるだろうということで特に細かい説明はしない(ひどい)。

Gemfile
source 'https://rubygems.org'

group :development, :test do
  gem 'rspec'
end
spec/spec_helper
# 現時点では空ファイルでよい
spec/my_example_spec.rb
require_relative 'spec_helper'

describe 'My behaviour' do
  before(:each) do
    sleep(0.1)
  end

  it 'should_be_true_000' do
    expect(true).to be_truthy
  end

  it 'should_be_true_001' do
    expect(true).to be_truthy
  end

  it 'should_be_true_002' do
    expect(true).to be_truthy
  end
  
  # 略

  it 'should_be_true_120' do
    expect(true).to be_truthy
  end
end

実装は特に用意する必要はない。テストごとに0.1秒sleepするだけの、何の変哲もないSpecである。

ここでCTRL+rを押すとテストが始まるはずである。実は、私の手元のRubyMineではだいぶキーバインドをいじってしまったので、最初からこのキーバインドだったものかどうか、どうも自信がない。適宜、なんとか格闘していただきたい。(どうも効かない場合は、メニューから Run... > Run 'All specs in spec...' を選ぶ、など、対処してほしい。)

さて、テストを実行することができたら、いったんRubyMineは終了させてしまってよい。以後、KDDを実現するための環境構築を行っていく。

合格音を鳴らしてみよう

まずは自分の端末から、「合格音」を鳴らすことができないようでは話にならない。「合格音って何?」という向きは、この素材(YouTube)をブラウザで開けば聴くことができるので、予習していただきたい。

さて、これをテストがオールグリーンになるたびに再生したいわけであるが、どのようにすればよいだろうか。もちろん、自分でWAVファイルを作成することなどは避けたい。さしあたり、テストが通るたびにこのリンクをWebブラウザで開く、といった方法でも目的は達成することができる。しかし、毎回ブラウザが開くようではいかにも鬱陶しい。そこで、ブラウザを使わずにこれを鳴らす方法を考えよう。

ではここで、MacPortsをお使いの皆さんは、次のようにコマンドを入力していただきたい。これで、コマンドラインから再生する手段が出に入る(Homebrewの場合も似たような感じで入るはずである)。

% sudo port install youtube-dl mplayer2 &&
  youtube-dl 'https://www.youtube.com/watch?v=eWahjyqFMoM' -o - | mplayer -

ソフトウェアのインストールが完了すると、お手元の端末から合格音が鳴り響くはずである。

さて、合格音を聞いてみて、どんな気持ちがしただろうか。
今までにないトキメキや、無限の可能性を感じることができただろうか。
これが「欽ちゃん駆動開発手法(KDD)」の醍醐味である。

では、この合格音を出す仕組みをシェルスクリプトにしてしまおう。いざRSpecを実行したときに、テストが成功したら、何らかの仕掛けでこのスクリプトを呼び出し、合格音を鳴らしてしまえばよい、という魂胆だ。

/usr/local/bin/kinchan-ok.sh
#!/bin/sh
if [ ! -e /tmp/kinchan-ok.wav ]; then
        youtube-dl 'https://www.youtube.com/watch?v=eWahjyqFMoM' -q -o /tmp/kinchan-ok.wav
fi
mplayer /tmp/kinchan-ok.wav -ss 1 -vo null > /dev/null 2>&1
% chmod +x /usr/local/bin/kinchan-ok.sh
% /usr/local/bin/kinchan-ok.sh  (鳴らしてみよう!)

ここで使ったmplayerのオプションを軽く説明しておこう。詳しく知りたい向きはmplayer -hを眺めて欲しい。

  • -ss <sec>: 再生開始位置の指定。sec秒目から再生を始める(hh:mm:ss 形式でもOK。)
  • -vo null: 動画そのものは画面に出さず、音声のみを再生する。

不合格音を鳴らしてみよう

不合格になってしまった場合の音は、この素材(YouTube)を使わせていただくことにする。

「合格音を鳴らしてみよう」の場合と、やることは同じだが、この動画では前半の7.5秒分ほどに、ほかの音が入ってしまっている。そこで、7.5秒目から再生を開始するようにしてあげる必要がある。

とはいえ、恐れることはない。これもmplayerの-ss <seconds>オプション一発で解決する話だ。

/usr/local/bin/kinchan-ng.sh
#!/bin/sh
if [ ! -e /tmp/kinchan-ng.wav ]; then
        youtube-dl 'https://www.youtube.com/watch?v=PGL2eMuLK3Q' -q -o /tmp/kinchan-ng.wav
fi
mplayer /tmp/kinchan-ng.wav -ss 7.5 -vo null > /dev/null 2>&1
% chmod +x /usr/local/bin/kinchan-ng.sh
% /usr/local/bin/kinchan-ng.sh  (鳴らしてみよう!)

採点音を鳴らしてみよう

さて、最後に「採点音」を鳴らすことにチャレンジしよう。点数が入るたびにトゥルルルル...ルルル!という、アレである。

採点音を鳴らすにあったっては、それぞれの音ごとにWAVファイルを用意したり、動画などからサンプリングしたWAVファイルをもとに周波数を変えて再生させてみる、など、さまざまな方法が考えられるが、ここではRubyからUniMIDIというGemを利用し、MIDIを介してGarageBandにそれらしい音を演奏してもらう、ということにする。

以下、Macでの設定方法をとりあえず箇条書きにする。より丁寧な説明はRubyとGarageBandでルビーの指環を演奏してみた(Qiita)を参照して欲しい。

MIDI関連の設定 (Macの場合)

  • アプリケーション > ユーティリティ > Audio MIDI 設定.app
  • ウインドウ > MIDIスタジオ
  • 「IACドライバ」をダブルクリック
  • 左下「ポート」を選択し、「装置はオンライン」をチェック
  • 「IACドライバのプロパティ」を閉じ、アプリケーションを終了する。

GarageBandの準備 (Macの場合)

  • 適当にプロジェクトを作る
  • 適当な楽器を選択する
  • 「ライブラリ」より、"Synthesizer > Lead > Analog Mono Lead"あたりを選択する(もちろん他の楽器を使うことにしてもかまわない。いろいろと試して欲しい。)
  • GarageBandはそのままにしてターミナルに戻る(隠してしまってもよい)

RubyからMIDIを鳴らしてみよう

これでRubyから演奏する準備ができた。さて、次のようにUniMIDIをインストールしよう(もちろんBundlerを使ってもかまわない)。

% gem install unimidi

では、適当に音を鳴らしてみる。

midi-test.rb
#!/usr/bin/env ruby
require 'unimidi'

40.times {|i| notes << 40 + i }
duration = 0.1

UniMIDI::Output.use(:first) do |output|
  notes.each do |note|
    output.puts(0x90, note, 100) # 鍵盤を押す
    sleep(duration)              # 音が響くのを待つ
    output.puts(0x80, note, 100) # 鍵盤から指を離す
  end
end
% ruby midi-test.rb

音色があまりそれらしくないかもしれないが、一応、音は聴こえると思う。
音がうまく鳴らない場合は、次の3点を確認していただきたい。

  1. まず、耳にバナナの類が刺さってないかを確認
  2. 次に、端末のボリュームを確認
  3. 最後に、GarageBandが起動していることを確認
    • GarageBand自体は、バックグラウンドに居てもいい

さて、これでRubyからMIDI楽器を弾けるようになった(ことにする)。
これらの成果をRSpecに組み込めば、KDDが実現するはずだ。

RSpecに組み込んでみよう

さて、音は鳴るようになったものの、これからどうしたものか... という心持ちで/Application/RubyMine.app以下を散策していると、/Application/RubyMine.app/rb/testing/patch/bdd/teamcity/spec/runner/formatter/teamcity/ディレクトリ以下にそれらしいものを発見できるはずである。

細かい部分をザックリと省くと、だいたい以下のような構造になっている。

rspec3_formatter.rb
class TeamcityFormatter < RSpec::Core::Formatters::BaseFormatter
  RSpec::Core::Formatters.register self, :start,
                                   :example_started, :example_passed,
                                   :example_pending, :example_failed,
                                   :dump_summary
  def start(count_notification)
    super
    # 何かする
  end

  def example_started(example_notification)
    # 何かする
  end

  def example_passed(example_notification)
    # 何かする
  end

  def example_pending(example_notification)
    # 何かする
  end

  def example_failed(example_notification)
    # 何かする
  end

  def dump_summary(summary_notification)
    duration = summary_notification.duration
    example_count = summary_notification.example_count
    failure_count = summary_notification.failure_count
    pending_count = summary_notification.pending_count

    # 何かする
  end
end

眺めているうちに、だいたい察しはつくかと思うが、要はRSpec::Core::Formatters#registerを呼び出しておけば、RSpecが適宜イベントに応じたメソッドをコールバックしてくれる、という寸法になっている(ほんとうは、さらにspec/spec_helperなどに、ちょろっと数行書く必要がある(後述))。

この手のクラスはRSpecの世界では「フォーマッタ」と呼ばれているようである。フォーマッタを差し替え、あるいは追加することで、テストの経過や結果の出力を多様なフォーマットで表示したり、結果を通知センターに表示する、などの機能を追加することができるようである(RubyMineはデフォルトで通知してくれるけど)。
そのほか、なんだかキラキラする感じのフォーマッタ(nyan-cat-rspec-formatter)だとか、正直ちょっとREADMEを見ていてもよくわからないけど、なんだか楽しそうなフォーマッタなど、無限の地平が広がっていることに気づくだろう。たとえば、次のような既存の枠組みを超越するようなフォーマッタを実現することも不可能ではない。

  • テストがパスする度に、野々村竜太郎元兵庫県議が日本語ラップでイルなリリックをキュッキュと繰り出すフォーマッタを実装する
  • 画面上部から流れてくる音符にあわせてテストをパスさせることで、コンボを繋いでいく的な音ゲーフォーマッタを実現する
  • テストの達成具合に応じて、観賞用モミの木に標準搭載されているLEDをチカチカさせ、その様子を眺めることにより、何かしらの精神の高みに到達する

さて、先のRubyMine.appバンドルの下にあるフォーマッタの話に戻ろう。このファイル(rspec3_formatter.rb)を自分の端末に発見した君は、「よし俺は音を鳴らすぜ!」とばかりにsudo vi rspec3_formatter.rbとコマンドを繰り出し、ゴリゴリと修正を始めてしまうかもしれない。もちろんそれはそれで一向にかまわないのだが、いろいろ後先を考えて、もう少し穏当な方法で実現することにしよう。

まずはプロジェクトのGemfileを次のように更新する。

Gemfile
source 'https://rubygems.org'

group :development, :test do
  gem 'rspec'
  gem 'unimidi'
end

テストのためにunimidiを使うというのはいかにも斬新だが、深くは気にしないことにする。
次に、KDDの要となる、独自のフォーマッタを作成する。

lib/kinchan-formatter/formatter.rb
require 'thread'
require 'timeout'
require 'unimidi'

class KinchanFormatter < RSpec::Core::Formatters::BaseFormatter
  RSpec::Core::Formatters.register(self, :start,
                                   :example_passed, :example_failed,
                                   :dump_summary)

  def initialize(output)
    super

    @max_gauge = 84   # [*1] ゲージの目盛の個数

    @total = nil
    @succeeded = 0
    @prev_progress = 0

    # @queue にパス率(current_progressの結果)の数値をpushすると、
    # @synthesizer が別スレッドで音を鳴らしてくれる
    @queue = Queue.new
    @synthesizer = Thread.new { do_synthesizer(@queue) }
  end

  def start(count_notification)
    @total = count_notification.count
  end

  def example_passed(example_notification)
    @succeeded += 1

    if current_progress > @prev_progress
      on_tick_gauge(current_progress)
    end

    @prev_progress = current_progress
  end

  def example_failed(example_notification)
  end

  def dump_summary(summary_notification)
    failure_count = summary_notification.failure_count

    if failure_count <= 0
      on_succeeded
    else
      on_failed
    end

    on_finish
  end

  private

  def on_tick_gauge(scale)
    raise ArgumentError unless (0..@max_gauge).cover?(scale)
    @queue << scale
  end

  def on_succeeded
    fork do
      Process.daemon
      system('/usr/local/bin/kinchan-ok.sh')
    end
  end

  def on_failed
    fork do
      Process.daemon
      system('/usr/local/bin/kinchan-ng.sh')
    end
  end

  def on_finish
    @queue << :exit
    Thread.pass
    @synthesizer.join
  end

  # テストの進捗率を示す Fixnum を返す
  #==== Returns
  # Fixnum (0..@max_gauge)
  def current_progress
    (@succeeded.to_f / @total * @max_gauge).to_i
  end

  def do_synthesizer(queue)
    out = UniMIDI::Output.use(:first)     # [*1] 楽器の初期化
    duration = 0.1
    note, prev = nil, nil

    loop do
      begin
        timeout(duration) do
          note = queue.pop                # [*2] メインスレッドからのpush待ち
        end
      rescue Timeout::Error
        # NOP
      ensure
        out.puts(0x80, prev, 100) if prev # [*4] note off (鍵盤を離す)
      end

      case note
      when :exit
        break
      when nil
        # NOP
      when Fixnum
        note += 40
        out.puts(0x90, note, 100)         # [*3] note on (鍵盤を押す)

        note, prev = nil, note
      end
    end
  end
end

詳しくはコメントを眺めていただきたいが、あまり大したことはしていない。
そして、実は、@max_gaugeを環境に応じて適切なものに変えてもらう必要がある。これはなにかというと「RubyMineの画面に表示されるゲージの目盛の個数」である。私の環境ではたまたま84個ほどだが、大きいサイズの画面だとこれが変わってくるようである。適切な値にしてもらいたい。

さて、これをspec/spec_helperから呼べば完成である。

spec/spec_helper
require_relative '../lib/kinchan-formatter/formatter.rb'

RSpec.configure do |conf|
  conf.add_formatter(KinchanFormatter)
end

さあ、RSpecを実行してみよう。CTRL+rを押せばよい(はずだ。だめだったらRunメニューから実行しよう)。

これで君も立派なKDD野郎である。
KDDの世界へようこそ!

追記

現在のKinchanFormaterでは、テスト間の時間的な間隔があまりに短いと、採点音の鳴りが悪いという問題点があるようだ。note on~note offの間隔が短すぎることが原因であり、#do_synthesizerのそのあたりを直せばよいのだが、時間がなくて取り掛かれていない。そのうち直したい。(実は作ってから1年ほど放置している)

36
34
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36
34

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?