TDD Boot Camp 名古屋 1 日目でやったことを Python で復習する
タイトルとおりです.
id:t-wada さんからも許可得ることができたのでお題も Python で書き直してみます.
あ,ちなみに Python 3.1 です.
お題
↓のような仕様の FileStore クラスを作ります.
>>> store = FileStore() >>> sotre.set('foo', 'hoge') >>> store.get('foo') 'hoge' >>> store.dump() 'foo:hoge\n' >>> store.set('bar', 'fuga') >>> store.dump() 'foo:hoge\nbar:fuga\n' >>> store.get('toto') None >>> store.set(None, 'momo') >>> store.dump() 'foo:hoge\nbar:fuga\n' >>> store.set('foo', 'piyo'); >>> store.dump() 'bar:fuga\nfoo:piyo\n'
まずはテストから書く
get のテスト書く.
assert first
テストを書くときは assert から書く.
import unittest class TestFileStore(unittest.TestCase): def test_get1(self): self.assertEqual(store.get('foo'), 'hoge') if __name__ == '__main__': unittest.main()
こんな感じ.
でもこれを実行するとエラーになっちゃう.
% FileStore.py E ====================================================================== ERROR: test_get1 (__main__.TestFileStore) ---------------------------------------------------------------------- Traceback (most recent call last): File "FileStore.py", line 10, in test_get1 self.assertEqual(store.get('foo'), 'hoge') NameError: global name 'store' is not defined ---------------------------------------------------------------------- Ran 1 test in 0.000s FAILED (errors=1)
とりあえずエラーにならないところまで書く.
それと,そもそも set して get すると値が得られるという仕様のはずなので set も追加する.
#!/usr/bin/env python3 # -*- utf-8 -*- class FileStore(object): def set(self, key, value): pass def get(self, key): pass import unittest class TestFileStore(unittest.TestCase): def test_get1(self): store = FileStore() store.set('foo', 'hoge') self.assertEqual(store.get('foo'), 'hoge') if __name__ == '__main__': unittest.main()
これで Error じゃなくて Fail になったはず.
% FileStore.py F ====================================================================== FAIL: test_get1 (__main__.TestFileStore) ---------------------------------------------------------------------- Traceback (most recent call last): File "FileStore.py", line 21, in test_get1 self.assertEqual(store.get('foo'), 'hoge') AssertionError: None != 'hoge' ---------------------------------------------------------------------- Ran 1 test in 0.000s
よし,おk.
仮実装 (Fake it)
Fake itはテストのテスト
TDD Boot Camp 名古屋に登壇させていただきました - t-wadaの日記
ということでまずgetを仮実装する.
class FileStore(object): def set(self, key, value): pass def get(self, key): return 'hoge'
そしてテスト.
% FileStore.py . ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
やったー.「OK」だってーーー!!!
三角測量 (Triangulation)
さぁ,仮実装をいじめるぞ!!
class TestFileStore(unittest.TestCase): def test_get1(self): store = FileStore() store.set('foo', 'hoge') self.assertEqual(store.get('foo'), 'hoge') def test_get2(self): store = FileStore() store.set('bar', 'fuga') self.assertEqual(store.get('bar'), 'fuga')
ぎゃ!仮実装じゃこんなテストを追加されたらひとたまりもない.
ということでset, getを実装します.
#!/usr/bin/env python3 # -*- utf-8 -*- from collections import OrderedDict class FileStore(object): def __init__(self): self._store = OrderedDict() def set(self, key, value): self._store[key] = value def get(self, key): return self._store[key] import unittest class TestFileStore(unittest.TestCase): def test_get1(self): store = FileStore() store.set('foo', 'hoge') self.assertEqual(store.get('foo'), 'hoge') def test_get2(self): store = FileStore() store.set('bar', 'fuga') self.assertEqual(store.get('bar'), 'fuga') if __name__ == '__main__': unittest.main()
% FileStore.py .. ---------------------------------------------------------------------- Ran 2 tests in 0.000s OK
(*'ω')b
ここでは,dump で順番の保持が必要そうなので collections.OrderedDict 使ったよ.
テスト->実装->リファクタリング
この調子で dump とか get,set の細かい仕様を実装していく
まずdump
class FileStore(object): ... def dump(self): return '\n'.join( [':'.join([k, v]) for k, v in self._store.items()]) + '\n' import unittest class TestFileStore(unittest.TestCase): def test_get1(self): store = FileStore() store.set('foo', 'hoge') self.assertEqual(store.get('foo'), 'hoge') def test_get2(self): store = FileStore() store.set('bar', 'fuga') self.assertEqual(store.get('bar'), 'fuga') def test_dump1(self): store = FileStore() store.set('foo', 'hoge') self.assertEqual(store.dump(), 'foo:hoge\n') def test_dump2(self): store = FileStore() store.set('bar', 'fuga') self.assertEqual(store.dump(), 'bar:fuga\n') def test_dump3(self): store = FileStore() store.set('foo', 'hoge') store.set('bar', 'fuga') self.assertEqual(store.dump(), 'foo:hoge\nbar:fuga\n')
- テスト test_dump1 を作成
- テスト実行: 失敗
- FileStore.dump を仮実装
- テスト実行: 成功
- テスト test_dump2 を作成
- テスト実行: 失敗
- FileStore.dump を実装
- テスト実行: 成功
- テスト test_dump3 を作成
- テスト実行: 成功
っていう流れ.
テストのリファクタリング
いい加減 store = FileStore()
とか store.set('foo', 'hoge')
って書くのがだるいのでテストをリファクタリングする.ついでに test_get2, test_dump2
ももういらないので削除する.
で,こうなる
class TestFileStore(unittest.TestCase): def setUp(self): self.store = FileStore() self.store.set('foo', 'hoge') def test_get1(self): self.assertEqual(self.store.get('foo'), 'hoge') def test_dump1(self): self.assertEqual(self.store.dump(), 'foo:hoge\n') def test_dump3(self): self.store.set('bar', 'fuga') self.assertEqual(self.store.dump(), 'foo:hoge\nbar:fuga\n')
細かい仕様を実装
とりあえずは最終形.
#!/usr/bin/env python3 # -*- utf-8 -*- from collections import OrderedDict class FileStore(object): def __init__(self): self._store = OrderedDict() def set(self, key, value): if key is None or key is '': return if key in self._store: self._store.pop(key) self._store[key] = value def get(self, key): return self._store.get(key, None) def dump(self): return '\n'.join( [':'.join([k, v]) for k, v in self._store.items()]) + '\n' import unittest class TestFileStore(unittest.TestCase): def setUp(self): self.store = FileStore() self.store.set('foo', 'hoge') def test_get1(self): self.assertEqual(self.store.get('foo'), 'hoge') def test_get_miss_hit(self): self.assertEqual(self.store.get('toto'), None) def test_set_invalid_key1(self): self.store.set('bar', 'fuga') self.store.set(None, 'momo') self.assertEqual(self.store.dump(), 'foo:hoge\nbar:fuga\n') def test_set_invalid_key2(self): self.store.set('bar', 'fuga') self.store.set('', 'momo') self.assertEqual(self.store.dump(), 'foo:hoge\nbar:fuga\n') def test_set_overwrite(self): self.store.set('bar', 'fuga') self.store.set('foo', 'buzz') self.assertEqual(self.store.dump(), 'bar:fuga\nfoo:buzz\n') def test_dump1(self): self.assertEqual(self.store.dump(), 'foo:hoge\n') def test_dump3(self): self.store.set('bar', 'fuga') self.assertEqual(self.store.dump(), 'foo:hoge\nbar:fuga\n') if __name__ == '__main__': unittest.main()
やっぱり,ここでも
- テスト作成
- テスト実行: 失敗
- 実装
- テスト実行: 成功
の流れには逆らわずにひたすらまわす.