SIerを退職しギルドワークスと仕事を始めます

3/31付けで仙台のSIerを退職しました。

本日4/1からは仙台を拠点としたフリーランスのエンジニアとなります。
そして、本日設立されましたギルドワークス株式会社と一緒に仕事をしていくことになりました。

GuildWorks -ギルドワークス-

自分がやりたいことと会社が目指す方向性に大きな溝を感じて、何とかするべく始めた社外での勉強会やコミュニティ活動でしたが、会社との思いの溝が埋まることは無く、また会社の合併が発表されて色々と考えていた所にギルドワークス設立メンバーである増田さん([twitter:@masuda220])に声を掛けて頂き、さらにはギルドワークス代表でもある市谷さん([twitter:@papanda])にもギルドワークスへの想いをお話頂き、今回このような運びになりました。

これまで勉強してきたドメイン駆動設計(DDD)やテスト駆動開発(TDD)等を生かしながら、ギルドワークスが大事にする「正しいものを正しくつくる!!」をモットーに頑張っていきたいと思います。

仙台を拠点に活動しますので、東北デベロッパーズの運営委員や勉強会等のコミュニティ活動も引き続き続けていきます。

皆様今後ともよろしくお願い致します。

TDDBC 仙台 the 3rd に参加しました

2013/10/12 に開催された TDDBC 仙台 the 3rd にTAとして参加しました。

TDDBC 仙台 the 3rd - TDDBC | Doorkeeper

TDDBC 仙台 the 3rd #tddbc のまとめ - Togetter

今回はデータコム株式会社さんを会場にお借りしました。ありがとうございます。

開催準備

今回は@135yshrさんがTDDBC仙台の主催に名乗りを上げて頂きました。
そして、前回の主催者である@nnasakiと私が中心メンバーとなって事前準備を進めました。

前日には仙台スタッフに加えて@t_wadaさん他、TAスタッフ(仙台以外からも!)が集まって翌日の流れの確認や、チーム分け等を実施しました。

基調講演

第一回/第二回に引き続き、今回も@t_wadaさんに基調講演をして頂きました。ありがとうございます。
今回は、裏方の作業とペアプログラミングデモの準備もあり、じっくりお話を聴けずに残念。

ペアプログラミングデモ

今年は、@leecomさんとペアプログラミングデモを行うことになりました。

昨年は@t_wadaさんとFizzBuzzを題材にペアプログラミングデモを行ったのですが、その後の演習でTDD初心者が「飲み物自動販売機」を解くには大分ギャップがあったと感じました。実際に、演習では進め方に四苦八苦するペアが散見されて、次回はもう少し工夫した方が良いと思っていました。

そう思っていた所、他会場のTDDBCで、実際の課題を使ってペアプログラミングをしていたことを知り、今年は実際の演習課題である「整数の区間」を使うことにしました。今回のTDDBCは初心者をターゲットにしていましたので、ペアプログラミングデモを見てもらい、演習で内容をそのまま追体験してもらうことで、Red-Green-Refactoring のいわゆる「黄金の回転」のリズムを掴んでもらう手助けが出来たのではないかと思います。

ペアプログラミング実習

私はJavaのTAとして参加したのですが、Javaでの参加者が4人だったということもあり、参加者の方とはペアを組まず、外からアドバイスをすることとなりました。

今回の言語別のペア数は Java x 2, C# x 1, Groovy x 2(内1組がKotlin), Python x 2, Objective-C x 1, Ruby x 1, PHP x 1 と非常に多彩な顔ぶれとなりました。
また、1台のPCでペアプログラミングをするのではなく、GitHub等でコードを共有してしたペアも何組か見られましたね。

演習の課題について

演習の課題は「区間」をテーマにすることは仙台メンバ全員で決めたのですが、詳細については私が担当することとなり、前日のスタッフ打ち合わせ直前まで頭を抱えながら作成しました。

課題「整数の区間」は、以下に公開しています。
前日には@t_wadaさんからアドバイスを頂き、お題の順番変更などの調整を行いました。
また、本番では課題2までしか公開しませんでしたが、課題8まであります。

TDD Boot Camp(TDDBC) - TDDBC仙台03/課題

また、用語集は@nnasakiさんに作成して頂きました。

TDD Boot Camp(TDDBC) - TDDBC仙台03/課題用語集

実際の演習では、最初提示されるのは課題1だけで、課題1を解くと課題2、課題2を解くと課題3が提示される形式を取りました。
新しい課題が提示されると、それまで作成したコードに修正が必要となるような構成となっており、リファクタリングが促進されるような仕掛けになっています。
個人的に今回のこの試みはとても上手く行ったのではないかと思いました。

反省点としては以下の通りです。

  • 仕様に関する質問が何度があったので、もう少し誤解がない記述が必要かな
  • 結果課題2までしか進まなかったので、課題1/課題2のボリュームをもう少し減らして、課題3(閉区間と開区間を同時に扱う)へ早めに到達するように調整したほうが良さそう

(課題については、別エントリでもう少し詳しく書こうかな・・)

コードレビュー

全7種類の言語から1組ずつ前に出てもらい、恒例のコードレビューです。
今回は比較的言語に依存しないお題だったため、各言語間でプロダクトコードに大きな違いはなく、どちらかというとテスティングフレームワークの違いがよくわかる
コードレビューとなりました。普段Java以外に触れることが少ないため、とても参考になりました。

張り切って質問等していたら、以下のようなお褒めの言葉(?)を頂き、とりあえずTAとしての役割は果たしたということで。

懇親会

本編終了後は、そのままビアバッシュへ突入です。殆どの方が参加して、大変盛り上がりました。
LT大会では、課題作成の裏側等を話しました。

最後に

今年のTDDBCも最初から最後まで熱気にあふれていましたね。運営側としてはキャンセル0というのも素晴らしい。

また、仙台発のお題を作るという個人的な裏テーマを達成出来たのと、何人かの方からお題を褒めて頂いたので満足です。

来年もTDDBC仙台が開催されると良いですね。今回関わって下さった皆さん、ありがとうございました。

学習テストで学ぶJUnit

9/18のJava勉強会で「学習テストで学ぶJUnit」という内容でお話してきました。

2013-09-18 (水) Java勉強会 - TDC - ニュース - 東北デベロッパーズコミュニティ(TDC)

今回も日本オラクル東北支社のセミナールームをお借りしての開催です。いつもありがとうございます。

はじめに

今回の勉強会は参加者の皆さんに手を動かしてもらいながら進めていきます。
まずは環境構築です。今回は参加者全員がEclipseを使用していました。

学習テストとは

学習テストとは、サードパーティのコードを調査し理解するために作成するテストコードのことです。
テスト駆動開発入門」や「Clean Code アジャイルソフトウェア達人の技」に詳しい内容が書かれています。

外部で制作されたソフトウェア用のテストをいつ作成するのか。→パッケージ内の新機能を初めて使用する前に作成する。
(中略)
ジェームズ・ニューカークは学習用のテストを日常的に作成するプロジェクトについて報告した。パッケージの新規リリースが届いたら、最初にそのテストを実行する(そして、必要に応じて修正する)。テストが動作しなければ、アプリケーションもおそらく動作しないので、アプリケーションを実行させる意味がない。テストが動作すれば、いつでもアプリケーションは動作する。

[テスト駆動開発入門 p134-135 学習用のテスト]

サードパーティのコードについて学習するのは大変なことです。サードパーティのコードを統合するのもやはり大変なことです。そしてこれら2つを同時に行うことは2倍大変です。別の方法をとってみてはどうでしょう?実際に開発中の製品コードを使って実験する代わりに、サードパーティのコードを調査し理解するためにテストコードを書くのです。ジム・ニューカークは、こうしたテストを学習テストと呼んでいます。

[Clean Code アジャイルソフトウェア達人の技 p166 境界の調査と学習]

今回の勉強会では、Java SE APIを題材とした学習テストを作成していきます*1

なぜ学習テストを題材にしたのか

これまでTDDBCのTAを経験したりJUnit勉強会の開催をしてみて、初学者がユニットテストを書くというのはかなりハードルが高いことだと感じていました。
実際TDDBC等では、最初のユニットテストをどうやって書いてよいかずっと悩んでいるペアも見受けられたりしました。

今回の勉強会では、参加者8名の内、JUnitを初めて使う方が7名いたので、まずはユニットテストの書き方に慣れてもらうため、学習テストを書いてもらうのが良いのではないかと思い、今回の勉強会の題材としました。

学習テストの例

学習テストの例としてjava.lang.Math#abs(int)メソッドを取り上げます。
学習テストを作成するには、まず仕様の確認ということで Java API ドキュメントを見てみます。

public static int abs(int a)
int 値の絶対値を返します。引数が負でない場合は引数そのものを返します。負のときは、その正負を逆にした値を返します。
引数が Integer.MIN_VALUE の値 (int の最小値) と等しい場合は、結果も同じ値 (負の値) になります。

パラメータ:
a - 絶対値を決定する引数
戻り値:
引数の絶対値。

http://docs.oracle.com/javase/jp/7/api/java/lang/Math.html#abs(int)

上記内容から、学習テストのテスト項目となるものをTODOコメントとして作成してみます。
Mathクラスのテストなので、テストクラス名はMathTestとしました。

package learning;

public class MathTest {
    //TODO absに1を渡すと1を返す
}

ここで、しばし演習タイムです。
仕様を確認しながら、残りのテスト項目をTODOコメントとして挙げてもらいました。

私の方では、 引数(int型)を同値クラスに分割し、その境界値をテスト項目として挙げました。

同値クラス 範囲
正の数 1以上2147483647(Integer.MAX_VALUE)以下
0 0
負の数(最小値を除く) -2147483647以上-1以下
負の最小値 -2147483648(Integer.MIN_VALUE)
package learning;

public class MathTest {
    //TODO absに1を渡すと1を返す
    //TODO absに0を渡すと0を返す
    //TODO absにマイナス1を渡すと1を返す
    //TODO absに2147483647を渡すと2147483647を返す
    //TODO absマイナス2147483648を渡すとマイナス2147483648を返す
    //TODO absマイナス2147483647を渡すと2147483647を返す
}

ここまで出来たら、仕様から読み取った内容が正しいかを検証するため、学習テストを作成して行きます。
また、テスト項目名をそのままテストメソッド名にします。

package learning;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import org.junit.Test;

public class MathTest {

    @Test
    public void absに1を渡すと1を返す() {
        assertThat(Math.abs(1), is(1));
    }

    //TODO absに0を渡すと0を返す
    //TODO absにマイナス1を渡すと1を返す
    //TODO absに2147483647を渡すと2147483647を返す
    //TODO absマイナス2147483648を渡すとマイナス2147483648を返す
    //TODO absマイナス2147483647を渡すと2147483647を返す
}

assertThatメソッドの第一引数に実測値(actual value)、第二引数に期待値(expected value)を渡します。
Ctrl + 0 でテストを実行すると、無事グリーンになりました。

同様に他のテスト項目もコードにしてみます。

package learning;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import org.junit.Test;

public class MathTest {

    @Test
    public void absに1を渡すと1を返す() {
        assertThat(Math.abs(1), is(1));
    }

    @Test
    public void absに0を渡すと0を返す() {
        assertThat(Math.abs(0), is(0));
    }

    @Test
    public void absにマイナス1を渡すと1を返す() {
        assertThat(Math.abs(-1), is(1));
    }

    @Test
    public void absに2147483647を渡すと2147483647を返す() {
        assertThat(Math.abs(2147483647), is(2147483647));
    }

    @Test
    public void absマイナス2147483648を渡すとマイナス2147483648を返す() {
        assertThat(Math.abs(-2147483648), is(-2147483648));
    }

    @Test
    public void absマイナス2147483647を渡すと2147483647を返す() {
        assertThat(Math.abs(-2147483647), is(2147483647));
    }
}

ここまで作成すれば java.lang.Math#abs(int) メソッドの仕様が理解出来るので、学習テストとしてはこれで十分かと思います。

演習

次は演習です。今度は java.lang.String#substring(int) メソッドが題材です。
先ほどと同様、仕様を確認します。

public String substring(int beginIndex)

この文字列の部分文字列である新しい文字列を返します。部分文字列は指定されたインデックスで始まり、この文字列の最後までになります。

"unhappy".substring(2) returns "happy"
"Harbison".substring(3) returns "bison"
"emptiness".substring(9) returns "" (an empty string)

パラメータ:
beginIndex - 開始インデックス (この値を含む)。
戻り値:
指定された部分文字列。
例外:
IndexOutOfBoundsException - beginIndex が負の値の場合、あるいはこの String オブジェクトの長さより大きい場合。

http://docs.oracle.com/javase/jp/7/api/java/lang/String.html#substring(int)

これらを踏まえ、先ほどと同様、テスト項目をTODOコメントで作成して行きます。

package learning;

import org.junit.Test;

public class StringTest {
    //TODO 文字列abcdefのsubstringに0を渡すとabcdefを返す
    //TODO 文字列abcdefのsubstringに1を渡すとbcdefを返す
    //TODO 文字列abcdefのsubstringに6を渡すと空文字列を返す
    //TODO 文字列abcdefのsubstringにマイナス1を渡すと例外が発生する
    //TODO 文字列abcdefのsubstringに7を渡すと例外が発生する
    //TODO 空文字列のsubstringに0を渡すと空文字列を返す
}

文字列"abcdef"(文字列長6)を対象として、テスト項目を挙げてみました。

同値クラス 範囲
負の数 -1以下
0以上文字列長以下 0以上6以下
文字列長より大きい 7以上

また、空文字列(文字列長0)についてのテスト項目も入れてみました。
今度は、例外が発生することを検証する必要がありますね。
テストコードに落とし込んでいきます。

package learning;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import org.junit.Test;

public class StringTest {

    @Test
    public void 文字列abcdefのsubstringに0を渡すとabcdefを返す() {
        assertThat("abcdef".substring(0), is("abcdef"));
    }

    @Test
    public void 文字列abcdefのsubstringに1を渡すとbcdefを返す() {
        assertThat("abcdef".substring(1), is("bcdef"));
    }

    @Test
    public void 文字列abcdefのsubstringに6を渡すと空文字列を返す() {
        assertThat("abcdef".substring(6), is(""));
    }

    @Test(expected = StringIndexOutOfBoundsException.class)
    public void 文字列abcdefのsubstringにマイナス1を渡すと例外が発生する() {
        "abcdef".substring(-1);
    }

    @Test(expected = StringIndexOutOfBoundsException.class)
    public void 文字列abcdefのsubstringに7を渡すと例外が発生する() {
        "abcdef".substring(7);
    }

    @Test
    public void 空文字列のsubstringに0を渡すと空文字列を返す() {
        assertThat("".substring(0), is(""));
    }
}

無事全てグリーンとなった所で、今回は時間切れとなりました。
JUnitについては本当に基礎的な使い方しか出来なかったので、次回以降の勉強会でその辺りも触れられたらなと思います。

TDDBC仙台の告知

最後に、10/12に開催される「TDDBC 仙台 the 3rd」の告知を主催者の砂金さんからして頂きました。
私もJavaのTAとして参加予定です。

私自身、TDDを通じて学ぶ所が多かったので、未体験の方には是非参加してほしいです。

TDDBC 仙台 the 3rd - TDDBC | Doorkeeper

参考文献

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)

テスト駆動開発入門

テスト駆動開発入門

Clean Code アジャイルソフトウェア達人の技

Clean Code アジャイルソフトウェア達人の技

*1:OracleJava SE APIの学習テストを公開するか、もしくは学習テストをコミット出来る場を提供して、有志が学習テストを追加していけるようにすれば、Java SE APIの正しい使い方が広まるのに一役買うのではないかと思ったりします。

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

2011/7/2に開催されたTDDBC仙台に参加してきました。
非常に充実した内容でしたので簡単にまとめて見ます。

前日

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

WEB+DB PRESS Vol.63

WEB+DB PRESS Vol.63

  • 作者: 竹迫良範,和田卓人,おにたま,中島聡,角田直行,はまちや2,上谷隆宏,青木俊介,大塚知洋,生尾剛士,大和田純,永安悟史,馬場俊彰,久保達彦,白土慧,じゅんいち☆かとう,太田昌吾,小野修司,ミック,嶋田裕二,個々一番,みやけん,清水亮,WEB+DB PRESS編集部
  • 出版社/メーカー: 技術評論社
  • 発売日: 2011/06/24
  • メディア: 大型本
  • 購入: 20人 クリック: 434回
  • この商品を含むブログ (22件) を見る

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つ欠けると倒れてしまう。

  • xUnit の登場

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

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

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

  • TDDのこころ

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

  • TDDの真の目的

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

  • 事例

TDDの導入効果は、ざっくり言うと「工数2割増・欠陥5割減」。
→私の個人的な経験で言うと、欠陥は7〜8割減少という感覚です。

  • 応用

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

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

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

レガシーコード改善ガイド (Object Oriented SELECTION)

レガシーコード改善ガイド (Object Oriented SELECTION)

    • 既にデータの入ったデータベースがある

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

データベース・リファクタリング

データベース・リファクタリング

  • 作者: スコット W アンブラー,ピラモド・サダラージ,梅澤真史,越智典子,小黒直樹
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2008/03/26
  • メディア: 単行本
  • 購入: 10人 クリック: 211回
  • この商品を含むブログ (53件) を見る

    • テストが脆い+遅い

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

最後に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を好きになって貰えていたら本当に嬉しいです。

デブサミ東北でTDDBCに参加します

7/2(土)に Developers Summit 2011 Tohoku (通称:デブサミ2011東北)が開催されます。

http://codezine.jp/devsumi/2011/tohoku

どのセッションも参加したいですが、今回は TDD Boot Camp(TDDBC) に参加します。

http://codezine.jp/devsumi/2011/tohoku/special

TDD好きの私ですが、最近のTDDのトレンドについていけていないので色々吸収出来たらなと思います。

seasar-users.jp

普段、はてなRSSを使っていて、「SAStruts」をキーワードに登録しているのですが、
そのなかでこのサイトを見つけました。

seasar-users.jp

Seasarに関連する情報を集めているサイトのようですね。
とりあえず、SAStruts チートシートが便利そうです。

「このサイトについて」を見ると、「Seasar プロジェクトとは関係ありません」とあります。
どなたかが個人で運営されているのでしょうか?