Apexによるコーディング
何かをコーディングする際、JavaやC#のプログラマであれば、プロダクションコードよりも前にユニットテストのテストコードから書き始めたいと思うかもしれません。Apexにおいても、ある程度のテスト駆動開発ができます。ここで「ある程度」と言っているのは、Apexのコンパイルが、JUnitやNUnitのようなテスティングフレームワークと異なり、ローカル環境で行われないからです。JUnitやNUnitなどに慣れたプログラマにとっては、開発リズムという点で、少し物足りないと感じる場面もあるかもしれません。
サンプルアプリケーションのテストコード
ここで、前回デプロイし、Force.com IDEのワークスペースに展開したテストコード「PlayManagerTest」を解説していきます。このテストコードは「PlayManager」クラスに対するテストです。
@isTest
private class PlayManagerTest {
テストクラスの宣言は、JavaやC#でもおなじみのアノテーションによって行います。「private」なクラスに「@isTest」アノテーションを付けることによって、テストコードとして扱われるようになります。
「@isTest」アノテーションには「@isTest(seeAllData=true)」といった属性値を付記することができ、「seeAllData」を指定しなかった場合のデフォルトはfalseです。「seeAllData」がtrueの場合、テストを実行した時点で格納されているオブジェクトやカスタム設定のレコードを、テストコード内からクエリーすることができます。falseの場合、テストコード内からクエリーできるのは、発行するクエリーと同じテストメソッドの中で追加されたレコードのみです。なお、テストコード内で追加されたレコードは、テストメソッドの処理の単位で自動的にロールバックされます。
次はテストメソッドの宣言です。
@isTest
static void testCreateAnswers1() {
テストメソッドの宣言は、テストクラスと同じく「@isTest」アノテーション(または「testmethod」修飾子)を指定することで行います。「seeAllData」属性の指定も、テストクラスの場合と同様です。一時的に、あるテストメソッドをテストとして実行したくない場合などは、この「@isTest」アノテーションをコメントアウトすると良いでしょう。
// @isTest
static void testCreateAnswers1() {
次にテストメソッドの内容を見ていきます。テストメソッドの序盤では、テストデータとしてのオブジェクトを作成しています。サンプルアプリケーションのオブジェクトである「クイズ」「質問」「プレー」をnew演算子によってメモリ上に生成し、insertによって永続化しています。なお、Apexのリテラル文字列はダブルコーテーションではなく、シングルコーテーションでくくることに注意してください。
Quiz__c quiz = new Quiz__c(Name = 'クイズ1'); insert quiz; Question__c question = new Question__c(Name = '質問1', Text__c = 'タコの足は何本?', Quiz__c = quiz.Id); insert question; Choice__c[] choices = new List<Choice__c>(); choices.add(new Choice__c(Name = '6本', Question__c = question.id)); choices.add(new Choice__c(Name = '8本', Question__c = question.id)); insert choices; Play__c[] plays = new List<Play__c>(); plays.add(new Play__c(Name = 'プレー1', Quiz__c = quiz.id));
Apexでは、Force.comのオブジェクトのレコードとApexの「class」によって定義されたクラスのオブジェクト(インスタンス)は似て非なるものです。Force.comのオブジェクトは特別に「SObject」(エスオブジェクト)と呼ばれます。例えばSObjectに対しては、プロパティに割り当てる値をnewの際のカッコの中で指定することができます。次のコードは、上記の「クイズ」オブジェクトのレコードの生成と同じ意味の異なる記述方法を示しています。
Quiz__c quiz = new Quiz__c(); quiz.Name = 'クイズ1';
次に、テスト対象であるPlayManagerのインスタンスを生成します。クラスのインスタンスの生成も、SObjectのレコードの生成と同じようにnew演算子によって行います。
PlayManager pm = new PlayManager();
そして、実際にテストを行いたい対象のメソッドの実行です。「Test.startTest()」と「Test.stopTest()」は、ガバナ制限を測定する範囲の開始と終了を表しています。
Test.startTest(); Answer__c[] answers = pm.createAnswers(plays); Test.stopTest();
最後に、「System.assertEquals()」によって、テスト対象のメソッド実行後の値の妥当性を評価します。
System.assertEquals(1, answers.size());
System.assertEquals('質問1の回答', answers[0].Name);
System.assertEquals(question.id, answers[0].Question__c);
Force.comにおけるテストコードの役割
Force.comにおけるコーディングで最も特徴的と言えるのは、テストコードによるプロダクションコードのカバレッジが平均75%以上でないと、プロダクション環境にデプロイできないということです。これによりプロダクションコードの品質をある程度の水準に保つことができ、マルチテナントのコンピュータリソースを無駄に消費しないことなどに繋がっています。一方で、「とにかくカバレッジだけを確保すれば良い」という考えには、なるべく陥らないように注意しなければなりません。
Apexにおいても、JavaやC#の場合と同じように、次のようなテストコードの役割があります。
| テストコードの役割 | 説明 |
|---|---|
|
テスト 駆動開発 |
主にクラスを設計するためのテストコード。クラスを提供する側からではなく、 クラスを利用する立場に立ち、必要最低限の実装から優先的に進めていく手法。 |
| 回帰テスト | 主に機能の追加や変更の際に、デグレが起こっていないかを確認するためのテストコード。 |
| カバレッジ | テストコードを実行した際に、全体のプロダクションコードのうちのどれだけの文が実行されているかの割合。 |
テストコードは、それぞれの役割に分けて、別々のものとして実装することができます。もちろん、1つのテストコードですべての役割を果たすことができれば、それに越したことはありません。よく行う方法としては、最初にテスト駆動開発を行い、そのうちのいくつかを整理し、回帰テスト用のテストコードとする方法です。継続的に機能拡張するためには、できるだけテストコードを変更しやすい状態にしておく方が良いからです。この際、カバレッジが下がっていないかを確認しながら、カバーされていないプロダクションコードがある場合は、回帰テスト用のシナリオを追加します。
Force.comのテストコードには、JavaやC#での開発に比べ、次のような特徴があると言えます。
- 手作りしなければならないクラスが圧倒的に少ない
- Apexトリガやワークフローを伴うため、永続化データに対するテストコードが中心になる
- カバレッジをできるだけ上げるためのテストコードの追加が重要視される
しかしForce.comにおいても、継続的に機能拡張していくことが望ましい点は、他のプラットフォームと変わりがありません。
まとめ
今回は、前回に引き続きサンプルアプリケーションを利用して、Force.comにおける開発方法を解説しました。テストコードの実装では、これまで培ってきたノウハウが、クラウド上のプラットフォームでも活かせるということをお分かりいただけたと思います。次回は引き続き、プロダクションコードとしてのクラスやトリガのコードを解説します。
参考資料
