2011-07-04
TDDBC仙台で作成したコード
TDD | |
![]()
TDDBC仙台で我々JavaのBチームが作成したコードです。
まだ未完成ですし、誤字脱字などもありますが、あえてそのまま晒します。
個人的には、近いうちに最後まで実装したいと思います。
- LRUCacheTest
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; import static org.junit.Assert.assertThat; import java.util.Arrays; import java.util.Collections; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; public class LRUCacheTest { private LRUCache lru; @Before public void 前準備() { } @Test public void なにも足さずにgetするとnullガかえる() { lru = new LRUCache(); assertThat(lru.get("_"), nullValue()); assertThat(lru.get("a"), nullValue()); } @Test public void 文字列をputしてgetすると値が取得できる() { lru = new LRUCache(); lru.put("a", "dataA"); assertThat(lru.get("a"), is("dataA")); } @Test public void 文字列_をputしてgetすると値が取得できる() { lru = new LRUCache(); lru.put("_", "data_"); assertThat(lru.get("_"), is("data_")); } @Test public void 文字列aをputしてbでgetするとmullが取得できる() { lru = new LRUCache(); lru.put("a", "dataA"); assertThat(lru.get("b"), nullValue()); } @Test public void 何も要素を足さずにsizeを取得すると0が返される() { lru = new LRUCache(); assertThat(lru.size(), is(0)); } @Test public void まず1つ要素を足すとsizeに1が返る() { lru = new LRUCache(); lru.put("a", "dataA"); assertThat(lru.size(), is(1)); } @Test public void 要素を3つ足して最初のを取得するとnullが返る() { lru = new LRUCache(); lru.put("a", "dataA"); lru.put("b", "dataB"); lru.put("c", "dataC"); assertThat(lru.get("a"), nullValue()); } @Test public void デフォルトコンストラクタで生成するとキャッシュサイズ2が返る() { lru = new LRUCache(); assertThat(lru.cacheSize(), is(2)); } @Test public void コンストラクタ指定したサイズ3がキャッシュサイズが返る() { lru = new LRUCache(3); assertThat(lru.cacheSize(), is(3)); } @Test public void 要素を3つ足しても要素数がキャッシュサイズを越えない() { lru = new LRUCache(); lru.put("a", "dataA"); lru.put("b", "dataB"); lru.put("c", "dataC"); assertThat(lru.size(), is(2)); } // 防御的プログラミング @Test(expected = IllegalArgumentException.class) public void キャッシュサイズに0を渡すと例外が発生する() { lru = new LRUCache(0); } @Test public void 文字列を2回putしてgetすると後から追加した値が取得できる() { lru = new LRUCache(); lru.put("a", "dataA"); lru.put("a", "dataA2"); assertThat(lru.get("a"), is("dataA2")); } @Test public void 最初にキーリストを取得すると空のリストが返る() { lru = new LRUCache(); assertThat(lru.getKeyList(), notNullValue()); assertThat(lru.getKeyList().size(), is(0)); } @Test public void 要素を1つ足すとキーリストに値が追加される() { lru = new LRUCache(); lru.put("a", "dataA"); assertThat(lru.getKeyList(), notNullValue()); assertThat(lru.getKeyList().size(), is(1)); assertThat(lru.getKeyList().get(0), is("a")); } @Test public void 要素を2つ足すとキーリストに2つ値が追加される() { lru = new LRUCache(); lru.put("a", "dataA"); lru.put("b", "dataB"); assertThat(lru.getKeyList(), notNullValue()); assertThat(lru.getKeyList().size(), is(2)); assertThat(lru.getKeyList().get(0), is("a")); assertThat(lru.getKeyList().get(1), is("b")); } @Test public void 要素を3つ足すとキーリストにキーリストから最後の二つが返される() { lru = new LRUCache(); lru.put("a", "dataA"); lru.put("b", "dataB"); lru.put("c", "dataC"); assertThat(lru.getKeyList(), is(Arrays.asList("b", "c"))); } @Test public void 最初に値をgetしてもキーリストは空のまま() { lru = new LRUCache(); lru.get("a"); assertThat(lru.getKeyList(), is(Collections.<String> emptyList())); } @Test public void 要素を2つ追加して最初の値をgetするとキーリストの順番が変わる() { lru = new LRUCache(); lru.put("a", "dataA"); lru.put("b", "dataB"); lru.get("a"); assertThat(lru.getKeyList(), is(Arrays.asList("b", "a"))); } @Test public void 要素を2つ追加して最初の値をgetしてさらに要素を追加したらキーリストの順番が変わる() { lru = new LRUCache(); lru.put("a", "dataA"); lru.put("b", "dataB"); lru.get("a"); lru.put("c", "dataC"); assertThat(lru.getKeyList(), is(Arrays.asList("a", "c"))); } @Test public void 同じキーを複数回putしてもキーリストのサイズは1() { lru = new LRUCache(); lru.put("a", "dataA"); lru.put("a", "dataA"); lru.put("a", "dataA"); assertThat(lru.getKeyList(), is(Arrays.asList("a"))); } @Test public void キーをputした順番でキーリストに入る() { lru = new LRUCache(); lru.put("a", "dataA"); lru.put("b", "dataB"); lru.put("a", "dataA"); assertThat(lru.getKeyList(), is(Arrays.asList("b", "a"))); } @Test public void キャッシュサイズを越えたときキーをputした順番でキーリストに入る() { lru = new LRUCache(); lru.put("a", "dataA"); lru.put("b", "dataB"); lru.put("b", "dataBB"); assertThat(lru.getKeyList(), is(Arrays.asList("a", "b"))); } @Test public void 要素を3つ足した後最初に追加したキーでgetするとnullになるはず() { lru = new LRUCache(); lru.put("a", "dataA"); lru.put("b", "dataB"); lru.put("c", "dataC"); assertThat(lru.get("a"), nullValue()); } // TODO キーリストと値のマップの同期が取れていない @Ignore @Test public void 要素を3つ足した後最初に追加したキーでgetしてその後キーリストを取得する() { lru = new LRUCache(); lru.put("a", "dataA"); lru.put("b", "dataB"); lru.put("c", "dataC"); lru.get("a"); assertThat(lru.getKeyList(), is(Arrays.asList("b", "c"))); } }
- LRUCache
import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class LRUCache { private int cacheSize; private Map<String, String> map = new HashMap<String, String>(); private List<String> keyList = new ArrayList<String>(); public LRUCache(int cachesize) { if (cachesize <= 0) { throw new IllegalArgumentException(); } this.cacheSize = cachesize; } public LRUCache() { this.cacheSize = 2; } public String get(String key) { boolean containsKey = false; if (keyList.contains(key)) { keyList.remove(key); containsKey = true; } if (map.containsKey(key)) { keyList.add(key); } if (containsKey) { return map.get(key); } else { return null; } } public void put(String key, String value) { if (keyList.size() < this.cacheSize) { if (keyList.contains(key)) { keyList.remove(key); } keyList.add(key); } else { if (keyList.contains(key)) { keyList.remove(key); } else { keyList.remove(0); } keyList.add(key); } map.put(key, value); } public int size() { return Math.min(map.size(), this.cacheSize); } public int cacheSize() { return this.cacheSize; } public List<String> getKeyList() { return keyList; } }
TDDBC仙台に参加してきました
TDD | |
![]()
2011/7/2に開催されたTDDBC仙台に参加してきました。
非常に充実した内容でしたので簡単にまとめて見ます。
前日
TDD自体は10年ほど前から(当時はテストファースト開発)行っていますが、最近人のコードをレビューしてばかりでTDDどころかコードもあまり書けていない状態だったので、リハビリを兼ねて、id:t-wada さんが書かれた WEB+DB PRESS vol.63の特集1「現場で役立つ実践ノウハウWeb開発の「べし」「べからず」〜危険なコード,腐るテスト,不安定なインフラからの脱却〜」の第2章、 "テスト編 腐らないテストコードにするための「書き方」と「動かし方」" を再読かつ写経して翌日に備えました。
TDDBC
受付の際にTDD実習のチーム分けを言われ、私はJavaのBチームでした。全体のチーム構成としては、Javaが6チーム、その他に、Ruby、C#、PHP、Scalaが1チームずつの合計10チームです。
はじめに
基調講演の前に、id:t-wada さんから今回のTDDBCの経緯についてお話があり、協力者をtwitterで募り地元の沢山の方が協力したいと手を上げて下さったお陰でイベントが成立しているというお話がありました。主催者の id:yuichi_katahira さんを始め、スタッフの皆様本当にありがとうございます。
基調講演
お楽しみの基調講演。id:t-wada さんは紹介するまでも無いですが、TDDについての情報を探す際にはいつもお世話になっております。「プログラマが知るべき97のこと」(通称:きのこ本)の監修をされています。もちろん私は読了ずみです。
- 作者: 和田卓人,Kevlin Henney,夏目大
- 出版社/メーカー: オライリージャパン
- 発売日: 2010/12/18
- メディア: 単行本(ソフトカバー)
- 購入: 49人 クリック: 1,863回
- この商品を含むブログ (306件) を見る
さて、基調講演については、中身で気になったところをピックアップしておきます。
- テストを再分類
従来のテスト範囲による分類には曖昧さ・限界があるので、誰が何のために行うのかという観点で再分類。TDDは「Developer Testing」に分類され、開発者の開発者による開発者のためのテストである。
「バージョン管理」「テスティング」「自動化」の3つ。「三種の神器」ではなく「三脚椅子」のメタファ。「三種の神器」は1つ欠けても大丈夫だが、「三脚椅子」は1つ欠けると倒れてしまう。
- xUnit の登場
テスト方法が共有され、キャズムを超えた!
- 動作するきれいなコードへ
完ぺき主義の呪い→私も心当たりがあります。「TDDと黄金の回転」。
- TDDのこころ
1つずつ、すこしずつ。自分が最初のユーザ。不安をテストに。
- TDDの真の目的
健康。変更に対応できるのは健康体のコード。変更に対応できるのは健康体のコード。
- 事例
TDDの導入効果は、ざっくり言うと「工数2割増・欠陥5割減」。
→私の個人的な経験で言うと、欠陥は7〜8割減少という感覚です。
- 応用
TDDの助けとなる本を3冊+1冊紹介して頂きました。
- 既にテストの無いコードがたくさんある
レガシーコード改善ガイド→購入済み&読書中
レガシーコード改善ガイド (Object Oriented SELECTION)
- 作者: マイケル・C・フェザーズ,ウルシステムズ株式会社,平澤章,越智典子,稲葉信之,田村友彦,小堀真義
- 出版社/メーカー: 翔泳社
- 発売日: 2009/07/14
- メディア: 大型本
- 購入: 40人 クリック: 574回
- この商品を含むブログ (126件) を見る
- 既にデータの入ったデータベースがある
- 作者: スコット W アンブラー,ピラモド・サダラージ,梅澤真史,越智典子,小黒直樹
- 出版社/メーカー: ピアソンエデュケーション
- 発売日: 2008/03/26
- メディア: 単行本
- 購入: 9人 クリック: 170回
- この商品を含むブログ (40件) を見る
- テストが脆い+遅い
XUnit Test Patterns (鈍器)→購入済み&未読
xUnit Test Patterns: Refactoring Test Code (Addison-Wesley Signature Series (Fowler))
- 作者: Gerard Meszaros
- 出版社/メーカー: Addison-Wesley Professional
- 発売日: 2007/05/21
- メディア: ハードカバー
- 購入: 5人 クリック: 177回
- この商品を含むブログ (61件) を見る
- さらにもう一冊
Growing Object-Oriented Software Guided By Tests→ポチりました
Growing Object-Oriented Software, Guided by Tests (Addison-Wesley Signature Series (Beck))
- 作者: Steve Freeman,Nat Pryce
- 出版社/メーカー: Addison-Wesley Professional
- 発売日: 2009/10/12
- メディア: ペーパーバック
- 購入: 3人 クリック: 60回
- この商品を含むブログ (28件) を見る
- この先生「きのこ」るために
acts_as_professional。沢山本を読もう。写経しよう。
ペアプロデモ
id:t-wada さんと太田さんによるペアプログラミングのデモ。お題はFizzBuzzでした。TDDは他の人がやっているのを見ているだけでもワクワクしてきます。
TDD 実習(コーディング道場乱取り版)
我々JavaのBチームは3人構成でした。他のお二人にTDDの経験を伺ったところあまり経験が無いとのことで、形としては私がある程度リードする形で実習を進めることにしました。また、今回は純粋なペアプログラミングではなく、ペアを次々と入れ替えて行う乱取り形式で行われました。私のチームは5分毎にペアを入れ替えたのですが、ペアプロをやっていると5分は本当にあっという間です。
肝心ののお題ですが、「LRUキャッシュ」の実装でした。作成したコードについては別エントリにします。( http://d.hatena.ne.jp/i-takehiro/20110704/1309779205 )
2回にわけて各チームによる発表とコードレビューがあったのですが、我々は2回目に発表しました。一応私が発表者として作成したコードを説明し、最後に id:t-wada コメントを頂いたのですが、我々のチームのテストの粒度が良いとのコメントを頂けて嬉しかったのと同時に、今まで独学でやってきたTDDの方向性がそんなに間違ってはいなかったとという自信にもつながりました。
最後に id:t-wada さんが実装したJavaScript版のテストコードを見せて頂いたのですが、テストが文脈(コンテキスト)ベースに分類され、非常に洗練されたものでした。私自身TDDでテストコードを書くとつい多すぎるほどのテストを作成してしまい、あとでコントロールに困る場合があるのですが、文脈ベースで作成されたコードはその問題に対する1つの答えを見たようでした。
ふりかえり
「技術書を各チーム1冊ずつプレゼント」コーナーがありました。抽選の結果、我々Java Bチームにはきのこ本が当たったのですが、私は既に購入済みなので他のお二人に権利を譲りました。
その後、スタッフTシャツを先着1名にプレゼントして下さるとのことで勢いよく手を上げたところ、運良くゲット出来ました。DDD本の表紙の絵がプリントしてあるクールなTシャツです。ありがとうございました。
最後にKPTのコーナーということで、各自が付箋に記入して模造紙に張っていきました。私のKTPは以下の通りです。
- Keep 細かい単位でテストする
- Problem リファクタリングが十分に出来なかった
- Try 文脈ベースのテスト
なお、参加した皆さんのKTPは、id:yuichi_katahira さんが TDDBC仙台01 KPT にまとめて下さっています。
懇親会
久しぶりに懇親会にも参加させて頂きました。
会社では技術について熱い話をしてくれる人が殆どいないので、このような機会は本当に貴重です。
きのこ本を持参していたので、ミーハー心で id:t-wada さんにサインをお願いしたところ快く書いて頂きました。ありがとうございます。
また、色々な方とお話している間に、「DDDの勉強会とかやりたいですよね」とつぶやいたりしたのですが、何人かの方から「言い出しっぺの法則」と言われ、さらに id:yuichi_katahira さんから、DDD本の翻訳をされた id:digitalsoul さんをご紹介頂き、勉強会をやるならお手伝いをして頂けるという嬉しいお言葉を頂きました。これはやるしかですね。
最後に
繰り返しになりますが、id:t-wada さん、id:yuichi_katahira さん、その他のスタッフの皆さん、すばらしいイベントを実施して頂きありがとうございました。
また、TDD実習で一緒になったお二人もありがとうございました。私のつたないサポートでお二人が少しでもTDDを好きになって貰えていたら本当に嬉しいです。
2011-06-30
デブサミ東北でTDDBCに参加します
TDD | |
![]()
7/2(土)に Developers Summit 2011 Tohoku (通称:デブサミ2011東北)が開催されます。
http://codezine.jp/devsumi/2011/tohoku
どのセッションも参加したいですが、今回は TDD Boot Camp(TDDBC) に参加します。
2008-08-09
Seasar Conference 2008 Autumn
SAStruts | |
![]()
気が付いたら申し込みが開始されていたので、早速申し込みました。
会社からお金は出ないと思うので、仙台から自費で参加します(泣)。
「SAStrutsとS2JDBCの最新機能」と「SAStrutsの開発Tips」はどちらも聞きたいんですが、
同じ時間なんですよねー。どっちに出ようかなー。
2008-07-10
SAStrutsで独自の検証用アノテーションを作成する
SAStruts | |
![]()
SAStrutsで既に@Requiredなどいくつかの検証用アノテーションが利用できます。
そこで、それらとは別に独自の検証用アノテーションを作成しようと思ったのですが、
私の探し方が悪いのかSAStrutsのドキュメントなどには記述が見つかりませんでした。
仕方が無いので、SAStrutsのソースコードを追いながら、作成してみたいと思います。
SAStrutsのドキュメント内に記述がありました。以下のリンクの最後のほうを参照です。
id:higayasuo さん、ありがとうございます。
http://sastruts.seasar.org/featureReference.html#Validator
検証用アノテーションクラスを作成する
今回は、郵便番号を検証するアノテーションを作成します。
@Maskを利用すれば実現できますが、まあサンプルということで…
Zip.java
package takehiro.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.seasar.struts.annotation.Arg; import org.seasar.struts.annotation.Msg; import org.seasar.struts.annotation.Validator; /** * 郵便番号かどうかを検証するためのアノテーションです。 * * @author takehiro * */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @Validator("zip") public @interface Zip { /** * メッセージです。 * */ Msg msg() default @Msg(key = "errors.zip"); /** * メッセージの最初の引数です。 * */ Arg arg0() default @Arg(key = ""); /** * 検証の対象となるメソッド名を指定します。 複数ある場合はカンマで区切ります。 * */ String target() default ""; }
@Validator("zip") を指定するのがミソです。引数に指定した "zip" が
このアノテーションを指すキーになります。
検証用メソッドを作成する
次は、実際に郵便番号を検証するメソッドを作成します。
org.seasar.struts.validator.S2FieldChecks を継承して新しいクラスを作成しました。
CustomizedS2FieldChecks.java
package takehiro.validator; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; import org.apache.commons.validator.Field; import org.apache.commons.validator.GenericValidator; import org.apache.commons.validator.Validator; import org.apache.commons.validator.ValidatorAction; import org.apache.struts.action.ActionMessages; import org.seasar.struts.validator.S2FieldChecks; /** * Seasar2用の独自検証メソッドです。 * * @author takehiro * */ public class CustomizedS2FieldChecks extends S2FieldChecks { private static final long serialVersionUID = 1L; /** * 郵便番号かどうかをチェックします。 * * @param bean * JavaBeans * @param validatorAction * バリデータアクション * @param field * フィールド定義 * @param errors * エラーメッセージの入れ物 * @param validator * バリデータ * @param request * リクエスト * @return 検証結果がOKかどうか */ public static boolean validateZip(Object bean, ValidatorAction validatorAction, Field field, ActionMessages errors, Validator validator, HttpServletRequest request) { String value = getValueAsString(bean, field); if (!GenericValidator.isBlankOrNull(value)) { final Pattern zipPattern = Pattern.compile("\\d{3}-\\d{4}"); final Matcher matcher = zipPattern.matcher(value); if (!matcher.matches()) { addError(errors, field, validator, validatorAction, request); return false; } } return true; } }
検証用メソッドを登録する
先ほど作成した検証用のメソッドを登録します。
具体的には、validator-rules.xml に登録することになります。
ここで、name属性の "zip" は、アノテーションクラスの@Validatorの引数と同じにします。
また、msg属性の "errors.zip" は、次に追加するエラーメッセージのキーとなります。
validator-rules.xml
…中略
<validator name="zip"
classname="takehiro.validator.CustomizedS2FieldChecks"
method="validateZip"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
org.apache.commons.validator.Validator,
javax.servlet.http.HttpServletRequest"
depends=""
msg="errors.zip"/>
…中略
エラーメッセージを追加する
郵便番号の検証でエラーが発生した場合に表示するエラーメッセージを追加します。
先ほど書いたように、キー "errors.zip" は、validator-rules.xml の msg 属性と一致させます。
application_ja.properties
errors.zip={0}は郵便番号として不正です。
application.properties
errors.zip={0} is an invalid zip code.
Actionのpublicフィールドに、作成したアノテーションを追加する
今回は、SAStrutsのチュートリアルに含まれる ValidatorAction を利用します。
以下のように、郵便番号を入力するフィールドを新しく追加します。
ValidatorAction.java
…中略
@Required
@Zip
public String zip;
…中略
@Execute(validator = false)
public String index() {
byteText = "1";
shortText = "1";
integerText = "1";
longText = "1";
floatText = "1.0";
doubleText = "1.0";
dateText = "2008/1/1";
emailText = "higayasuo@gmail.com";
urlText = "http://d.hatena.ne.jp/higayasuo";
intRangeText = "7";
longRangeText = "7";
floatRangeText = "7.0";
doubleRangeText = "7.0";
minlengthText = "123";
maxlengthText = "1234567890";
minbytelengthText = "ああ";
maxbytelengthText = "あああああ";
phoneText = "03-9999-9999";
zip = "999-9999";
return "validator.vm";
}
…中略
画面(vmファイル)を修正する
validator.vm
…中略 <tr> <td>郵便番号:</td><td><input type="text" name="zip" value="$!zip"></td> </tr> …中略
以上で全ての準備が完了しました。
実際動かしてみる
無事動きました。エラーメッセージもちゃんと表示されます。
まとめ
実際のPJなどでは、今回のように独自の検証を行うケースがあると思うので
今回のような作業がマニュアル化されてると嬉しいですね。
SAStrutsのドキュメント内に記述がありました。以下のリンクの最後のほうを参照です。
id:higayasuo さん、ありがとうございます。
higayasuo
http://sastruts.seasar.org/featureReference.html#Validatorの最後の所に載ってますよ。
i-takehiro
> higayasuo さん
コメントありがとうございます。
確かにドキュメントに記述がありますね。見落としておりました。
本文にも追記しておきます。
2008-06-04
Seasar勉強会 in Sendai が開催されます!
SAStruts | |
![]()
東北デベロッパーズコミュニティから、Seasar勉強会 in Sendai の開催案内が来ました。
イベント案内 | 2008-06-14 (土) Seasar勉強会 in Sendai - 東北デベロッパーズコミュニティ
早速、参加の申し込みをしました。
以下、タイムテーブルです。
14:00〜14:10 開催にあたって
14:10〜15:10 S2JDBCについて(ひがやすを様から)
15:20〜17:10 東北でのSeasar事例紹介(各30分ずつ)
17:20〜18:05 SAStrutsについて
18:05〜18:15 閉会&今後の予定について
18:30〜 懇親会
目玉は、Seasarプロジェクトチーフコミッタのひがやすをさんです。
東北デベロッパーズコミュニティ設立総会に参加した時はお話しする機会がなかったので、
今回はぜひともお話出来ればなと思います。





自分でもちょっとコードを育ててみようと思います。
TDD だけでなくペアプロも初でしたが、スムーズにできました。
ありがとうございました。
こちらこそありがとうございました。
コードを育てたらぜひ公開してみてくださいね。