PythonでTDD入門(3) - バリューオブジェクトと等価性

前回はオブジェクトに副作用があるという設計上の問題を解決する為に、timesメソッドの戻り値をDollarに修正しました。この変更はバリューオブジェクトパターンを適用したと考える事ができます。

バリューオブジェクトとはその名前が表すようにオブジェクトが値そのものを表現するパターンです。つまり、Dollar(10)とDollar(10)は等価であり、Dollar(10)とDollar(15)は等価でない事が必要になります。まずはこの事を確認する為のテストを書きましょう。

    def test_eq(self):
        """
        等価性に関するテスト
        """
        self.assertEqual(Dollar(5), Dollar(5))
        self.assertEqual(Dollar(6), Dollar(6))

当たり前のよう成功すると思えますが、テストは失敗します。

1) Failure:
test_eq(tdd.tests.money_test.Money_Test):
!=

assertEqualの中でオブジェクトを比較しているわけですが、どうしてDollar(5) と Dollar(5) が等しくないのでしょうか?

オブジェクトの比較

Pythonではオブジェクトのインスタンスが等しいかどうかの比較にはis演算子を用い、等価性は==を用います。また、==を用いて比較する場合は、オブジェクトに定義された__eq__メソッドが用いられます。
確認します。

class Foo():
    pass

f1 = Foo()
f2 = Foo()
print f1 == f2
print f1 is f2

class Bar():
    def __eq__(self, other):
        return True

b1 = Bar()
b2 = Bar()
print b1 == b2
print b1 is b2

Javaと比較してみました。Javaと同様にデフォルトの__eq__の振る舞いはインスタンスの比較(すなわちisと同じ)となります。

_ Java Python
オブジェクトの同値比較 equals ==(__eq__)
インスタンスの比較 == is

Dollarに__eq__を実装する

assertEqualでは==によるオブジェクトの等価性をチェックしている為、__eq__をオーバライドして値を比較するように実装します。

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

    def times(self, multipaier):
        return Dollar(self.amount * multipaier)

    def __eq__(self, other):
        return self.amount == other.amount

これでテストも成功しましたので、コミットします。
リポジトリ: http://www.deathmarch.jp/svn/sjc/python_tdd/