「テスト駆動開発」を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()
まとめ
今回は、一度立ち止まってテストコードの整理を行いました。その結果、書籍のコードとはどんどん離れてしまいましたが、言語が違うので仕方ないでしょう。