C# テスト駆動開発の実践方法:バリューオブジェクトを考える #8

当連載ではテスト駆動開発を行うために必要な基礎知識や実践方法を解説しています。前回からは実践編として、簡単なアプリケーションを開発しながらが、テスト開発の手法を見ていくことにしています。

今回は「お金」の扱い方をテスト駆動開発で考えていきます。お金をテスト駆動開発は「テスト」を行いながら進めていくので、実際にソースコードを下記ながら実装のイメージを膨らませていきましょう。

前回の「C# テスト駆動開発の実践方法:テスト駆動開発の始め方 #7」ではテスト駆動開発を進めるための準備としてアプリケーションの準備等を行いました。まだの人は先にチェックとしておいた方が良いでしょう。

お金を扱う方法

お金を扱うにはどうすれば良いのでしょうか。一般的に銀行口座のようなシステムであれば小数点以下は発生しませんし、桁数も十分に取れるint型でも十分なように思うかもしれません。

しかしながら果たして本当にそれが便利なのでしょうか。例えばクラスのプロパティに以下のように定義されているだけだとしたら、「お金」は重要な概念にも関わらず、どこかのクラスの属性でしかないような感じがしちゃいますよね。

public int Money { get; set; }

そういう場合にはプロパティで考えるのではなく、お金それ自体をオブジェクトにしてしまうのが良いです。int型ですと「お金」という意味を成さない、単なる「お金」と名のついたint型でしかないからです。

そこで「値」を扱うためのオブジェクトを作っておくと、「お金」をint型の何かではなく「お金」というオブジェクト(重要な概念)でとらえるようになります。こういう「値」のためのオブジェクトを「バリューオブジェクト」と呼んだりします。その時に使えるのが「完全コンストラクタパターン」です。

バリューオブジェクトを考える

ではまず、既存のテストクラスにデフォルトで作成されたTestMethod1メソッドを改変して「CreateMoneyTestメソッド」にして、以下のテストを書いてみましょう。

[TestMethod]
public void CreateMoneyTest()
{
    var money = new Money(100);
    Assert.AreEqual(money.Value, 100);
}

バリューオブジェクトは単純に値をラップするクラスであることが殆どです。完全コンストラクタパターンを使って初期値を設定できるようなオブジェクトを考えます。

中身に設定した値をValueプロパティで取り出せるようにしたいと思いますので上記のようなテストを書いてみました。とはいえ、今のところはまだテストができません。Moneyクラスが存在しないのでコンパイルエラーが発生するので解消するためにMoneyクラスを作りましょう。

App03に「ValueObjects」フォルダを作成してMoneyクラスを新規作成します。内容は以下の通りにしてみてください。

namespace App03.ValueObjects
{
    public class Money
    {
        public int Value { get; private set; }

        public Money(int value)
        {
        }
    }
}

これができたらテストクラス側を修正していきます。テストクラスの参照の部分に「using App03.ValueObjects;」を追加してください。するとエラーは消えるはずです。まずはテストを最初に書いて、想定したいパターンを記述してテストを行います。まずはエラーになることを想定しています。

using App03.ValueObjects;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;

namespace BankAccountTest
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void CreateMoneyTest()
        {
            var money = new Money(100);
            Assert.AreEqual(money.Value, 100);
        }
    }
}

テストメソッドは上記の通りです。これでテストを実行すると「レッド」になることが確認できると思います。このレッドの状態をグリーンにすることを考えましょう。この問題を修正するのは非常に簡単です。Moneyクラスのコンストラクタで、オブジェクトの値を意味するプロパティに値を設定していないのが問題でした。Moneyクラスを以下のように書き換えて再度テストを実行してみましょう。

namespace App03.ValueObjects
{
    public class Money
    {
        public int Value { get; private set; }

        public Money(int value)
        {
            this.Value = value;
        }
    }
}

この状態でテストをすると「グリーン」になったかと思います。このようにテスト駆動開発を実践していけばケアレスミス等も防げるようになっていきます。今回はコンストラクタでの処理なので、あえて設定していないという点もありますが、テストコードを書いて実践していくことで、書き忘れやもったいないミスなども減らせるということです。

これでテスト駆動開発の最初のステップが完了しました。この後はリファクタリングなのですが、今回は単純なバリューオブジェクトなのでリファクタリングは今のところ不要そうです。

根底となるオブジェクトを決めよう

さて、今回の最初の実践編としてはこのくらいで留めておこうと思います。まずは「口座」に関連するアプリケーションを作ると考えた時、重要になるオブジェクトをつくるところから始めました。

これは単純なint型の定義で「お金」を表してしまうと、「お金」として扱うべき重要な概念が薄っぺらい意味で語られてしまうのを防ぐためです。単純に実装をするだけならint型のプロパティにしてもいいのですが、オブジェクト指向を用いてより分かりやすいコードを目指すならバリューオブジェクトにした方がよいと思います。

次回はもう少しバリューオブジェクトを深堀りして、値を足したり引いたりすることを考えていきます。もう少しソースコードに変化が現れるようにしていきたいと思います。