StatSVNをインストールする

(1)ダウンロード
StatSVN download | SourceForge.net
0.7.0をダウンロードしました。

(2)/var/svn/statsvn/に入れる

(3)対象のデータを用意

ディレクトリ作って、ログを作成する。

 # cd /tmp
 # mkdir test_statsvn
 # cd /test_statsvn
 # svn co file:///var/svn/repos/testrepo/trunk/testpj
 # cd testpj
 # svn log -v --xml > logfile.log
 # mkdir /var/www/html/statsvn

(4)statSVNしてみる

# java -jar /var/svn/statsvn/statsvn.jar /tmp/test_statsvn/testpj/logfile.log /tmp/test_statsvn/testpj

でてきた!!

(5)StatSVNの結果をUTF-8にする

HTMLのエンコーディングUTF-8にしたい。
出力するときにオプション指定ができるので、それでもう一回。

# java -jar /var/svn/statsvn/statsvn.jar -charset UTF-8 /tmp/test_statsvn/testpj/logfile.log /tmp/test_statsvn/testpj

かんぺき!

(番外編)cronで毎日やりたい

/etc/cron.dailyに突っ込んだshell。

 #!/bin/bash
 OUTPUT_DIR=/var/www/html/statsvn
 ROOT_DIR=/tmp/test_statsvn
 SRC_DIR=$ROOT_DIR/testpj
 rm -rf $OUTPUT_DIR/*
 cd $ROOT_DIR
 svn co file:///var/svn/repos/testrepo/trunk/testpj
 cd $SRC_DIR
 svn log -v --xml > logfile.log
 java -jar /var/svn/statsvn/statsvn.jar -output-dir $OUTPUT_DIR -charset UTF-8 $SRC_DIR/logfile.log $SRC_DIR

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 のこと。

チーム開発環境

はじめに

Salesforce を使った新しい何かが開けないかと社内で零細プロジェクトを運営中です。
そんな零細PJにもメンバーが時々増えたりします。
零細なりのSalesforce でのチーム開発環境をまとめてみようと思います。

Eclipse

セールスフォースはブラウザでも開発できますが、基本Eclipse でやっています。
Subversion からの最新取得やコミットなどもEclipse 経由です。
うちは Java での開発が長いので、Eclipse は馴染み深くForce.com 初心者にも優しい。
Force.com IDEEclipse プラグインです。使っているうちにお友達になれます。
時々どういう因果か package.xml のバージョンが変わって振り回されるのですが、皆さんのところは優しいお友達でしょうか。

Subversion

これはお好きなのを使えばよいかと思います。
でも絶対必要。SCM(ソフトウェア構成管理)のために使います。

Redmine

零細なりに色々試すということで、チケット駆動開発でForce.com 開発をしております。
チケット駆動とは相性が良いです。
Redmineチケット駆動開発でよく使われるツールです。
作業に追われる人、作業の見える化をしたい人におすすめです。
Subversion と連携させていると、素敵です。
No Ticket, No Commit は、効果あります。

StatSVN

Subversion のログを使って、Subversion の状況を見える化します。
水曜日がずば抜けてコミットが多い私。

PukiWiki

共有したい情報や、日々の作業を各自書き付けています。
各人にひとつ用意しているので、ブログみたいな感じになっています。

おわりに

駆け足で紹介しましたが、今のチーム開発の要は実感を得ることだと思っています。
特定のお客様に向けて開発するのが基本スタイルの会社なので、不特定のお客様に対してのものづくりの経験がありません。
ましてや、コーディングが得意!という人間でもない。あるのは、(私の)ノリと勢いだけ。
加入者にとってハードルの高いPJなので、やったことを誇れるようにしてあげたいと思って試行錯誤しています。
愉快な Force.com 開発を目指して前進あるのみ!
みなさまの工夫もぜひ教えてください。

                                                                                                                                            • -

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

二巡目はテストのことを書きたいと思います。

ApexDocとは

外人さんが作ってくれたjavadoc的なことができるツール。
eclipseプラグインの形で提供されている。
Technology Share - TechShare: ApexDoc - A Salesforce Code Documentation Tool
GitHub - SalesforceFoundation/ApexDoc: The latest java source for ApexDoc, a tool to document your Salesforce Apex classes.

Very useful tool.
Thanks Aslam!


New BSD Licence
The 2-Clause BSD License | Open Source Initiative


このままだとマルチバイトに対応していないのでFileManager.javaを書き換えます。
dos.writeBytes(contents);としているところをdos.write(contents.getBytes());にすればよいだけ。
2箇所あるので二つとも直しましょう。
以下、例です。
original

for(String fileName : classHashTable.keySet()){                         
        String contents = classHashTable.get(fileName);                                 
        fileName = path + "/" + fileName + ".html";                             
        File file= new File(fileName);
        fos = new FileOutputStream(file);
    dos=new DataOutputStream(fos);
    dos.writeBytes(contents);
    dos.close();
    fos.close();
    infoMessages.append(fileName + " Processed...\n");
    System.out.println(fileName + " Processed...");
        if (monitor != null) monitor.worked(1);                     
}

changed

for(String fileName : classHashTable.keySet()){
	String contents = classHashTable.get(fileName);
	fileName = path + "/" + fileName + ".html";
	File file= new File(fileName);
	fos = new FileOutputStream(file);
    dos=new DataOutputStream(fos);
    dos.write(contents.getBytes());
    dos.flush();
    dos.close();
    fos.close();
    infoMessages.append(fileName + " Processed...\n");
    System.out.println(fileName + " Processed...");
	if (monitor != null) monitor.worked(1);
}

Redmine TimeTrackerプラグインを追加した

%REDMINE%配下のvendor/pluginsにプラグインを追加する。

[user@server redmine]# cd vendor/plugins
[user@server plugins]# tar zxf delaitre-redmine_time_tracker-0.4-1-g2920746.tar.gz
[user@server plugins]# mv delaitre-redmine_time_tracker-2920746 timetracker
[user@server plugins]# rm -f delaitre-redmine_time_tracker-0.4-1-g2920746.tar.gz
[user@server redmine]# rake db:migrate_plugins RAILS_ENV=production
(in /var/lib/redmine)
Migrating engines...
Migrating acts_as_activity_provider...
Migrating acts_as_attachable...
Migrating acts_as_customizable...
Migrating acts_as_event...
Migrating acts_as_list...
Migrating acts_as_searchable...
Migrating acts_as_tree...
Migrating acts_as_versioned...
Migrating acts_as_watchable...
Migrating awesome_nested_set...
Migrating classic_pagination...
Migrating gravatar...
Migrating open_id_authentication...
Migrating prepend_engine_views...
Migrating rfpdf...
Migrating ruby-net-ldap-0.0.4...
Migrating timetracker...
==  CreateTimeTrackers: migrating =============================================
-- create_table(:time_trackers)
   -> 0.0339s
==  CreateTimeTrackers: migrated (0.0344s) ====================================

==  AddPauseSupport: migrating ================================================
-- add_column(:time_trackers, :time_spent, :float, {:default=>0})
   -> 0.0154s
-- add_column(:time_trackers, :paused, :boolean, {:default=>false})
   -> 0.0115s
==  AddPauseSupport: migrated (0.0278s) =======================================

所有者の変更を忘れずに。
apache再起動もね。

注意

TimeTrackerプラグインはテーブルを作っている。
今後、rake db:migrate_plugins RAILS_ENV=production する時には、削除してあげる必要がある。
気づかずにやっちゃうと、こんなエラーがでる。

rake aborted!
An error has occurred, all later migrations canceled:

Mysql::Error: Table 'time_trackers' already exists: CREATE TABLE `time_trackers` (`id` int(11) DEFAULT NULL auto_increment PRIMARY KEY, 
`user_id` int(11), `issue_id` int(11), `started_on` datetime) ENGINE=InnoDB

MySQLのコマンドたたいてテーブルをdropしてから、rake db:migrate_plugins RAILS_ENV=production すればよいみたい。

mysql> drop table time_trackers;

バーンダウンチャートプラグインの追加

#open_flash_chartを入れる
 cd /vendor/plugin
 wget --no-check-certificate https://github.com/pullmonkey/open_flash_chart/zipball/v2.1.1
 unzip pullmonkey-open_flash_chart-v2.1.1-0-ge19e239.zip
 rm pullmonkey-open_flash_chart-v2.1.1-0-ge19e239.zip
 mv pullmonkey-open_flash_chart-a098aef open_flash_chart
 cp -p open_flash_chart/assets/open-flash-chart.swf %REDMINE%/public/
 cp -p open_flash_chart/assets/javascripts/swfobject.js %REDMINE%/public/javascripts/
 /etc/init.d/httpd restart
#redmine_version_burndown_chartsを入れる
 wget --no-check-certificate https://github.com/daipresents/redmine_version_burndown_charts/zipball/master
 unzip daipresents-redmine_version_burndown_charts-0.1.0-0-g4716ba7.zip
 rm daipresents-redmine_version_burndown_charts-0.1.0-0-g4716ba7.zip
 mv daipresents-redmine_version_burndown_charts-4716ba7 redmine_version_burndown_charts
 chown -R apache:apache open_flash_chart
 chown -R apache:apache redmine_version_burndown_charts
 /etc/init.d/httpd restart


Redmine上でロールと権限を変更すればオッケー。