C# テスト駆動開発の実践方法:データの読込機能を実装する #12

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

過去の記事については「C# テスト駆動開発の実践方法」から確認できますので、テスト駆動開発について詳しく知りたい人は参考にしてみてください。さて、今回はよりアプリケーションに近づくためにデータの読込機能を実装していきます。

テストコードを記述する

早速ですがデータの読込機能を実装していきます。今回はデータをテキストファイルとして出力し、テキストファイルを読込で最新の口座残高を記録するという簡単な手法を取り入れます。

まずはデータの読込部分を実装していきますが、「レポジトリ」という名称にして作成したいと思います。レポジトリとは「英語で「貯蔵庫」「収納庫」の意味」ですが、ここでは単にデータにアクセスして取得する・出力する機能の総称とでも理解しておいてください。

とはいえ、ここではテスト駆動開発を行いますのでテストコードから書いていくことにしましょう。BankAccoutTestのプロジェクトに新規の単体テスト用ファイルを追加して、RepositoryTest.csを作成しておきます。作成したRepositoryTestに対して以下のテストメソッドを追加してみましょう。

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

    var repos = new BankRepository(path);
    var money = repos.Read();
    Assert.AreEqual(money.Value, 10000);
}

上の3行はテストデータを作成するテストコードで、下3行が機能のためのテストコードになります。今回はBankRepositoryを使用して値の読込・書込みを行って行きます。

とはいえ、現在はまだコンパイルエラーになるので、コンパイルを通るように簡単に実装を行っていきます。まずはBankRepositoryクラスを作成しますが、そのためにディレクトリ等を追加していきます。

App03のプロジェクト直下に「Repository」フォルダを作成し、その中に「BankRepository.cs」を作成しましょう。作成できたらテストコードのコンパイルエラーを取り除くための作業をしていきます。

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

まずは作成したBankRepository.csにコンストラクタを追加します。今回は暫定的にパスの文字列をコンストラクタに引き渡すようにしたいと思います。いったん、今回は文字列のパスに依存したモジュールとして作成します。

namespace App03.Repository
{
    public class BankRepository
    {
        public BankRepository(string path)
        {
        }
    }
}

BankRepositoryクラスにコンストラクタを追加しました。テストクラス側では、まだコンストラクタを認識できていないので、usingの設定を行います。「using App03.Repository;」を先頭に記述することで、コンストラクタの認識ができました。

Readメソッドを追加する

コンストラクタを追加しましたが、まだReadメソッドの部分でコンパイルエラーが発生しているのでこれを取り除きます。このReadメソッドはMoneyオブジェクトを返却するようなメソッドを想定しています。以下のようなメソッドをBankRepositoryクラスに追加します。

public Money Read()
{
    return new Money(0);
}

これを記述することでBankRepository.csの全文は以下のようになりました。

using App03.ValueObjects;

namespace App03.Repository
{
    public class BankRepository
    {
        public BankRepository(string path)
        {
        }

        public Money Read()
        {
            return new Money(0);
        }
    }
}

まだ難しいことは考えずに値を0として持つMoneyクラスのオブジェクトを作成して返却するようにしています。本来ならテキストファイルを読み込んで、Moneyオブジェクトを生成して返却するのが理想ですが、今はレッドの状態にするための手順ですのでこれでOKになります。完成したらテストを実行して状態が「レッド」であることを確認しましょう。

Readメソッドを実装する

前項まででReadTestのステータスが「レッド」であることを確認したら、次は正しく実装されるように修正を加えていきます。ここで行いたかったのはテキストファイルから読み込んで、それをMoneyオブジェクトに変換して返却することでした。

BankRepositoryクラスのコンストラクタではパスを引数で取得しているため、いったんはこのパスを使うことができそうです。まずはコンストラクタを修正してパスを保持できるように変更します。

private readonly string _path;

public BankRepository(string path)
{
    this._path = path;
}

これでパスをクラス内で使用できるようになりました。この_path変数を使用してReadメソッドを仕上げていくことにしましょう。privateメソッドなどを追加して、以下のようにReadメソッドの周辺を実装をしていきます。

public Money Read()
{
    var balance = ReadFile();
    return new Money(balance);
}

private int ReadFile()
{
    if (!CanReadFile()) { return 0; }

    var txt = File.ReadAllText(_path);
    if (int.TryParse(txt, out var converted))
    {
        return converted;
    }
    return 0;
}

private bool CanReadFile()
{
    return File.Exists(_path);
}

ここまで出来たら、いったんテストを実行してみます。「レッド」の状態から「グリーン」になったでしょうか?また読み込めなかった場合に0になるテストも作成してみます。以下のようなテストメソッドを作成して実行してみましょう。

[TestMethod]
public void ReadTest_NG()
{
    string path = @"C:\temp\balance.txt";
    string text = "テスト";
    File.WriteAllText(path, text);

    var repos = new BankRepository(path);
    var money = repos.Read();
    Assert.AreEqual(money.Value, 0);
}

テキストファイルに文字列を入力した場合は正しく読み取れなかったとして、値が0になるパターンのテストになります。メソッド名の最後を「_NG」としたので、最初のテストのメソッド名も語尾に「_OK」を付けておきましょう。最後にメソッド名を変更したので、もう一度テストを実行してすべてのテストを「グリーン」にしておきます。

読込機能の完成と次回

以上でテキストファイルに記録した値を読み取って、Moneyオブジェクトで返却する機能の実装が完了しました。簡単な機能ではありますが、これでも十分に機能するはずです。

最初に書いたテストメソッドが難しく感じたかもしれませんが、ファイル形式の場合はダミーとして、いったんファイルを吐いてしまうようにするのも良い手かと思います。ダミーの文字列を内部で持つスタブ形式にするのもいいですが、そういうのはDBを使った場合にしておくほうがよいでしょう。

以上で、読込機能は完了しましたので今回は終了です。次回は書込み機能を実装していきたいと思います。ここまででおおよそ半分くらいは実装が完了したイメージになります。少しずつテスト駆動開発にも慣れてきたのではないでしょうか。