わんくま同盟 名古屋勉強会 #20 に参加 & LTしてきました。

1/14にわんくま同盟 名古屋勉強会 #20 に参加 & LTしてきました。

TDDワークショップ

お題はこちら
http://www.tdd-net.jp/wankuma20-tdd-workshop-assignment.html

C言語を選んだ人は4人。2組のペアを作った。
最初にもう一方のペアの方にPCUnitの使い方を簡単に説明。スクリプトでテストの雛形を自動生成できたのでそんなに時間は食わなかったと思う。

お題その1はデータが何個でも入ること。で、その2からデータは2個までに変更とかC言語にはつらいお題だった。
時間は1時間しかなかったので都合のいいように解釈して、お題2からのスタート。

引数のキーにNULLを渡したら例外発生という仕様だがCには例外がないので戻り値0で代用しようと思ったのだが、Get()の戻り値でNULLはありうる。とりあえず引数のキーにNULLを渡さないことを事前条件ということにした。(でもこれじゃテストできないから-1を返すとかでもよかったかも)

C言語とはいえデータ構造の実装なのでオブジェクト指向的に抽象データ型(Cのクラス)にしようと最初思っていたけど、コード量が増えるし時間も少ないので泣く泣くstatic変数を使ってオブジェクトは一つだけ、という仕様にした。


最初にLruCacheMap.hを作ってAPIのプロトタイプ宣言のみ書いた。

void Put(const char *key, const char *data);
const char *Get(const char *key);

それから最初のテストを書くためにまずpcunit_template.rbスクリプトでテストの雛形を生成した。

$ pcunit_template.rb LruCacheMapTest -m -M

Makefile, main.c, LruCacheMapTest.cを生成。

LruCacheMapTest.cのtest_TODO()を修正して、次のように1回のPutとGetをするテストを作った。安易なテスト名だ。

static void test_PutGet1(void)
{
	Put("key1", "data1");
	PCU_ASSERT_STRING_EQUAL("data1", Get("key1"));
}

ここでmakeをするとPutとGetの実体がなくてリンクエラーになる。
LruCacheMap.cを作ってこのテストを通すための仮実装をする。

void Put(const char *key, const char *data)
{
}

const char *Get(const char *key)
{
	return "data1";
}

MakefileにOBJS += LruCacheMap.oを追加してmake。テストが通った。

でもこんな実装でいいわけないので次のテストを書く。今度は2回のPutとGetをするテストを作った。

static void test_PutGet2(void)
{
	Put("key1", "data1");
	Put("key2", "data2");
	PCU_ASSERT_STRING_EQUAL("data1", Get("key1"));
	PCU_ASSERT_STRING_EQUAL("data2", Get("key2"));
}

これでmakeすると当然失敗。

Suite: LruCacheMapTest

Test: test_PutGet2
 LruCacheMapTest.c:37: PCU_ASSERT_STRING_EQUAL("data2", Get("key2"))
  expected : "data2"
  actual   : "data1"

2 Tests, 1 Failures, 0 Skipped

ここで初めてPutとGetの実装をする。キーとデータの文字列はポインタのみコピーしているのでリテラルのみ対応ということにした。
ついでに初期状態にするためのInitも実装してテストのsetupでInitを呼ぶようにした。

#define DATA_SIZE 2

typedef struct Pair {
	const char *key;
	const char *data;
} Pair;

static Pair pair[DATA_SIZE];
static int next_idx = 0;

void Init(void)
{
	memset(pair, 0, sizeof pair);
	next_idx = 0;
}

void Put(const char *key, const char *data)
{
	assert(key);
	pair[next_idx].key = key;
	pair[next_idx].data = data;
	next_idx++;
	if (next_idx == DATA_SIZE) {
		next_idx = 0;
	}
}

const char *Get(const char *key)
{
	int i;
	assert(key);
	for (i = 0; i < DATA_SIZE; i++) {
		if (pair[i].key != NULL && strcmp(pair[i].key, key) == 0) {
			return pair[i].data;
		}
	}
	return NULL;
}

これで2つ目のテストも通った。

ここで時間切れ。あと何分と言われるとテンパってしまって冷静な思考ができず全然書けなかったorz
結局できたのは2個のデータをPutしてGetできることを確認しただけ。
同じキーをPutすると置き換わるという仕様のテストも実装も書けなかった。

中途半端な状態で終わってしまったが、テストファーストで書き始め→仮実装でテストをとりあえず通す→テスト追加→本当の実装をする、という手順は守れたのでよかった。

反省点はペアプロだったのに自分で説明しながら実装を全部やってしまったこと。ペアの人にコードを書いてもらって僕が後ろで口を出すというスタイルにしたほうがよかったかもしれない。退屈させてしまってたらごめんなさい。

初LT

LTはテストに関すること、というテーマだったので自作のユニットテストフレームワークのPCUnitの紹介をした。

(組み込み)C言語用のツールなんて需要あるかな?と思ったけど、知ってもらわないことには使ってもらえないので思い切ってLT申し込みした。
人前で喋るのは苦手でよく頭が真っ白になって口から言葉が出なくなってしまうので、何回も練習して口を慣れさせたら緊張したけど割と普通に喋れたのではと思う。

欲を言えば組み込みのお仕事してる人がいたらツッコミをいただきたかったけど、TDDワークショップではC言語チームもあったし、PCUnitのことを知って使ってもらえたのでやってよかったと思う。

追記

TDDワークショップのお題をC言語でもう一度やってみた。

リポジトリはこちら
https://bitbucket.org/katono/wankumatdd_lrucachemap

今度はお題その1から順番にやった。なのでコンテナが使いたいので以前自作したCSTLを使った。それから今度は複数のオブジェクトが持てるように抽象データ型にした。

お題1は何個でもデータが入るマップそのものなのでunordered_map(ハッシュマップ)を使った。ほとんどコンテナのラッパーみたいな感じ。

お題2は2個までの制限が付き、古いものから消していく仕様になったので、要素に順序のないunordered_mapは仕様に合わなくなった。そこで内部データ構造を、要素の型がキーと値のペアの構造体であるlistに変更した。これなら追加した順序を保てる。あと、お題1のテストも仕様に合わなくなったので修正した。

そういえば仕様に明記されてないけど、お題1はハッシュをデータ構造に使ったからPutとGetの計算量はO(1)だったが、お題2からはlistに変えたから計算量がO(n)になった。

お題3はPutやGetで使われたデータは最新扱いにするという仕様。これはお題2でlistに変更したからわりと簡単。List_splice()で使われたデータの要素を一番後ろにつなぎ換えてやればいい。