preload
Force.com IDE 小ネタ:Apexクラス内でSOQL記述を手抜きして書く MacBook(白)をSSD化![3章:OSインストール編]
5 月 27

今回は、Workbook(第2版)のチュートリアル#6をやって行きます。
#5については、日本語版(第1版)と同じですので第1回でやっているはず、ということで省略です。

レベルは上級。所要時間は45-60分ということですので気を引き締めて行きましょう。

Tutorial #6: Adding Tests to Your Application

 (アプリケーションのテストを追加する)

どんなアプリケーションにおいても、開発する上で重要なステップの一つとしてテストがあげられます。
テストはプログラムされたソースコードの振る舞いが正しいこと、望ましい結果が出ることを確認できるようにするためのものです。

直近のチュートリアルでは、マイレージ記録アプリケーションを【構築すること】に焦点を当ててきました。
29ページのチュートリアル5のApexコードを使用したビジネスロジックの追加では、マイレージ記録システムにマイレージ制限を適用するためのコードを書きました。
このコードが正しいことを確認するために、オンラインのユーザインターフェースを通して手動でテストを行い、アプリケーションの動作を確認しました。
しかし、もっと長い開発期間をカバーできるだけの自動テストの方法があれば良いと思いませんか?
Apexテストフレームワークはこれらのテストを自動的にやるのと同じようにアプリケーションの振る舞いを実行/評価する手助けをする重要な鍵となります。

【前提条件】
開発モード」「全てのデータの修正」「Apexを作成できる」パーミッション
このチュートリアルではApexの動作を含みますのでApexクラスを作成したり、電子メールサービスを設定できる権限がなければなりません。

メモ:DeveloperEditionの管理ユーザはすでにこれらのパーミションはデフォルトで付与されています。

マイレージ記録アプリケーション(オリジナルではさらに説明が入ってますが、いつものことなんで省略します)

必要なソフトウェア

※Eclipseの最新バージョンは3.4(Ganymede)ですが、Force.com IDEは3.3(Europa)でしか動きません。

Step 1: Create an Apex Class

(Apexクラスを作成する)

Apexクラスには全てのユニットテスト(単体テスト)が含まれています。このステップではユニットテストを導入するためのクラスを作成します。

1. Force.com IDEで新規Apexクラスを作成します。

a. IDE内のプロジェクトフォルダで、右クリックし【新規】→【Apex Class】を選択します。

b. 「Create Apex Class」画面で「Name」に「MileageTrackerTestSuite」と記述します。

c. Template項目で「Test Class」を選択します。すると「@isTest」という注釈がついたクラスが作成されます。
これはForce.comがこのクラスはテスト目的のみで作成されたことを示すものです。
この(@isTest注釈のある)クラスのコードは「組織内でApexコードは1MBまで」という制限にはカウントされません。
※最新バージョンのForce.comIDEを利用しないと、Template項目はでません。

d. クラスを作成するため【終了】をクリックします。
※一部日本語と英語が入り交じっているのは、日本語化されたEclipseに日本語化されていないForce.com IDEが入っているからです。

2. クラスのコーディングをします。

a. テストクラスのテンプレートにはあらかじめユニットテストとして宣言された「testMethod」というメソッドが定義されています。
ユニットテストは個々のコードが正しく動くかどうか確認するためのクラスメソッドです。
ユニットテストメソッドは「testMethod」というキーワードを用いて定義され、引数を持たず、データベースにデータを登録しません。
テストメソッドは常に「static」なメソッドとして定義されなければなりません。
テンプレートによって作成されたメソッドは「myUnitTest」と名付けられています。これを「runPositiveTestCases」とリネームします。
デフォルトでは、アクセス修飾子がありませんので「private」ということになります。
現在、メソッドは以下のように定義されているはずです。

  
  static testMethod void runPositiveTestCases() {
// TODO: implement unit test
}

b. メソッド内に自動作成されたコメントを削除し、次に挙げるメソッド変数を定義します。
:totalMiles, maxtotalMiles, singletotalMiles, createdbyId and deleteMiles

  Double totalMiles = 0;
final Double maxtotalMiles = 500;
final Double singletotalMiles = 300;
final String createdbyId = UserInfo.getUserId();
List deleteMiles = new List();

※説明文では「maxTotalMiles」なのに、コード例では「maxtotalMiles」になっています。singletotalMilesの例もありますので説明文を合わせました。
(変数名はキャメルケースで統一すべきではないか?と思ったりしますが・・・)

Step 2: Clean up Existing Data

(現在あるデータを削除する)

1日に一回以上テストを実行しようとするならば、前回のテストの結果が残っていない方が良いはずです。
以下のコードをクラスの最後に追加します。

1. デバッグログ用のテキストを追加して、スクリプトが次に何をしようとしているのかを示します。

System.debug(’Setting up testing - deleting any mileage records for today’); //テスト用の設定 - 本日の全てのマイレージレコードを削除します

2. このユーザによって作られたデータがあれば全て削除します。これはあなたがいつも同じユーザでSalesforceにログインすると仮定しているものです。

deleteMiles = [ SELECT miles__c from Mileage__c
WHERE createdDate = TODAY and createdById = :createdbyId];
if (!deletedMiles.isEmpty()) {
delete deleteMiles;
}

Step 3: Add Tests for the Positive Case

(成功するパターンのテストを追加する)

※Positive Case /Negative Caseのうまい訳は何でしょう?

それでは、コードが期待通りに動くということを確認するために、成功するパターンのテストを今あるクラスに追加して行きましょう。
初めのテストは1つのレコードが登録されたとき、次に大量のレコードが登録(バルクインサート)されたときのテストです。
以下のコードをMileageTrackerTestSuiteクラスの末尾に追加します。

1. デバッグログ用のテキストを追加して、スクリプトが次に何をしようとしているのかを示します。

System.debug(’Inserting 300 more miles … single record validation’); //300以上のマイルのレコードを1つ追加してチェック

2. マイレージオブジェクトを作成し、データベースに登録します。

Mileage__c testMiles1 = new Mileage__c(Miles__c = 300, Date__c = System.today());
insert testMiles1;

3. 登録されたレコードが帰ってくるかでコードをチェックします。

for (Mileage__c m: [SELECT miles__c FROM Mileage__c
WHERE createdDate = TODAY
and createdById = :createdbyId
and miles__c != null]) {
totalMiles += m.miles__c;
}

4. 期待通りの結果が帰って来ていることを「System.assertEquals」メソッドを使って確認します。

System.assertEquals(singletotalMiles, totalMiles);

5. 次のテストに移る前に、マイル合計(totalMiles)をゼロに戻します。

totalMiles = 0;

6. 200個のレコードをバルクインサートした場合のコードをチェック
まず、デバッグログ用のテキストを追加して、スクリプトが次に何をしようとしているのかを示します。

System.debug(’Inserting 200 Mileage records … bulk validation’); //200個のマイレージレコードを登録し、バルクインサートをチェック

7. 次に200個のMileage__cレコードを登録します。

List testMiles2 = new List();
for (integer i=0; i<200; i++) {
testMiles2.add (new Mileage__c (Miles__c = 1, Date__c = System.today()));
}
insert testMiles2;

8. 期待通りの結果が帰ってくることを「System.assertEquals」を使って確認します。

for (Mileage__c m: [SELECT miles__c FROM MIleage__c
WHERE createdDate = TODAY
and createdById = :createdbyId
and miles__c != null]) {
totalMiles += m.miles__c;
}
System.assertEquals(maxtotalMiles, totalMiles);

Step 4: Add Negative Test Cases

(失敗するパターンのテストケースを追加する)

コードの動作を確認するために失敗するパターンのテストも追加します。
1. 「runNegativeTestCases」という名前のstaticテストメソッドを追加します。

static testMethod void run NegativeTestCases() {

2. デバッグログ用のテキストを追加して、スクリプトが次に何をしようとしているのかを示します。

System.debug(’Inserting 501 miles… negative test case’); //501マイルのデータを登録 失敗するテストケース

3. 501マイルのMileage__cレコードを作成します。

Mieage__c testMiles3 = new Mileage__c(Miles__c = 501, Date__c = System.today());

4. 「insert」文を「try/catch」ブロック内に置きます。これによって例外をcatchして自動的にエラーメッセージを吐き出すことができます。

try {
insert testMiles3;
} catch (DmlException e) {

5. ここで、「System.assert」と「System.assertEquals」を利用してテストをして行きます。
先に作ったchatchブロック内に以下のコードを追加します

//Asset Error Message
System.assert(e.getMessage().contains(’Insert failed. First exception on row 0; ‘ +
‘first error: FIELD_CUSTOM_VALIDATION_EXCEPTION, ‘ +
‘Mileage request exceeds daily limit(500) : [ Miles__c] ‘),
e.getMessage());
//Assert Field
System.assertEquals(Mileage__c.Miles__c, e.getDmlFields(0)[0]);
//Assert Status Code
System.assertEquals(’FIELD_CUSTOM_VALIDATION_EXCEPTION’, e.getDmlStatusCode(0));
}
}
}

6. クラスが以下のようなコードになっていることを確認してください。(2つの画像に分かれています

6_1
6_2

7. 【ファイル】→【保存】をクリックします。これで、ローカルにクラスが保存されます。
もしForce.com IDEがオンラインモードであれば同様にサーバーにも保存されます。

メモ:MileageTrackerTestSuiteとMileageUtilクラスがサーバに保存されるまではいかなるテストも実行できません。
加えて、Mileage__cオブジェクトも同様にサーバに保存されている必要があります。

Step 5: Run the Apex Test Class in the IDE

(IDEでApexテストクラスを実行する)

さて、テストクラスができましたので、テストが実行できますね、Force.com IDEはテスト実行を簡単に、結果を詳細に提供できるようになっています。
可能であるならば、Apexコードの100%のテストケースを実行すべきです。開発環境から本番環境にコードをデプロイするためには
すくなくともコードの75%はカバーしていなければなりません。

テストクラスを実行するためには:
1. 「Apex Test Runner」タブをクリック

2. 「Log category」は「Apex Code」を選択、「Log Level」は「Finest」(スライドを一番右にします)を選択

メモ: もし、「Apex Text Runner」タブが使用できない場合は、IDE内で右クリックし【Force.com】→【Run Tests】と選択します。
 1回テストを実行すると、コントロールできるようになります。

3. IDE内で、「MileageTrackerTestSuite」クラスを選択し、右クリック【Force.com】→【Run Tests】を選択します。
メモ:もし組織内にテスト用のクラスやメソッドがあった場合はそれらも実行されます。

4. 「Code Coverage Results」で、「MileageTrackerTestSuite」の隣に緑のチェックマークが付いていることが確認できるはずです。
これは、MileageTrackerTestSuiteクラスの全ての行がテストされ、テストによってコードの100%がカバーされたことを示しています。

5. 「Apex Test Runner」タブの右にあるのがデバッグログです。
・ログの最初の部分は、「MileageTrackerTestSuite」クラスの「runNegativeTestCases」メソッドを実行中に起きたイベントを列挙しています。

6_3

・デバッグログのこの行
6_4

MileageTrackerTestSuiteクラスのこの行の結果が表示されています。
6_5

・デバッグログの次の数行はコードの特定の行がどれだけの時間をかけて行われたかを示しています。これはパフォーマンスチューニングの際にとても役立ちます。
6_6

・Apexのランタイムエンジンは全てのスクリプトで使われたリソースの数をずっと保持しています。これは単独のスクリプトがサーバを独占している訳ではないからです。
制限を一つでも越えるスクリプトを書けば、エラーメッセージを受け取ることとなります。
デバッグログはリソースが利用可能な総数とプログラムが利用したリソースがどれぐらいかを一覧にします。
6_7

・デバッグログは「runPositiveTestCase」メソッドに付いても同様に統計項目を一覧にします。

Step 6: Run the Test Class in the Online User Interface

(オンラインでテストクラスを実行する)

Force.com IDE上でのテストクラスの実行に加えて、オンラインでもテストを実行できます。
異なる方法で実行したとしても、デバッグログは同じ情報を含んでオンライン上に表示されます。
さらにオンラインでは一つのクラスのみテスト実行可能です。IDEを利用した場合は組織内の全てのテストを実行します。
メモ:オンライン上で全てのテストを実行することも可能です。

1. オンライン上で【設定】→【開発】→【Apexクラス】をクリックし、MileageTrackerTestSuiteと言う名前のクラスをクリックします。

2. 【テストを実行】をクリックします

3. Apexテスト結果のページが表示されます。

6_8

「カバーされたコード」と「カバーされていないコード」のセクションに注意してください。これらはクラスがトリガーや他のクラス上でテスト実行されたときに利用されます。
例えば、MileageTrackerTestSuiteと言うテストクラスは明示的にMileageUtilクラスをテストするメソッドも含んでいて、これらのセクションに表示されていることがわかります。
デバッグログはIDEのものと同じ情報がページの下部に表示されています。

Summery

このチュートリアルでは先に作成したマイレージ記録アプリケーション上でMileageTrackerTestSuiteというアプリケーションをテストするクラスとして定義されるApexクラスを作成しました。
さらに、前回のテスト後にはデータをクリーンアップする高度を追加しました。
期待通りに動くかどうかを確認するPositiveテストを追加しました。これは単独のレコードでもバルクインサートでも動作することが確認できたと思います。
エラーの発生を正しくとらえられることを確認するためにNegativeテストを追加しました。
テストはForce.com IDE上でもオンライン上でも同じように実行できます。
デバッグログはリソースの制限をうまくコントロールし、パフォーマンスチューニングにどれだけ役立つか、と言うことを確認しました。

関連する投稿

Leave a Reply