Hatena::ブログ(Diary)

i-takehiroの日記 このページをアンテナに追加 RSSフィード

2011-07-04

TDDBC仙台で作成したコード

| 20:33 | TDDBC仙台で作成したコードを含むブックマーク TDDBC仙台で作成したコードのブックマークコメント

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仙台に参加してきました

| 20:27 | TDDBC仙台に参加してきましたを含むブックマーク TDDBC仙台に参加してきましたのブックマークコメント

2011/7/2に開催されたTDDBC仙台に参加してきました。

非常に充実した内容でしたので簡単にまとめて見ます。

前日

TDD自体は10年ほど前から(当時はテストファースト開発)行っていますが、最近人のコードをレビューしてばかりでTDDどころかコードもあまり書けていない状態だったので、リハビリを兼ねて、id:t-wada さんが書かれた WEB+DB PRESS vol.63の特集1「現場で役立つ実践ノウハウWeb開発の「べし」「べからず」〜危険なコード,腐るテスト,不安定なインフラからの脱却〜」の第2章、 "テスト編 腐らないテストコードにするための「書き方」と「動かし方」" を再読かつ写経して翌日に備えました。

TDDBC

受付の際にTDD実習のチーム分けを言われ、私はJavaのBチームでした。全体のチーム構成としては、Javaが6チーム、その他に、RubyC#PHPScalaが1チームずつの合計10チームです。

はじめに

基調講演の前に、id:t-wada さんから今回のTDDBCの経緯についてお話があり、協力者をtwitterで募り地元の沢山の方が協力したいと手を上げて下さったお陰でイベントが成立しているというお話がありました。主催者の id:yuichi_katahira さんを始め、スタッフの皆様本当にありがとうございます。

基調講演

お楽しみの基調講演。id:t-wada さんは紹介するまでも無いですが、TDDについての情報を探す際にはいつもお世話になっております。「プログラマが知るべき97のこと」(通称:きのこ本)の監修をされています。もちろん私は読了ずみです。

プログラマが知るべき97のこと

プログラマが知るべき97のこと

さて、基調講演については、中身で気になったところをピックアップしておきます。

  • テストを再分類

従来のテスト範囲による分類には曖昧さ・限界があるので、誰が何のために行うのかという観点で再分類。TDDは「Developer Testing」に分類され、開発者の開発者による開発者のためのテストである。

「バージョン管理」「テスティング」「自動化」の3つ。「三種の神器」ではなく「三脚椅子」のメタファ。「三種の神器」は1つ欠けても大丈夫だが、「三脚椅子」は1つ欠けると倒れてしまう。

テスト方法が共有され、キャズムを超えた!

  • 動作するきれいなコードへ

完ぺき主義の呪い→私も心当たりがあります。「TDDと黄金の回転」。

  • TDDのこころ

1つずつ、すこしずつ。自分が最初のユーザ。不安をテストに。

  • TDDの真の目的

健康。変更に対応できるのは健康体のコード。変更に対応できるのは健康体のコード。

  • 事例

TDDの導入効果は、ざっくり言うと「工数2割増・欠陥5割減」。

→私の個人的な経験で言うと、欠陥は7〜8割減少という感覚です。

  • 応用

TDDの助けとなる本を3冊+1冊紹介して頂きました。

    • 既にテストの無いコードがたくさんある

レガシーコード改善ガイド→購入済み&読書中

データベースリファクタリング→ポチりました

    • テストが脆い+遅い

XUnit Test Patterns (鈍器)→購入済み&未読

xUnit Test Patterns: Refactoring Test Code (Addison-Wesley Signature Series (Fowler))

xUnit Test Patterns: Refactoring Test Code (Addison-Wesley Signature Series (Fowler))

    • さらにもう一冊

Growing Object-Oriented Software Guided By Tests→ポチりました

Growing Object-Oriented Software, Guided by Tests (Addison-Wesley Signature Series (Beck))

Growing Object-Oriented Software, Guided by Tests (Addison-Wesley Signature Series (Beck))

  • この先生「きのこ」るために

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シャツです。ありがとうございました。

f:id:i-takehiro:20110705010708j:image

最後にKPTのコーナーということで、各自が付箋に記入して模造紙に張っていきました。私のKTPは以下の通りです。

  • Keep 細かい単位でテストする
  • Problem リファクタリングが十分に出来なかった
  • Try 文脈ベースのテスト

なお、参加した皆さんのKTPは、id:yuichi_katahira さんが TDDBC仙台01 KPT にまとめて下さっています。

懇親会

久しぶりに懇親会にも参加させて頂きました。

会社では技術について熱い話をしてくれる人が殆どいないので、このような機会は本当に貴重です。

きのこ本を持参していたので、ミーハー心で id:t-wada さんにサインをお願いしたところ快く書いて頂きました。ありがとうございます。

f:id:i-takehiro:20110705010811j:image

また、色々な方とお話している間に、「DDDの勉強会とかやりたいですよね」とつぶやいたりしたのですが、何人かの方から「言い出しっぺの法則」と言われ、さらに id:yuichi_katahira さんから、DDD本の翻訳をされた id:digitalsoul さんをご紹介頂き、勉強会をやるならお手伝いをして頂けるという嬉しいお言葉を頂きました。これはやるしかですね。

最後に

繰り返しになりますが、id:t-wada さん、id:yuichi_katahira さん、その他のスタッフの皆さん、すばらしいイベントを実施して頂きありがとうございました。

また、TDD実習で一緒になったお二人もありがとうございました。私のつたないサポートでお二人が少しでもTDDを好きになって貰えていたら本当に嬉しいです。

yscodeyscode 2011/07/05 07:16 コード公開、ありがとうございます。
自分でもちょっとコードを育ててみようと思います。
TDD だけでなくペアプロも初でしたが、スムーズにできました。
ありがとうございました。

i-takehiroi-takehiro 2011/07/05 21:38 > yscode さん
こちらこそありがとうございました。
コードを育てたらぜひ公開してみてくださいね。