FactoryMethodを用いて、本番用と試験用のデータの使い分けを行う手法について

初投稿です。
試験時にテストケースをいちいちデータベースに値を入れたり、通信を投げたりするのは非常に面倒です。今回はFactoryMethodを用いて本番用データとテスト用データを使い分ける方法を記します。サンプルコードはC#です。

# FactoryMethodとは
- クラスのインスタンスの生成の責務を「〇〇Factory」に負わせる手法です。
- インスタンスを使う側のクラスは、コンストラクタを直接呼び出すのではなく、Factoryクラスのメソッドを呼び出し、その戻り値を利用します。

これにより、インスタンスを生成するときに行う処理をFactoryクラスの中で一元管理することができ、保守性が向上します。
ここでは、Factoryクラスでインスタンスを生成するときに、本番用とテスト用で扱うデータ様式を分岐する方法を紹介します。

# サンプルの作成
## ToDo(やりたいこと)
- 例として、本番用のデータをSQLite、テスト用のデータをCSVで管理する
- 素材の在庫状況を扱う「Materials」というデータ単位を対象とする
- データクラスとして、MaterialEntityを作成する。
- IMaterialRepositoryというインターフェースを作成し、Get()とSet()を定義する
- インターフェースの実装クラスとして、MaterialSQLとMaterialFakeを作成する
- 上記二つのクラスのインスタンスを生成する静的クラスとしてMaterialFactoryを作成し、CreateMaterial()を定義する
- CreateMaterial()では、本番用かテスト用かによって、作成するインスタンスを分岐させるようにする

## データの作成
カラムを左から、素材ID、素材名、在庫数とします。

本番用はSQLiteでMaterialsテーブルを作成します。
![本番用テーブル作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3852955/8c5008f7-bb24-561f-6af0-67e28e4cb941.png "本番用テーブル作成")
![本番用テーブル.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3852955/f34c903d-bad6-65f7-08a7-3a89526d5484.png "本番用テーブル")

テスト用はMaterialFake.csvを作成します。
![テスト用.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3852955/0be58303-cfac-784c-3e8d-98377d9db7ce.png "テスト用CSV")　



## MaterialEntityの作成
データクラスのMaterialEntityを作成します。特に理由がない限りは完全コンストラクタ型にしておきます。
```C#:MaterialEntity.cs
namespace FactoryMethod
{
    // 素材のデータクラス
    public sealed class MaterialEntity
    {
        // コンストラクタ
        public MaterialEntity(int id, string name, int amount) 
        {
            Id = id;
            Name = name;
            Amount = amount;
        }

        // 素材ID
        public int Id { get; }

        // 素材名
        public string Name { get;}

        // 素材量
        public int Amount { get; }
    }
}
```

## リポジトリのインターフェースを作成
IMaterialRepositoryを作成します。リポジトリは、「あるデータのやりとりにおける単位」という認識でOKです。例えるなら「入荷や配達を行う宅配便の配達員さん」みたいなもんです。
今回はこのインターフェースにSet()とGet()を定義します。適宜Update()やDelete()などを定義してもいいでしょう。
``` C#:IMaterialRepository.cs
namespace FactoryMethod
{
    // 素材のリポジトリ
    public interface IMaterialRepository
    {
        // 素材の取得
        public MaterialEntity Get();

        // 素材の設定
        public void Set(MaterialEntity entity);
    }
}

```
## 実装クラスの作成
データを扱うクラスとして、本番用はMaterialSQLを、テスト用はMaterialFakeを作成します。それぞれインタフェースを実装させます。本番用はSQLを、テスト用はSystem.IOのファイル処理を用います。
```C#:MaterialSQL.cs（本番用）
namespace FactoryMethod
{
    internal class MaterialSQL : IMaterialRepository
    {
        public MaterialEntity Get()
        {
            //・・・（SQLの処理）
        }

        public void Set(MaterialEntity entity)
        {
            //・・・（SQLの処理）
        }
    }
}
```
```C#:MaterialFake.cs（テスト用）
namespace FactoryMethod
{
    internal class MaterialFake : IMaterialRepository
    {
        public MaterialEntity Get()
        {
            //・・・（CSVファイルの処理）
        }

        public void Set(MaterialEntity entity)
        {
            //・・・（CSVファイルの処理）
        }
    }
}
```

## Factoryクラスの作成と生成するインスタンスの分岐
上記2つのクラスのインスタンスを生成する責務を担うMaterialFactoryを作成します。本番用の場合はMaterialSQLの、テスト用の場合はMaterialFakeのインスタンスを生成することとします。configファイルに設定値を待っても良いですし、DebugとReleaseの場合で分けても良いでしょう（今回は後者です）。
``` C#:MaterialFactory.cs
namespace FactoryMethod
{
    public static class MaterialFactory
    {
        public static IMaterialRepository CreateMaterial()
        {
# if DEBUG
            return new MaterialFake();
#else
            return new MaterialSQL();
#endif
        }
    }
}
```

## 呼び出し
呼び出す側は、実装クラスを直接呼び出すのではなく、MaterialFactory.CreateMaterial()を呼び出し、IMaterialRepository型の変数に代入します。その変数からメソッドを呼び出して利用します。
```C#:MainClass.cs
namespace FactoryMethod
{
    public class MainClass
    {
        static void Main()
        {
            //本番時はMaterialSQLが、テスト時はMaterialFakeのインスタンスが生成される
            IMaterialRepository material = MaterialFactory.CreateMaterial();

            //Setメソッド
            material.Set(new MaterialEntity(4, "鉛", 500));

            //Getメソッド
            var entity = material.Get();
        }
    }
}
```

サンプルプログラムの作成は以上です。

# メリット

- 単体テストなどの机上デバッグを行う際に本番用のデータを扱う必要がないため、テストが効率化される。
- データの様式が変わっても(データベースからjsonとか)、Repositoryインターフェースを実装させているデータのやり取りを行う箇所のみ変更すれば良いため、変更容易性が高い。

以上です。