1.1 複数通貨のMoneyオブジェクト
翻訳がアレと評価されているテスト駆動開発入門をPythonで写経してみようと思います。あわせてPythonを学習していきましょう。
まずは最初の目標である「$5 * 2 = 10」となるテストケースを作成します。最初にテストケースを作成しますが、パッケージは「tdd」としました。さらにテスト用の「tdd.tests」パッケージを作成し、「money_test」モジュールを作成します。
リポジトリはhttp://www.deathmarch.jp/svn/sjc/python_tdd/trunk/、NetBeans6.7のPythonプラグインの試用をかねています。
パッケージ
ここで少しはまったのですが、PythonではJavaのクラスローダの挙動を予想してパッケージを作ると痛い目にあうようです。Javaですと通常はテストコードとメインのコードは別のパス(ディレクトリ)に分けた上で、同じパッケージ内にテストケースを作成します。しかし、Pythonではパスの通った中で最初に見つかったパッケージを認識する為、同じパッケージを作成してしまうと思わぬ副作用に見舞われてしまいます。なので、tdd.moneyパッケージを今回のアプリケーションで使うパッケージとし、その下にテスト用サブパッケージを配置しました。尚、Pythonではパッケージとして認識させるために__init__.pyが必要です。
テストコード
# -*- coding: utf-8 -*- import unittest # Money test # # TODO レートが2:1 の場合、$5 + 10 CHF = $10 # TODO $5 * 2 = 10 # TODO amount のプライベート化 # TODO Dollarの副作用 class Money_Test(unittest.TestCase): def test_multiplication(self): """ 乗法に関するテスト $5 * 2 = 10 """ five = Dollar(5) five.times(2) self.assertEqual(10, five.amount) if __name__ == '__main__': unittest.main()
JUnitに慣れていれば難しい所はないでしょう。Dollarクラスのインスタンス($5)を作成し、2倍しています。結果として、$10になることを確認するだけの簡単なコードです。
実行するとそんなクラスはない「global name 'Dollar' is not defined」となりました。
クラスの定義
tdd.moneyにdollarモジュールを作成し、Dollarクラスを定義します。
# -*- coding: utf-8 -*- class Dollar(): pass
また、パッケージが異なるのでテストクラスにimport文を追加します。
from tdd.money import Dollar
テスト実行
this constructor takes no arguments
コンストラクタの定義
class Dollar(): def __init__(self, amount): pass
テスト実行。
Dollar instance has no attribute 'times'
timesメソッドの定義
def times(self, multipaier): pass
テスト実行。
Dollar instance has no attribute 'amount'
amountの定義
class Dollar(): def __init__(self, amount): self.amount = 0
テスト実行。
1) Failure:
test_multiplication(tdd.tests.money_test.Money_Test):
10 != 0
とりあえずはテスト失敗までこぎ着けました。
テストを通す
テストコードを書き、テストが動くようになったので最小限の実装でテストを通るようにします。
class Dollar(): def __init__(self, amount): self.amount = 10
テスト実行。
Finished in 0.0 seconds.
1 tests, 0 failures, 1 errors
固定値を入れることは正しくないことは自明かも知れませんが、最初は仮実装を行いテストを通すことが重要です。テストが通ったならばリファクタリングを行い、コードを綺麗にします。
Revision: 38
テストが通った時点でコミットします。
リファクタリング
重複を取り除く(1)
テストコードと実装コードを眺めると10という定数が重複しているのでこれを取り除きます。5と2を使う事ができるので次のようにしましょう。
class Dollar(): def __init__(self, amount): pass def times(self, multipaier): self.amount = 5 * 2
テスト成功。
メソッドの引数で2を置き換える
class Dollar(): def __init__(self, amount): pass def times(self, multipaier): self.amount = 5 * multipaier
テスト成功。
コンストラクタで2を保持する
class Dollar(): def __init__(self, amount): self.amount = amount def times(self, multipaier): self.amount = self.amount * multipaier
テスト成功。
Revision: 39
テストが通った時点でコミットします。