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

テスト成功。

*=演算子を使う

読みやすいかは議論の余地はありますが、Pythonでも使用できるようです。

class Dollar():
    def __init__(self, amount):
        self.amount = amount
        
    def times(self, multipaier):
        self.amount *= multipaier

Revision: 39

テストが通った時点でコミットします。