pyhaya’s diary

プログラミング、特にPythonについての記事を書きます。Djangoや機械学習などホットな話題をわかりやすく説明していきたいと思います。

「テスト駆動開発」をPythonで書き直してみた 3

名著「テスト駆動開発」をPythonで書き直してみたシリーズの第三弾です。過去の記事はこちらです。

pyhaya.hatenablog.com
pyhaya.hatenablog.com

テストを書きなおす

今までのテストは下みたいな感じでした。

tests/test_money.py

import sys
sys.path.append('../src')

import unittest
from dollar import Dollar

class MoneyTest(unittest.TestCase):
    def testMultiplication(self):
        five = Dollar(5)
        product = five.times(2)
        self.assertEqual(10, product.amount)
        product = five.times(3)
        self.assertEqual(15, product.amount)

    def testEquality(self):
        self.assertTrue(Dollar(5).equals(Dollar(5)))
        self.assertFalse(Dollar(5).equals(Dollar(6)))

if __name__ == '__main__':
    unittest.main()

testMultiplicationを見てみると、five.times(2)Dollarクラスを返すことがわかりにくくなっています。テストコードはソースコードの理解を促進する役割も果たすので、メソッドの返り値が何なのかはパッと見てわかるようにしておきましょう。

tests/test_money.py

import sys
sys.path.append('../src')

import unittest
from dollar import Dollar

class MoneyTest(unittest.TestCase):
    def testMultiplication(self):
        five = Dollar(5)
        product = five.times(2)
        self.assertEqual(Dollar(10), product)
        product = five.times(3)
        self.assertEqual(Dollar(15), product)

    def testEquality(self):
        self.assertTrue(Dollar(5).equals(Dollar(5)))
        self.assertFalse(Dollar(5).equals(Dollar(6)))

if __name__ == '__main__':
    unittest.main()

これを走らせてみると、なんとエラーが出ます。Javaだと出ないのに。。。エラーメッセージを見るとどうやらIDが違うといっている。オブジェクトごとに異なるIDが割り当てられるので、これが等しくないのでAssertionErrorだって言ってるわけです。

仕方ないので特殊メソッドを書き直しましょう。

__eq__を書き直す

src/dollar.py

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

    def __eq__(self, other):    # 等価性の定義
        return self.__dict__ == other.__dict__

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

    def equals(self, money):
        return self.amount == money.amount

__dict__属性が同じなら等しいとしていいでしょうという感じです。

すると、__eq__equalsがかぶっていることがわかります。equalsメソッドはなくていいでしょう。

src/dollar.py

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

    def __eq__(self, other):    # 等価性の定義
        return self.__dict__ == other.__dict__

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

テストコードを再び見直す

テストコードからequalsを使っているところを消します。

再びテストが通るようになったら、テストコードをもう一度見直して、冗長なところがないか、見てみると、いちいちproductを計算するのは冗長であることに気づきます。インライン化しましょう。

結局、コードは下のようになります。

tests/test_money.py

import sys
sys.path.append('../src')

import unittest
from dollar import Dollar

class MoneyTest(unittest.TestCase):
    def testMultiplication(self):
        five = Dollar(5)
        self.assertEqual(Dollar(10), five.times(2))
        self.assertEqual(Dollar(15), five.times(3))

if __name__ == '__main__':
    unittest.main()

まとめ

今回は、一度立ち止まってテストコードの整理を行いました。その結果、書籍のコードとはどんどん離れてしまいましたが、言語が違うので仕方ないでしょう。