20181103 ミニTDDBC振り返りその3
だいぶ放置してしまいましたが・・・(^^;
の続きです。
※今回、DDDの「Value Object」が出てきますが、
DDD学び始めたばかりで理解が曖昧なので
違ったらご指摘いただけると幸いです(>_<)
今回学んだことは
1. きれいなコミットメッセージの書き方
2. 学習テスト
3. DDDなリファクタリング
この記事では、「3. DDDなリファクタリング」について書きます。
今回の題材はこちら
我々のGitHub
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にそれぞれ1と4と2を与えてバージョンオブジェクトを作成(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というそうです。
追加したテストコード↓
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^)/