Force.com内でテストを定期実行する

はじめに

Force.com開発ではテストコードが絶対必要です。75%以上のコードカバレッジを達成していることが求められています。
今回はテストコードではなく、テスト実行環境について書きたいと思います。


Salesforce がテストコードを書いて実行してね(はぁと) と言うくらいなので、いろいろ方法があります。

  • ブラウザで実行
  • Eclipse で実行
  • API で実行

もちろん一番簡単なのは「ブラウザから実行」です。メニューから選んで実行するだけ。

しかし、今回はAPIで実行を取り上げたいと思います。

API で実行

Ver23 でベータリリースされたクラス [ApexTestQueueItem] [ApexTestResult] を使ってテスト実行します。
これらのクラスとApex Scheduler を組み合わせることで、定期的にテストができるようになるのです!
それでは、早速コードへ。

global class TestScheduler implements Schedulable{

	global void execute(SchedulableContext ctx) {
		ID jobId= enqueueTestClasses();
		System.assert(jobId != null);
	}

	public static ID enqueueTestClasses() {
		ApexClass[] testClasses = [SELECT Id, Name FROM ApexClass WHERE Name LIKE 'T%'];
		if(testClasses.size() == 0){
			return null;
		}

		ApexTestQueueItem[] queueItems = new List<ApexTestQueueItem>();

		for (ApexClass cls : testClasses) {
			queueItems.add(new ApexTestQueueItem(ApexClassId=cls.Id));
		}

		insert queueItems;
		ApexTestQueueItem item = [SELECT ParentJobId FROM ApexTestQueueItem WHERE Id=:queueItems[0].Id LIMIT 1];
		return item.parentjobid;
	}
}

Apex Scheduler に登録するクラスは、Schedulable を implements している必要があります。
あとは、テストクラスをかき集めて、ApexTestQueueItem につめて insert しているだけです。
ここでは テストクラスはTで始まるもの です。
なお、ApexTestQueueItem は 「ブラウザから実行」のうちメニュー テスト実行 を行った時と同等のものです。


スケジューラに登録します。
設定->開発->Apexクラス->Apexをスケジュール ボタンから登録できます。


毎日朝四時位に、勝手にやっておいてほしいですよねー。

これだけで終わりです。
あとは動いたかどうかを確認します。

テスト結果確認

テスト結果は「ブラウザから実行」のメニュー テスト実行 で確認できます。


ここではテスト実行して一番最新のものだけを表示するviewerを用意します。
朝四時の定期実行のあとでブラウザからテスト実行した場合は、それが最新になります。
その場合は、メニュー テスト実行の履歴で確認してください。

まずは、Visualforce Page

<apex:page id="page" controller="TestResultsViewer" sidebar="false">

<apex:form id="form">
	<apex:outputPanel id="top" layout="block">
	</apex:outputPanel>
	<apex:pageBlock id="middle" mode="detail">
		<apex:pageBlockSection columns="1">
			<apex:outputText value="{!testTimeStamp} 頃に {!methodCount} メソッド実行されました。"/>
			<apex:outputText value="成功数は {!passCount} , 失敗数は {!failCount} です。"/>
			<apex:outputText >詳しい内容を見たい場合は<apex:outputLink value="書き換えてね">Apexテスト実行</apex:outputLink>を確認してください。</apex:outputText>
		</apex:pageBlockSection>
		<apex:pageBlockSection columns="2">
			<apex:pageBlockTable id="monthList" value="{!testResults}" var="testResult" >
				<apex:column id="ApexclassName" styleClass="resultCell">
					<apex:facet name="header">クラス名</apex:facet>
					<apex:outputField value="{!testResult.ApexClass.Name}"/>
				</apex:column>
				<apex:column id="MethodName"  styleClass="resultCell">
					<apex:facet name="header">メソッド名</apex:facet>
					<apex:outputField value="{!testResult.MethodName}"/>
				</apex:column>
				<apex:column id="Outcome" styleClass="resultCell" >
					<apex:facet name="header">成功/失敗</apex:facet>
					<apex:outputField value="{!testResult.Outcome}"/>
				</apex:column>
				<apex:column id="Message" styleClass="resultCell" >
					<apex:facet name="header">エラーメッセージ</apex:facet>
					<apex:outputField value="{!testResult.Message}"/>
				</apex:column>
			</apex:pageBlockTable>
		</apex:pageBlockSection>
	</apex:pageBlock>
	<apex:outputPanel id="bottom" layout="block">
	</apex:outputPanel>
</apex:form>
</apex:page>

コントローラ

public with sharing class TestResultsViewer {
	public ApexTestResult[] testResults {get; set;}
	private Integer passCount = 0;
	private Integer failCount = 0;

	public TestResultsViewer(){

		Id jobId = [SELECT AsyncApexJobId FROM ApexTestResult order by SystemModstamp desc limit 1].AsyncApexJobId;
		System.assert(jobId != null);

		testResults = [SELECT Outcome, ApexClass.Name, MethodName, Message, StackTrace, TestTimestamp
			FROM ApexTestResult where AsyncApexJobId = :jobId order by ApexClass.Name, MethodName];
		System.assert(testResults.size() != 0);

		for(ApexTestResult result : testResults){
			if(result.Outcome == 'Pass'){
				passCount++;
			}else if(result.Outcome == 'Fail'){
				failCount++;
			}else if(result.Outcome == 'CompileFail'){
				//今のところなしとしておく
			}
		}
	}

	public Integer getMethodCount(){
		return testResults.size();
	}

	public Datetime getTestTimeStamp(){
		return testResults.get(0).TestTimestamp;
	}

	public Integer getPassCount(){
		return passCount;
	}

	public Integer getFailCount(){
		return failCount;
	}
}

作ったページを開けば、↓のように表示されます。

おわりに

これで継続的にテスト自動実行ができるようになります。しかもForce.com内で完結しています。
この内容前半部は、以下のドキュメントで書かれたものを参考にしています。
Salesforce Developers
ドキュメントはお役立ちです。


この情報で、皆様の手間が一つでも減ればと思います。
合言葉は、
紳士*1がいなくてもCIはできる!

                                                                                                              • -

このエントリーはForce.com Advent Calendarに参加しています。
Force.com Advent Calendar 2011 : ATND

*1:Jenkins のこと。