2012年3月18日 19:22

GithubのリポジトリでJavaDocを公開する

先日Githubに自分用のライブラリを公開しました。
Githubに「gh-pages」というブランチを作ってpushすると静的なサイトを公開することができるため、それを応用してJavaDocも一緒に公開してみました。


gh-pagesをpushした場合「http://[ユーザー名].github.com/[リポジトリ名]/?」というURLで参照できます。

参考にしたサイト

以下の手順ではMavenで生成したJavaDocをコマンドベースでGithubにpushすることを想定しているのでそれ以外の場合は適宜読み替えてください

手順メモ
    1. [master] pom.xmlに下記を追加
      <project>
        <reporting>
          <outputDirectory>site</outputDirectory>
          <plugins>
            <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-javadoc-plugin</artifactId>
              <configuration>
                <source>1.6</source>
                <encoding>UTF-8</encoding>
                <docencoding>UTF-8</docencoding>
                <charset>UTF-8</charset>
                <links>
                  <link>http://docs.oracle.com/javase/6/docs/api/</link>
                </links>
              </configuration>
            </plugin>
          </plugins>
        </reporting>
      </project>
      <outputDirectory>で出力先の設定。JavaDoc以外にもFindBugsやカバレッジのレポートもできるため詳しくはggrks(ググレカス)
    2. [master] .gitignoreに<outputDirectory>で追加した出力先を追加する
      echo site/ >> .gitignore
    3. [master] ここらでいったんコミット
      git add .
      git commit
      
    4. [master] 下記コマンドを実行
      git symbolic-ref HEAD refs/heads/gh-pages
      rm .git/index
      git clean -fdx
      よく分かってないですが、これをやることでどこのブランチからも切り離されたgh-pagesが作られるらしい
    5. [gh-pages] index.htmlを追加
      echo "My GitHub Page" > index.html
    6. [gh-pages] .gitignoreで適宜ファイルを除外したりする。
      (site/とindex.html以外は除外していいかと)
    7. [gh-pages]  ここらでいったんコミット
      git add .
      git commit
    8. [master] masterをcheckoutしてmvn siteする
    9. [gh-pages] gh-pagesをcheckoutしてsite/を追加
    10. [gh-pages] git push origin gh-pagesするとページが作られる。ファイル数にもよるけど多い時は10分くらいかかる

2回目以降はmasterでmvn siteする前にgh-pagesでrm -rf site/してコミットしておく(既存ファイルがあるとエラーになってcheckoutできないため)

2011年12月 6日 0:00

実録!TDD風景 #TddAdventJp

TDD Advent Calendar jp: 2011 の6日目で @shuji_w6e さんのエントリの次です。

他の人のエントリと比べると内容がかなりぶっとんでいるのであしからず(笑)

TDDを学ぶ上で何が一番いいなぁと考えたのですが、実際にコーディングしている風景を見るのがいいかと思い動画を撮ってみました。既出かどうかはシラネ


今回はTDDBC横浜のお題でTDDしてみました(言語はJava)

コード

本来なら課題4までやっていたのですが動画が途中で壊れていたので課題2まで収録しています。(githubには課題4まで収録)

不自然に間が空いているのは途中何度もサブPCでググったりしてるからです。一発撮り無編集なのであしからず。
実際のコーディング風景からTDDのリズムを感じ取ってもらえれば幸いです。

Quick JUnitではなくS2JUnit4 Pluginを使っていますが、使い方はだいたい一緒です。仕事がSeasar2中心なのでその流れでWinにはS2JUnit Pluginを入れています。(MacにはQuick JUnitだけどw)

キー入力表示には拙作の「見えるキーボード」を利用しています。


よく使うEclipseのショートカット(Macの人はCtrlをCommandに読み替えてください)

  • Ctrl + Space: コード補完
    • 「tes」まで入れるとテストメソッドのテンプレートが作れる
    • カーソル位置がそのままだとJUnit3用のテストメソッド(testXxxxメソッド)
    • カーソルを1つ上に移動させるとJUnit4用のテストメソッド(@Testメソッド)
  • Ctrl + 1: クイックフィックス。その場に応じた補完ができる
    • エラーや警告があればそれを解決
    • テストファーストで先にテストケースだけ作り、必要なメソッドがない状態でコンパイルエラーにしておいた状態からメソッドを作るのは日常茶飯事
    • メソッドの戻り値をローカル変数やフィールド変数に代入
    • とにかくいろいろできるすぎるのでとりあえず困ったらCtrl + 1しておくと吉
  • Ctrl + Shift + O: インポートの編成。足りないimport文や余分なimport文の整理ができる
  • Alt + Shift + R: クラス名、メソッド名、変数名などの変更。
    • 直接参照している箇所はもとより、Javadocの中も全部まとめて変更できるので超便利
    • ただしリフレクションで参照してるのは無理なので、リファクタリングのことを考えるとリフレクションは極力使わない方がいい
  • Alt + Shift + L: 選択範囲をローカル変数に抽出
  • Alt + Shift + M: 選択範囲をメソッド抽出
  • Alt + Shift + I: インライン展開。ローカル変数抽出やメソッド抽出の逆
右クリック→リファクタリングは便利なコマンドが揃ってるので、一度全部使ってみてどんなことができるか確認してみるといいです。


以下はQuick JUnit or S2JUnit plugin専用(JavaでEclipseでTDDやる上ではどちらかは必須)

  • Ctrl + 0: テスト実行(テストメソッド内であればそのメソッドだけ、それ以外ならテストクラス全体)
  • Ctrl + 9: テスティングペアの移動。なければ新規作成ダイアログ

その他や詳しいショートカットはこちらが参考になります



自分がTDDをやり始めて1年くらい経つのでついでに雑感など。

そもそも自分の場合、初めからTDDをやろうとしてたわけじゃないです。
前のPJとかその前のPJとかでテストコードをほとんど書いてなくて苦労してた苦い経験があり、丁度新しいPJということで今度は何が何でもがっつりテストコードを書こうと勝手に決心したのが始まりです。

その頃はTDDという言葉は知っていても具体的にどんなことかは知らず、ただ闇雲にテストを書きやすい開発スタイルを追求していったら自然とTDDのRed→Green→Refactoringの黄金の回転らしきものが見についていました。

※たぶん同時期にリファクタリングやクリーンコードも読んでいたと思いますが、「テストコードのリファクタリング」という言葉を知らない時期に書いたテストコードはいい加減リファクタリングしたいです。(この間リファクタリングして1クラス4000行から3000行くらいには減ったけどw)

そして実際にTDD BootCampに参加し和田さんの講演を聞いたり実際にペアプロをやったりして、自分の開発スタイルがTDDであることを再認識し自信をもつ頃が出来ました。

5年後とか10年後とか先のことは分かりませんが、とりあえず現時点で自分に一番相性のいい開発スタイルはTDDなので飽きるまではこのスタイルで行くと思います。


まぁ、何が言いたいかと言うと

べ、べつに好きでTDDをやっているんじゃないんだからね!///

ということです(ぇー



2011年11月30日 23:00

Memcacheに1MB以上のオブジェクトを格納する

AZusaar! ではDatastoreからとってきた検索結果を100件ずつMemcacheに入れているのですが、最近ちょっとDatastore Reads Oppsが増えてきました。

チューニング前

これが一番多かった時ですが、$0.11/日課金されてます。普通だとFrontEnd InstanceやDatastore Writesで課金がかかると思うんですがうちの場合これらは余裕ですw

これくらいなら微々たるものなのですがなんとかチューニングできないかと課金対策してみました。

とりあえず検索結果を100件ずつではなく全件Memcacheに入れるようにしたところ、下記のようなエラーが発生。

Uncaught exception from servlet
com.google.appengine.api.memcache.MemcacheServiceException: Memcache put: Error setting single item (dev20111122.354967843445918452_KokucheeseSearchService__ALL_201111)
	at com.google.appengine.api.memcache.AsyncMemcacheServiceImpl$7.transform(AsyncMemcacheServiceImpl.java:427)
	at com.google.appengine.api.memcache.AsyncMemcacheServiceImpl$7.transform(AsyncMemcacheServiceImpl.java:419)
	at com.google.appengine.api.memcache.MemcacheServiceApiHelper$RpcResponseHandler.convertResponse(MemcacheServiceApiHelper.java:59)
	at com.google.appengine.api.memcache.MemcacheServiceApiHelper$1.wrap(MemcacheServiceApiHelper.java:98)
	at com.google.appengine.api.memcache.MemcacheServiceApiHelper$1.wrap(MemcacheServiceApiHelper.java:92)
	at com.google.appengine.api.utils.FutureWrapper.wrapAndCache(FutureWrapper.java:57)
	at com.google.appengine.api.utils.FutureWrapper.get(FutureWrapper.java:98)
	at com.google.appengine.api.utils.FutureWrapper.get(FutureWrapper.java:90)
	at com.google.appengine.api.memcache.MemcacheServiceImpl.quietGet(MemcacheServiceImpl.java:27)
	at com.google.appengine.api.memcache.MemcacheServiceImpl.put(MemcacheServiceImpl.java:79)
	at org.slim3.memcache.MemcacheDelegate.put(MemcacheDelegate.java:551)
	at org.slim3.memcache.Memcache.put(Memcache.java:361)

エラーメッセージからは分かりづらいですが、Memcacheに入れようとしたデータが1MB超えているためのエラーのようです。(実際計算したら1.3MBくらいでした)

シリアライズした状態のバイナリは実は全然圧縮されていないため、gzip圧縮かけてMemcacheに入れるためユーティリティを作りました。

(appengine 1.6.0, slim3 1.0.15で確認済)

使い方はテストコードを見ればだいたい分かると思いますが、Slim3のMemcacheとだいたい同じです。
注意点としてはrootPackage配下に置いておかないとhotReloading時にエラーになること。

気になるProduction Server上でのパフォーマンスはこんな感じです。

put時 (1.3MB -> 286KB)
serialize:53ms
compress:121ms

get時 (286KB -> 1.3MB)
uncompressed:55ms
deserialize:19ms

※データの大部分がStringなので結構圧縮率が高いです

圧縮や再シリアライズによる多少のオーバーヘッドはありますがこれくらいなら許容範囲かと。
体感的にもそんなに変わってないです。
他のライブラリに依存していないのでspin upによるコストもありません。

普通だと圧縮してまでMemcacheに入れるということはあまりないと思いますが、もしどうしても1つのデータとしてMemcacheに入れたい場合はこういう方法もあるといういい例ですね。

このチューニングを施した後は見事Datastore Readsを$0に抑えることができました。わーい!
チューニング後


2011年8月31日 22:51

Slim3用のテストユーティリティ「S3 Tiger」を作りました

需要があるかどうか分かりませんが作ってみました。俺得万歳

before
public class SomeTest{
	@Test
	public void ignoreOnlyProduction(){
		if(AppEngineUtil.isProduction()){
			return;
		}
		// 本番サーバだけでは動かしたくないテスト
	}

	@Test
	public void ignoreOnlyDevelopment(){
		if(AppEngineUtil.isDevelopment()){
			return;
		}
		// 開発サーバだけでは動かしたくないテスト
	}
}
after
import net.sue445.s3tiger.Slim3;

@RunWith(Slim3.class)
public class SomeTest{
@IgnoreProduction
@Test
public void ignoreOnlyProduction(){
// 本番サーバだけでは動かしたくないテスト
}

@IgnoreDevelopment
@Test
public void ignoreOnlyDevelopment(){
// 開発サーバだけでは動かしたくないテスト
}
}
Productionサーバだけでは動かしたくないテストやDevelopmentサーバだけでは動かしたくないテストなどをアノテーションで制御することができます。

他にも以下のアノテーションが使えます
@IgnoreServer
@IgnoreNotServer

詳しくはgithubの方を見てください
https://github.com/sue445/s3tiger

2011年3月23日 22:37

Scenic3でAppUrlsのテストを簡単に行う #slim3 #scenic3

先日のTDDBCでGitを使ったのでためしに使ってみるテスト


Scenic3slim3を薄くラップして1つのPageクラスに複数のControllerを持たせることのできるフレームワークです。

Controllerクラスが増えすぎずStrutsライクにコードを書けるため結構使いやすいのですが、Pageクラスを追加する度にAppUrlsにも対応するMatcherクラスを追加しないといけないのが難点です。

package sample.page;

@Page("/")
public class FrontPage extends ScenicPage{

    // some method

}
package sample.controller;

public class AppUrls extends UrlsImpl {
    public AppUrls() {
        add(FrontPageMatcher.get());
    }
}
結構忘れがちなのでたまにページにアクセスできなくてハマります

そこで、JUnitを使ってAppUrlsに全てのMatcher.get()が書かれているかを動的に調べるユーティリティっぽいのを作ってみました
package sample.controller;

public class AppUrlsTest extends AppUrlsTester {
    @Test
    public void checkPageMatcher(){
        if(AppEngineUtil.isProduction()){
            // Production環境ではクラスパスがとれないためテストしない
            return;
        }
        assertAppUrls("sample", new AppUrls());
    }
}
こんなテストクラスを1つ作っておくだけで、Pageが追加された時に自動的にAppUrls内のMatcher.get()の記述かあるかどうかを調べてくれます。ビバ自動化

仕組み
  • クラスパス配下を全て検索してPageクラスを検出する(ScenicPageを継承していて、かつ@Pageがついているクラス)
  • 検出したPageクラスに対応するMatcher.get()のtoString()の結果(パス情報)が全てAppUrlsに含まれているかを調べる

欠点
  • 任意のPageクラスだけ除外ということができない
  • Production環境上ではクラスパスが取得できないため、ローカル上でしかテストできない。
    クラスファイルを列挙するためにファイルシステムにアクセスしていますが、[Java] 指定パッケージ内のクラス一覧を取得するサンプルのやり方だとjavax.tools.*がappengineのSDKでサポートされていないためローカルで動かすこともできません
  • UrlsImplの実装に依存しているため、UrlsImplの仕様が変わるとアウト
  • ろくなドキュメントがない。ソース嫁

そんなわけで便利なテストクラスですが欠点もあるため使いたい場合は自己責任で使ってください


動作確認環境
  • Google App Engine SDK 1.4.2
  • Slim3 1.0.9
  • Scenic3 0.4.2

ソース
main以下を適当にインポートして使ってください


3/24追記

Scenic3の作者のshuji_w6eさんが僕のソースをヒントにAppUrlsの自動テストを実装してくれました。ありがとうございますー(0.5.0で対応予定)

Scenic3のAppUrlsに関するテスト - やさしいデスマーチ