Hatena::ブログ(Diary)

ぬいぐるみライフ(仮) RSSフィード

2014-01-01

Cucumber+Mockで悩んでいる

CucumberでMockに対するmethod呼び出しのexpectation(should_receive)をどのように書くか悩んでいる.

動くけど好きじゃない例

CucumberのexamplesにMockを使う例がある.

https://github.com/cucumber/cucumber/tree/master/examples/rspec_doubles

この例ではMockの作成とmethod呼び出しのexpectationの設定をGiven節に書いている.Then節には何も書いていない.

mocking.feature

Feature: Mocking
  In order to test external stuff
  I want to mock

  Scenario: Mock a transmogrifier
    Given I have a cardboard box
    When I poke it all is good

calvin_steps.rb

class CardboardBox
  def initialize(transmogrifier)
    @transmogrifier = transmogrifier
  end
  
  def poke
    @transmogrifier.transmogrify
  end
end

Given /^I have a cardboard box$/ do
  transmogrifier = double('transmogrifier')
  transmogrifier.should_receive(:transmogrify)
  @box = CardboardBox.new(transmogrifier)
end

When /^I poke it all is good$/ do
  @box.poke
end

これでテスト自体は問題なく動くのだけど,いくつか不満点がある.

不満点
  • "Given I have a cardboard box"で実行されるコードが"I have a cardbox"以上のことをやっている
    • transmogrifierに関するGiven節を追加してコードを分割すればいいのかもしれないけど…
  • method呼び出しのexpectationがGiven節に,methodの戻り値のassertionがThen節に分散してしまう

こんなふうに書きたい

method呼び出しのexpectationもmethodの戻り値のassertionもThen節に書きたい.

mocking.features

Feature: Mocking
  In order to test external stuff
  I want to mock

  Scenario: Mock a transmogrifier
    Given I have a cardboard box
    When I poke it
    Then all is ok

calvin_steps.rb

class CardboardBox
  def initialize(transmogrifier)
    @transmogrifier = transmogrifier
  end
  
  def poke
    @transmogrifier.transmogrify
  end
end

Given /^I have a cardboard box$/ do
  @transmogrifier = double('transmogrifier')
  @box = CardboardBox.new(transmogrifier)
end

When /^I poke it all is good$/ do
  @box.poke
end

Then /^all is ok/ do
  @transmogrifier.should_receive(:transmogrify)
end

しかし,このテストは失敗する.Then節はWhen節よりもあとに実行されるため,When節で@box.pokeが呼ばれる時点で@transmogrifierにexpectationがセットされていないことが原因.

以上

どうすればこの手のテストがスマートに書けるのかまだよく分かってないけど,Then節がWhen節よりも後に実行される以上,Given節にexpectationを書く方法しかないんだろうなーと思ってる.

もっといい書き方がありましたらぜひ教えて下さい.

2013-02-28

Ruby 2.0.0-p0がリリースされてた

Ruby 2.0.0-p0 リリースノート

わーい.開発・リリースに関わった皆さん,本当におつかれさまでした&ありがとうございました.

非互換

リリースノートにRuby 2.0.0の「特筆すべき非互換」について書いてあったので真っ先に確認してみた.

デフォルトのスクリプトエンコーディングがUTF-8

これのおかげで各ファイルにUTF-8用のマジックコメントをわざわざ書く必要がなくなった.わーい.早くRuby 1.9をdeprecateしよう!(ムリ

でもこの変更によって一部のプログラムが遅くなるなどの悪影響もある様子.まあ当たり前か.

iconv削除

Ruby 1.9から登場したString#encodeなどを使ってくださいという話.

ABI互換性がなくなっている

「1.9の.so, .bundleファイルをコピーするな」ということだけ気をつけておけばいいらしい.

#lines, #chars, #codepoints, #bytesメソッドがEnumeratorではなくArrayを返すようになった

結構うれしい変更.今まで文字列の各文字のArrayを取ってくるときにsome_string.chars.to_aと書いていたけど,これがsome_string.charsだけでよくなる.Array#to_aはselfを返すので,to_aがついてる古いコードも一応動くので安心.

Object#inspectの結果が#to_sではなく#<ClassName:0x...>のような文字列になった

今のところ特に思うところはないかな.

以上

さっそくrvm get head && rvm install 2.0.0 && rvm use 2.0.0 --defaultした.普段使っているRubyGemsが問題なく動いてくれることを祈ろう.

その他新機能については後日調べてみようかな.リリースノートからもリンクされてた以下の記事が分かりやすそう.

http://blog.marc-andre.ca/2013/02/23/ruby-2-by-example/

2012-09-11

うちのbotがエアコンの操作方法を覚えた

f:id:mickey24:20120911230127p:image

ぼくの自宅のエアコン限定ですが,mickey24_botがエアコンを操作できるようになりました.

どんな機能なの

うちのbotにreplyで指示を送ると,うちのエアコンに赤外線が発射されます.指示の出し方の例は,

@mickey24_bot 冷房を28℃に設定して
@mickey24_bot 暖房を20℃に設定して

など.これで外出先からエアコンを操作しておけば,帰宅時にエアコンの効いた快適な部屋がぼくを出迎えてくれます.

エアコンの運転モードは冷房と暖房,温度の設定範囲は16〜30℃(華氏温度は後日対応予定)です.

どうやって実現してるの

Arduino Unoに赤外線LEDを繋いで,Arduino Unoでエアコン用赤外線リモコンの信号をエミュレートさせています.

Arduinoと赤外線LEDは下のような感じになっています.ふたつの赤外線LEDがそれぞれ部屋の照明とエアコンの方を向いています(ちなみに部屋の照明もArduinoから赤外線で操作可能になっています).このArduinoはUSBで自宅サーバと繋がっていて,USBシリアル通信で操作することができるようになっています.

f:id:mickey24:20120911230126j:image:w600

注意事項

  • だれでも操作できます.

以上

帰宅した時に既に部屋が冷えているのは思った以上に快適ですね.癖になりそう.

実際に書いたプログラムやサーバの詳細については後日記事を書く予定です.

関連記事

2012-08-11

unite.vimで:UniteWithBufferDirのsourceにfile_recを指定すると失敗する

現在開いているバッファのディレクトリでUniteのfire_rec (file_rec/async)を実行したいことが結構あるのだけど,以下のように:UniteWithBufferDirとfile_recを組み合わせて実行すると何故か結果がひとつも表示されない.

:UniteWithBufferDir file_rec

f:id:mickey24:20120811000559p:image

:Uniteとfire_recの組み合わせは一応動く.

:Unite file_rec

f:id:mickey24:20120811000558p:image

何が原因なのだろう.同様に:UniteWithCurrentDir file_recも動いてくれない.

仕方がないので,.vimrcに以下のように書いてバッファが切り替わるたびにバッファのディレクトリにcdするようにした.これなら:Unite fire_recを使ってカレントバッファのディレクトリでfile_recが実行できる.

augroup AutoCD
  autocmd!
  autocmd BufEnter * lcd %:p:h
augroup END

2012-08-09

JavaScriptの等値演算は==ではなく===を使うようにする

JavaScriptの==は予想外の結果を返すことがある

JavaScriptの等値演算子==は,左右のオペランドの型が等しい場合,両者の値が等しければtrueに,そうでなければfalseになります.

しかし,2つのオペランドの型が違う場合,==はオペランドを型変換してから比較することがあります.この型変換がなかなか曲者で,比較する値と型の組み合わせによっては予想外の結果が得られることがあります.

以下に例を示します.

'' == '0'          // false
0 == ''            // true
0 == '0'           // true

0 == []            // true
0 == {}            // false

0 == false         // true
0 == [0]           // true
0 == [false]       // false

false == 'false'   // false
false == '0'       // true

false == undefined // false
false == null      // false
null == undefined  // true

' \t\r\n ' == 0    // true

上の結果は冗談ではなく本当です.ぜひお使いのブラウザで試してみてください.

もはや何が何だかという感じですよね.しかしこれはECMAScriptの仕様通りの評価結果となっています.このように==の結果が予想外だったせいでバグが発生した場合,デバッグは困難を極めるでしょう.

===はオペランドの型変換を行わない

一方,===はオペランドの型と値が等しい時に限りtrueになります.そのため,上記のような型が違うケースはすべてfalseになります.

'' === '0'          // false
0 === ''            // false
0 === '0'           // false

false === 'false'   // false
false === '0'       // false

0 === false         // false
0 === []            // false
0 === {}            // false

0 === false         // false
0 === [0]           // false
0 === [false]       // false

false === undefined // false
false === null      // false
null === undefined  // false

' \t\r\n ' === 0    // false

==と比べると直感的でまともな結果が得られることが分かると思います.

自動的に型変換させてまで異なる型の値を比較したいことはそうそうないと思うので,等値演算では普段から===を使う癖をつけたほうがいいでしょう.また,同じ理由から非等値演算では!=よりも!==を使うようにしましょう.

参考資料

「==」はJavaScript: The Good Partsという本の中でも「JavaScriptのBad Parts」のひとつとして紹介されています.