mizzsugar’s blog

Pythonで学んだことや読書録を書きます。

20181103 ミニTDDBC振り返りその3

だいぶ放置してしまいましたが・・・(^^;

mizzsugar.hatenablog.com

の続きです。

※今回、DDDの「Value Object」が出てきますが、

DDD学び始めたばかりで理解が曖昧なので

違ったらご指摘いただけると幸いです(>_<)


今回学んだことは

1. きれいなコミットメッセージの書き方

2. 学習テスト

3. DDDなリファクタリング


この記事では、「3. DDDなリファクタリング」について書きます。

今回の題材はこちら

お題: セマンティック・バージョニング · GitHub


我々のGitHub

github.com


3. DDDなリファクタリング

問3が終わったところで、

メジャーバージョン・マイナーバージョン・パッチバージョンには

「0以上の整数である」

という共通した特徴があるという話になりました。


この時点のプロダクトコード↓

class SemVer:
    def __init__(self, major: int, minor: int, patch: int) -> None:
        if major < 0:
            raise ValueError("メジャーバージョンは0以上")
        if minor < 0:
            raise ValueError("マイナーバージョンは0以上")
        if patch < 0:
            raise ValueError("パッチバージョンは0以上")
        
        self.major = major
        self.minor = minor
        self.patch = patch

    def get_notation(self) -> str:
        return str(self.major) + "." + str(self.minor) + "." + str(self.patch)

    def __eq__(self, other) -> bool:
        return self.get_notation() == other.get_notation() \
               and isinstance(other, SemVer)


テストコード↓

class TestSemVer(unittest.TestCase):
    def test_major_minor_patchにそれぞれ142を与えてバージョンオブジェクトを作成(self):
        self.assertEqual("1.4.2", SemVer.generate(1, 4, 2).get_notation())

    def test_2つのSemVerオブジェクトの等価性を比較できる(self):
        with self.subTest("等しい場合"):
            self.assertTrue(SemVer.generate(1, 4, 2) == SemVer.generate(1, 4, 2))

        with self.subTest("異なる場合"):


そこで、int型ではなく、

「0以上の整数である」という特徴を持つ「VersionNumer」クラスを作成し

それぞれをVersionNumber型にしようということになりました。

major, minor, patchがVersionNumberクラスで管理されるような特徴をもつように、

同じ属性を保持し、生成されてから変化しないオブジェクトをValue Objectというそうです。

little-hands.hatenablog.com


追加したテストコード↓

    def test_バージョン番号は0以上の整数であること(self):
        self.assertRaises(ValueError, lambda: VersionNumber(-1))


プロダクトコードにVersionNumberクラスを追加します。

class VersionNumber:
    def __init__(self, value: int):
        if value < 0:
            raise ValueError("バージョン番号は0以上の整数でなければなりません")
        self.value = value


ただ、この時点では、他のコードはそれぞれのナンバーをint型として扱っているので他のテストが落ちます。

他のテストを通すために、一度

VersionNumberでSemverを生成するためのテストを追加します。

def test_オブジェクトを生成しやすくする(self):
        self.assertEqual("1.4.2", SemVer.generate(1, 4, 2).get_notation())


直したプロダクトコード↓

class SemVer:
    def __init__(self, major: VersionNumber, minor: VersionNumber, patch: VersionNumber):
        self.major = major
        self.minor = minor
        self.patch = patch

    def get_notation(self) -> str:
        return str(self.major.value) + "." + str(self.minor.value) + "." + str(self.patch.value)

    def __eq__(self, other) -> bool:
        return self.get_notation() == other.get_notation() \
               and isinstance(other, SemVer)

    @classmethod
    def generate(cls, major: int, minor: int, patch: int):
        return SemVer(VersionNumber(major), VersionNumber(minor), VersionNumber(patch))


まず、generateメソッドを作り、その引数にintを渡します。

intをもとに、VersionNumberを要素にしたSemverオブジェクトを生成します。

generateメソッドが完成した後に、

他のメソッドを書き直しました。


Value Objectとして管理し、特徴をまとめたクラスを作り、そのクラスのオブジェクトとすることで、

3つ別々にテストせず

特徴をまとめたクラスのみテストすれば良くなる利点があります。

後々に修正する際に楽ですね!

また、あとでコードを見返す時に

3つのオブジェクトが共通した特徴をもつことがひと目で分かることも良いです。


この学びは、DDDを学んでいらっしゃる方とのモブプロをしないとありませんでした!

ペアプロ・モブプロは最高ですね(^0^)/