C# テスト駆動開発の実践方法:サービスクラスの実装をしよう-1- #15

当連載ではテスト駆動開発を実践するために必要な基礎知識や実践方法を解説しています。現在は実践編として簡単なアプリケーションを作成しながらテスト駆動開発のやり方を解説しています。

過去の記事については「C# テスト駆動開発の実践方法」から確認できますので、テスト駆動開発について詳しく知りたい人は参考にしてみてください。前回までに当連載では残高の記録をファイル形式で読み書きする機能まで実装が終わりました。

今回の記事ではアプリケーションの機能を司るサービスクラスを実装していきます。サービスクラスはユースケースに近い形式でアプリケーションの処理を管理してくれるクラスになります。それでは早速始めていきましょう。

ユニットテストを追加する

ではまずはユニットテストのファイルを追加していきます。これまで通りBankAccountTestプロジェクトを右クリックして「追加」から「単体テスト」を選択してください。ファイル名は「BankServiceTest」としたいと思います。

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

namespace BankAccountTest
{
    [TestClass]
    public class BankServiceTest
    {
        [TestMethod]
        public void TestMethod1()
        {
        }
    }
}

追加したファイルが上記のようになっていればOKです。ソリューションエクスプローラーでファイル名を変更したい場合は、変更したいファイルを選択して右クリックから「名前の変更」を選択して任意の名称に変更してください。

サービスクラスを考える

さて、サービスクラスをコーディングしていきますが、まずはサービスクラスがどんな役割を持っているかを考えたいと思います。先ほどサービスクラスはユースケースの実行を管理するクラスだと説明しました。

今回のアプリケーションではどんなユースケースがあるのかを考えたいと思います。まず作成するべきは「残高を確認できること」になりますね。また「口座から金額を引き落とせること」「口座に金額を追加できること」の3つが考えられます。ユースケースとしては上記の3つで十分そうです。

それらを必要に応じて実行してくれるクラスを作っていくイメージです。今回のクラスは、どちらかというと「ドメインサービス」に近いイメージで作っていきます。満たしたい仕様を外部からの変更を切り離した状態で実現できるモジュールのイメージです。

残高照会機能を作成する

サービスクラスについて少しは理解が進んだかと思いますので、実際に実装を始めていきたいと思います。まずは作成したユニットテストに記述していく段階です。まずはテスト駆動開発の実践編ですのでテストコードから書いていきましょう。

デフォルトで作成されたテストメソッドを変更して、以下のような感じにしておきます。まずは参照機能から作っていきたいと考えています。

[TestMethod]
public void CheckOnBalance_OK()
{
    var service = new BankService();
    var balance = service.GetBalance();
}

上記のような感じで記述してみました。BankServiceクラスにGetBalanceというメソッドを追加して、このメソッドがコールされたら口座の残高をMoneyオブジェクトとして返却するイメージです。

とはいえ、口座の残高はどこから取得できるのか?という疑問があります。口座の残高をテキストから読み込むには、前回までに作成してきたBankRepositoryが必要になりますね。この時点でBankServiveはRepositoryに依存したモジュールになりそうです。それを踏まえてテストを修正してみます。

[TestMethod]
public void CheckOnBalance_OK()
{
    string path = @"C:\temp\balance.txt";
    var repository = new BankRepository(path);
    var service = new BankService(repository);

    var balance = service.GetBalance();
}

上記のようにテストが作成できれば十分かと思います。あとはテストを実行するためのスタブといったデータをそろえてあげて、Assertクラスのメソッドで確認できるように変更します。

[TestMethod]
public void CheckOnBalance_OK()
{
    string writePath = @"C:\temp\balance.txt";
    string text = ((int)100).ToString();
    File.WriteAllText(writePath, text);

    string path = @"C:\temp\balance.txt";
    var repository = new BankRepository(path);

    var service = new BankService(repository);
    var balance = service.GetBalance();

    Assert.IsTrue(balance.Equals(new Money(100)));
}

いったんこれで実行したいイメージまで持っていくことができました。事前に10000をファイルに書き込んでおき、BankSeriveのGetBalanceメソッドでそのデータを取得して書き込んだ値と正しくなるかを確認するテストになります。

実装をしてレッドを目指す

テストケースがコーディングできたら、これらをレッドにするように実装を加えていきます。現時点ではBankServiceクラスが存在しないためコンパイルエラーになっているので、まずはコンパイルエラーとならないように修正するのが先決になります。

サービスフォルダを作成する

テストメソッドで記述した「BankSericeクラス」の置き場を用意するために、App03の下に「Servicesフォルダ」を作成し、その中に「BankSerice.cs」を作成したいと思います。

App03プロジェクトを右クリックして「追加」から「新しいフォルダー」を選択し、フォルダ名を「Services」にしましょう。フォルダの作成ができたらフォルダを右クリックして「追加」から「新しい項目」を選択し、「クラス」を選択した状態でファイル名を「BankService」として「追加」を押下します。以下のようなデフォルトクラスが作成されるかと思います。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace App03.Services
{
    internal class BankService
    {
    }
}

ここまで出来たらいったんはOKです。それではコンパイルエラーを直すための下準備は整いました。

コンストラクタを追加する

デフォルトクラスは作成されていますが、クラスとしての記述は殆どありませんよね。ですのでコンパイルエラーの原因ともなっているコンストラクタ部分を修正していきます。以下のように変更を加えましょう。

using App03.Repository;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace App03.Services
{
    public class BankService
    {
        public BankService(IRepository repos)
        {
        }
    }
}

もともとBankRepositoryはIRepositoryのインターフェースを実装していたことを思い出してください。こういう引数などは、抽象化されていれば機能を表すインターフェースにしておくほうが無難です。上記の実装では内部でレポジトリを保持できていないので、内部変数としてレポジトリを保有できるように変更を加えます。

using App03.Repository;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace App03.Services
{
    public class BankService
    {
        readonly IRepository _repos;
        public BankService(IRepository repos)
        {
            _repos = repos;
        }
    }
}

上記の変更を加えたら、テストクラスに戻ってもてください。まだコンパイルエラーになっていると思います。現在の状況は作成したBankServiceが参照できていない状態です。「using App03.Services;」を加えたらコンパイルエラーは下の行であるGetBalanceメソッドに移動すると思います。

using App03.Repository;
using App03.Services;
using App03.ValueObjects;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.IO;

namespace BankAccountTest
{
    [TestClass]
    public class BankServiceTest
    {
        [TestMethod]
        public void CheckOnBalance_OK()
        {
            string writePath = @"C:\temp\balance.txt";
            string text = ((int)100).ToString();
            File.WriteAllText(writePath, text);

            string path = @"C:\temp\balance.txt";
            var repository = new BankRepository(path);

            var service = new BankService(repository);
            var balance = service.GetBalance();

            Assert.IsTrue(balance.Equals(new Money(100)));
        }
    }
}

GetBalanceメソッドを追加する

GetBalanceメソッドの箇所で赤波線が引かれているのを確認したら、次はこのエラーを解消していきます。直接的な原因はBankServiceクラスにGetBalanceメソッドが存在していないことになります。素直にGetBalanceメソッドを追加しましょう。この時の戻り値は後のAssertの内容を考えてMoneyクラスになります。

using App03.Repository;
using App03.ValueObjects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace App03.Services
{
    public class BankService
    {
        readonly IRepository _repos;
        public BankService(IRepository repos)
        {
            _repos = repos;
        }

        public Money GetBalance()
        {
            throw new NotImplementedException();
        }
    }
}

上記のように修正を加えたらテストメソッドのコンパイルエラーは解消されているはずです。これでレッドの状態になるかを確認してみましょう。「レッド」になっていれば順調です。CheckOnBalance_OKテストの「グリーン」化は次で行います。

「レッド」から「グリーン」にする

では「レッド」の状態から「グリーン」にする修正を加えていきましょう。とはいえ、BankRepositoryで読込の処理は作成済みですので簡単な修正で終わってしまいます。BankServiceクラスのGetBalanceメソッドを以下のようにしてください。

public Money GetBalance()
{
    return _repos.Read();
}

BankRepository、ないしはIRepositoryクラスのReadメソッドはテキストファイルを読み込んでMoneyオブジェクトで返却してくれるメソッドでしたね。これは過去の「C# テスト駆動開発の実践方法:データの読込機能を実装する #12」の記事で記述した機能になります。

この状態でテストを実行すれば「レッド」であったテストエクスプローラーも、すべての項目が「グリーン」になるかと思います。これで参照機能の実装は終了となります。次回は指定金額を引き出す処理をコーディングしていきます。