Sencha Test の例 -パート1
こんにちは、ゼノフィseoです。
イントロダクション
Sencha Test を利用すると、開発者自身のマシンでテストを書いて実行、ビルドオートメーションシステムでそれらのテストを簡単に自動化することができます。ユニットテストの書き方の習得には、例を見ていただくことが最も良い道です。この記事ではSenchaTestDemo リポジトリの新しいユニットテスト例をご紹介し、いかに簡単に始めることができるかをお伝えします。
ユニットテスト
Test Driven Development (TDD)やBehavioral Driven Development(BDD) の開発プロセスを利用していようと、コードが正しく動作することをただ確かめるためであろうと、ユニットテストの実施はベストプラクティスです。ユニットテストではシステムの小さな部品(ユニット)を対象にし、それらを他と切り離して正しく動作することを確認します。不具合を早期に検出してアプリケーションの質を大きく向上させるにあたりユニットテストは最も簡単な方法です。
実際のアプリケーションでこれがどのように実現されるかをお見せするため、アプリケーションの最も小さな構成要素であるデータモデルを取り上げます。シンプルに進めるため、Admin Dashboard example のTODOリストのレコードのデータモデルを使用します。
Admin.model.Todo の中で重要なコードは以下のsetメソッドです:
set: function (name, value) { var data = name; if (typeof name === 'string') { if (name !== 'done') { data = [name, value]; } else { data = [{ completedDate: value ? new Date() : null }]; data[0].done = value; } } else { if (data.done !== undefined) { data = [Ext.apply({ completedDate: data.done ? new Date() : null }, data)]; } else { data = [name]; } } return this.callParent(data); } |
上記のsetメソッドは次の基本的なビジネスルールに基づいて処理します:
完了(done)したTodoはcompleteDate フィールドを設定し、そうでないものは設定しない。
どのようなユーザインタフェースがdoneフィールドを編集しようとも、completeDateは正しく更新されるということをこのルールで保証しています。このメソッドが正しく動作していることを我々はどのように知ることができるでしょうか。ユニットテストを書いて実行してみましょう。
コードをテストするにあたり考慮すべき切り口は四点あります。どのようにメソッドがコールされているか、どのフィールドがセットされているか、そこに設定されている値、そしてレコードの初期状態です。
異なったコールの方法
setメソッドのコールの方法には二つあります: nameとvalueを別々の引数として渡すか、またはnameとvalueのペアのオブジェクトを一つの引数として渡すかです。
影響を受けるフィールド
setメソッドはdoneフィールドの変更に関してのみ応じるものです。その他のフィールドの変更でcompletedDateフィールドが影響を受けてはいけません。doneフィールドを変更し、completedDateへの影響を確かめるテストはポジティブテストと呼ばれ、一方、その他のフィールドを更新してもcompletedDateが変更されないことを確かめるテストはネガティブテストと呼ばれます。ネガティブテストは考慮し忘れやすいものす。しかし、すべきでない処理を実施していない(ネガティブ)ことを確認することは、すべき処理を実施している(ポジティブ)ことを確認することと同程度に重要であることを覚えておいてください。
新しいフィールド値
doneフィールドをtrueに設定するとcompletedDataフィールドに現在日付を格納し、一方でdoneをfalseに設定した場合はcompletedDateにはnull を格納します。
初期フィールド値
doneの初期値を変更すると、doneおよびcompletedDateは共にダーティ、または変更されたとしてマークされます。
手順
これら四つの切り口を組み合わせ、それらを一つずつテストする方法は数多くありますが大抵の場合、テストスイートをよりシンプルにするものはひとつです。今回の場合ではまず、初期値を切り口にテストを分けます。
describe('Todo Tests', function() { describe('with a not completed todo item', function() { beforeEach(function () { this.startTime = new Date().getTime(); // Create a not completed item this.todo = new Admin.model.Todo({ id: 99, task: 'Do this now', done: false }); }); |
テストスイートの各テスト前に呼び出されるべき関数をJasmineではbeforeEach関数に入れます。上記のコードではJasmineに用意されているコンテキストオブジェクト(「this」ポインタ)が、doneにfalseが設定されているtodoレコードのインスタンスを示しています。これは取り得る二値のうちの一つであり、テストの組み合わせの半分はこのレコード状態です。
setメソッドコールの形式を次の切り口にします。その他の切り口は各テストに相当します
describe('set fields by name', function() { it('should not set done or completedDate when changing another field', ... it('should set completedDate when done is set', ... it('should unset completedDate when done is unset', ... }); describe('set fields by object', function() { it('should not set done or completedDate when changing another field', ... it('should complete the todo', ... it('should unset completedDate when done is unset', ... }); |
似ていますが同一ではありません。二つ目の値が異なるためにテストの組合せが繰り返されています:
describe('with a completed todo item', function() { beforeEach( function() { this.startTime = new Date().getTime(); this.todo = new Admin.model.Todo({ id: 99, task: 'Do this now', done: true, completedDate: new Date() }); }); |
テストケース全体はGitHubのexampleにあります。
まとめ
このテストスイートは取り得る全ての初期状態、および、ネガティブケースが他に変更を及ぼさない点を含めて様々な変更をカバーしています。setメソッドは全体でおよそ20行のコードのものです。しかしその枝分かれする性質上、隅々までカバーするために250行程度のテストコードとなりました。250行のほとんどはシンプルな期待値の記述ですが、アプリケーションコードに対するテストコードの比率は膨らみがちになります。
言い換えればユニットテストを書くことは投資にあたりますが、しかしそれ相応のものが得られます。最小限の投資で最大のリターンを得るために役立つガイドラインを以下に示します。
- シンプルでない箇所を見ましょう。シンプルなメソッドはインスペクションで確かめることができます
- 重要な箇所を見ましょう。コードのすべてがアプリケーションの機能につながるわけではありません。不備の無いようにしておくべきコードのテストに投資しましょう
- 使われている箇所を見ましょう。あまり使われないコードのテストに時間をかけてもそれほど得られるものはありません。ただしあまり使われないものでも、重要なものはあります
- テストは簡潔にしておきましょう。テストの失敗をデバッグする際、テストの複雑さに悩まされたくはありませんし、テスト自体にバグがあってはどうしようもありません
- ポジティブとネガティブ両方の状態でテストしましょう。ただし、すべきでない挙動を行うコードは書かないようにしましょう
- 詳細な処理ではなく、入力と出力に観点を絞りましょう。入力、パブリックメソッド、出力が同じである限りは内部処理が変わってもテストに影響ありません
- DOMに加えられたエレメントなど、ガベージコレクトされないリソースが無いようにしておきましょう。afterEachもしくはafterAllのJasmine APIを使うことで、例外が投げられた場合でも適切にクリーンアップされます
最後に
ご紹介したユニットテストの例でその有用性や簡単であることを理解していただき、テストを書くきっかけとしていただけると幸いです。Sencha Test を使うことでユニットテストを、Sencha Studio でローカルに、もしくはコマンドラインstcユーティリティを通して実行することができます。開発時のテストをSencha Test で簡単に、継続的インテグレーション(CI)/ビルドシステム にも利用することができ、開発プロセスにセーフティネットを張ることができます。