Fork me on GitHub

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

describeit はテストの構造化に役に立ちます。これらの組み合わせを次のような文として読むことができます。"'Tests' アプリケーションは一つの window があります"。 describe ブロックにはたくさんの it を書くことができ、it にはたくさんの期待する値を書くことができます。describe ブロックは入れ子にすることもできます。

生成されたテストで最後に取り上げるのは before ブロックです。before ブロックのコードは各テストの事前に実行されます。オブジェクトをリセットしたり、テスト項目の状態を設定するのに適した場所です。

これらの楽しいおもちゃを実際にどのように使うのでしょう? Terminal で、rake spec を実行してみてください。Spec の実行用に、アプリケーションがビルドされます。

rake spec を試してください。次のような、いくつかの結果を確認できます。

test failure

うぉぉ、私たちのアプリケーションはウィンドウが無いようです。AppDelegate を修正してみましょう。

class AppDelegate
  def application(application, didFinishLaunchingWithOptions:launchOptions)
    @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.applicationFrame)
    @window.makeKeyAndVisible
  true
  end
end

再び rake spec を実行し、失敗が無いことを確認しましょう。

test pass

今まさに、みなさんはバグを修正するために自動化されたテストをもちいました。

それはオブジェクトのプロパティの動作をテストする方法の要点です。特定のものが存在するか、または属性と等しいかをチェックするのに非常に便利です。ただ、イベントを引き起こすために、おそらく多くの特定の機能や内部機能を呼び出す必要があります。たとえば、タップしたばかりのボタンの属性が変更されたかを確認したい場合には、手動でボタンのコールバックを起動する必要があります。よりよい方法があったなら、、、

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.windowself.controller でアクセスすることができます。これはテストの手続きを壊してしまうような、余計な UI やステートが無い状況を保証してくれます。

コントローラをテストするために Spec を用意し、it ブロックでアサーションを行います。もう一つの新しいおもちゃは tap です。 tap は最初の引数として UIView のオブジェクト accessibilityLabel の値を取ります。UIButtonUILabel ではデフォルトで自分の 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 ディレクトリからテストを読み込みます。
  • テストは describeit ブロックの集まりです。これらはラベルを引数に取り、同じようなテストをグループ分けするのに役に立ちます。
  • <any object>.should は値が等しいかチェックする際に用います。 例 : greeting.should == "hello"
  • UIViewController には機能テストがあり、tappinch のようなイベントをシミュレートしてくれます。describe ブロックで、これらの機能を有効にするために tests <controller class> を用います。
  • tap <accessibility label> は、ビューに設定した accessibilityLabel プロパティを引数に設定し、tap をコードから実行します。

次の章は HTTP リクエストを実行する方法です!


Like it? Spread the word