Testing
これまで取り上げてきたすべてのものは、Objective-C と Ruby のごく一般的なものでした。もちろん両者は構文が異なり、Ruby と Objective-C それぞれのメリットについて一日中論争できることでしょう。しかし、これまで扱ってきたものは通常の iOS のツールを使用しており、パフォーマンスについては同等です。それでは、RubyMotion 独自のものについて紹介しましょう。
自動テストは本当に最高なものです。ここでは私は押し売りはしませんが(私よりもそれにふさわしい方々がいます)、ソフトウェアを構築する際に、テストを書いて疑う余地なくコードの整合性を検証することはかなり生産的でしょう(常にそうではないですが、良い考えでしょう)。もし将来あるいは現在に何かが壊れている場合、すぐにそれを知ることができます。それはなんてクールなことでしょうか?
テストすることは素晴らしい考えだとみなさん賛成しますが、実際にテストを記述するときに多くのエンジニアは当然のことながら躊躇します。それには魅力的な機能も計測可能なパフォーマンス向上もありません。将来への保証です。とりわけ短期的なプロジェクトでは、テストにおける工数はもっともインパクトのないものと言うのは簡単です。
さてどうしましょうか?できるだけ簡単に、途切れずに書けるようなテスト環境を作るのです。
Ruby コミュニティーにはテストの文化が根付いており、プログラマーのオーバーヘッドがごくわずかな、非常に簡潔なテストプラクティスが確立されています。対照的に、Apple による Objective-C で書かれた iOS アプリのテスト自動化手法は JavaScript の記述を必要とします。Objective-C でテストを記述することができるサードパーティのライブラリもありますが、正直、RubyMotion のテストフレームワークはそれらをとても驚かせるものでしょう。
どれだけ簡単なのか見てみましょう。
Unit Testing
真新しい RubyMotion プロジェクトを motion create Tests
で作成し、cd
でプロジェクトへ移動しましょう。以前、motion create
でデフォルトのフォルダが作成されることを書きましたが覚えてるでしょうか?ここでは、新しく spec
を見ていくつもりです。
RubyMotion は ./spec
フォルダの *.rb
ファイルの一つ一つからテストを読み込みます。RubyMotion アプリを作成したときに、./spec/main_spec.rb
にデフォルトのテストが次のように作られます。
describe "Application 'Tests'" do
before do
@app = UIApplication.sharedApplication
end
it "has one window" do
@app.windows.size.should == 1
end
end
少し時間を取ってすべての単語を本を読むように声に出して読み上げてみましょう。シンプルで表現力があるでしょう?@app.windows.size.should == 1
は、「もし .size
の値が 1 と等しくなければテストが失敗します」ということを きっちり 行います。
.should
は他のどのような種類のオブジェクトでも同様に動作します。
@app.nil?.should == false
[1,2,3].should.not == [1,2,3,4]
@model.id.should == example_id
describe
と it
はテストの構造化に役に立ちます。これらの組み合わせを次のような文として読むことができます。"'Tests' アプリケーションは一つの window があります"。 describe
ブロックにはたくさんの it
を書くことができ、it
にはたくさんの期待する値を書くことができます。describe
ブロックは入れ子にすることもできます。
生成されたテストで最後に取り上げるのは before
ブロックです。before
ブロックのコードは各テストの事前に実行されます。オブジェクトをリセットしたり、テスト項目の状態を設定するのに適した場所です。
これらの楽しいおもちゃを実際にどのように使うのでしょう? Terminal で、rake spec
を実行してみてください。Spec の実行用に、アプリケーションがビルドされます。
rake spec
を試してください。次のような、いくつかの結果を確認できます。
うぉぉ、私たちのアプリケーションはウィンドウが無いようです。AppDelegate
を修正してみましょう。
class AppDelegate
def application(application, didFinishLaunchingWithOptions:launchOptions)
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.applicationFrame)
@window.makeKeyAndVisible
true
end
end
再び rake spec
を実行し、失敗が無いことを確認しましょう。
今まさに、みなさんはバグを修正するために自動化されたテストをもちいました。
それはオブジェクトのプロパティの動作をテストする方法の要点です。特定のものが存在するか、または属性と等しいかをチェックするのに非常に便利です。ただ、イベントを引き起こすために、おそらく多くの特定の機能や内部機能を呼び出す必要があります。たとえば、タップしたばかりのボタンの属性が変更されたかを確認したい場合には、手動でボタンのコールバックを起動する必要があります。よりよい方法があったなら、、、
Functional Testing
実はそれはあります。RubyMotion もまた、タップやスワイプのような UI イベントを引き起こして、その副作用を調査する機能テストについて素晴らしいサポートをしています。button.callback.call
のように何かをする代わりに、tap button
を使うことができます。すばらしいでしょ?
これら機能テストは素晴らしいものですが、単一の UIViewController
をテストすることを意図しており、複数のコントローラを使うことはしません。
これを動作させるために、はじめに新しい UIViewController
のサブクラスが必要となります。./app/ButtonController.rb
を作成し、次のようにボタンとコールバックを用意します。
class ButtonController < UIViewController
def viewDidLoad
super
@button = UIButton.buttonWithType(UIButtonTypeRoundedRect)
@button.setTitle("Test me title!", forState:UIControlStateNormal)
@button.accessibilityLabel = "Test me!"
@button.sizeToFit
self.view.addSubview(@button)
@button.addTarget(self, action:'tapped', forControlEvents:UIControlEventTouchUpInside)
end
def tapped
p "I'm tapped!"
@was_tapped = true
end
end
新しく登場した accessibilityLabel
以外は、ごく普通の UIButton
パターンですね。
accessibilityLabel
はそれぞれの UIView
で利用できる文字列を扱うプロパティです。デバイスの VoiceOver 機能を利用する際にオペレーティングシステムによって読み上げられるテキストとして使用されます (それなので、余計なもので埋めないでください)。なぜ、今これを導入するのでしょう? RubyMotion の機能テストは accessibilityLabel
をもとにビューを探します。メモリ上のどこかに存在するだけでなく、確実にテストするビューが階層へ追加されます。
最後に、AppDelegate
でコントローラをウィンドウに関連づける必要があります。
def application(application, didFinishLaunchingWithOptions:launchOptions)
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.applicationFrame)
@window.makeKeyAndVisible
@view_controller = ButtonController.alloc.initWithNibName(nil, bundle:nil)
@window.rootViewController = @view_controller
true
end
rake
を実行し、ボタンをタップすると Terminal に "I'm tapped!" が表示されることを確認できます。では、変数が用意されてるか確認するために Test を書いてみましょう。
main_spec.rb
で、新しく describe
ブロックを追加します。
describe "button controller" do
tests ButtonController
it "changes instance variable when button is tapped" do
tap 'Test me!'
controller.instance_variable_get("@was_tapped").should == true
end
end
いいね!私たちは新しい遊び道具を手に入れたようです。
tests <class>
は describe
と指定された UIViewController
のクラスを結びつけます。これは、UIWindow
とテストするコントローラをインスタンス化します。テスト内でウィンドウとコントローラにそれぞれ self.window
と self.controller
でアクセスすることができます。これはテストの手続きを壊してしまうような、余計な UI やステートが無い状況を保証してくれます。
コントローラをテストするために Spec を用意し、it
ブロックでアサーションを行います。もう一つの新しいおもちゃは tap
です。 tap
は最初の引数として UIView
のオブジェクト か accessibilityLabel
の値を取ります。UIButton
と UILabel
ではデフォルトで自分の title
の値を accessibilityLabel
として使用できます。今回の実装例では、テスト時、他のビューにアクセスするときの方法を示すためにデフォルトの挙動を用いませんでした。
tap
の他にも、flick
, drag
, pinch_close
, pinch_open
, そして rotate
も使うことができます。それぞれについて、RubyMotion の full documentation で確認してみてください。
rake spec
を実行し、実際に動作することを確認してみましょう。
2 specifications (2 requirements), 0 failures, 0 errors
Wrapping Up
私たちは RubyMotion で難なくテストを書くことができます。もし、みなさんがこれまでプロジェクトでテストを書かれていないのでしたら、今から始めない理由はありません。
今日はテストについてどんなことを学んだでしょう?
- RubyMotion は
./spec
ディレクトリからテストを読み込みます。 - テストは
describe
とit
ブロックの集まりです。これらはラベルを引数に取り、同じようなテストをグループ分けするのに役に立ちます。 <any object>.should
は値が等しいかチェックする際に用います。 例 :greeting.should == "hello"
UIViewController
には機能テストがあり、tap
やpinch
のようなイベントをシミュレートしてくれます。describe
ブロックで、これらの機能を有効にするためにtests <controller class>
を用います。tap <accessibility label>
は、ビューに設定したaccessibilityLabel
プロパティを引数に設定し、tap
をコードから実行します。