本文へスキップ
BecomeCoder

C# MVVMコース · 第4章 Repository ― データアクセスの抽象化 · レッスン14

Fake ― FakeTaskRepositoryでテストを書く

ブラウザで完結

導入

アプリが育つと、DB(または本物のファイル保存)を使うInMemoryTaskRepositoryだけではテストがつらくなります。DBの起動待ち・後片付け・実行順で結果が変わる不安定さ……。そこでテスト専用の偽物(Fake)を用意します。

図解

flowchart LR
    T["TaskService(テスト対象)"] --> I["ITaskRepository(抽象)"]
    I -->|本番| R["InMemoryTaskRepository<br/>(実際に保持)"]
    I -->|テスト| F["FakeTaskRepository<br/>(挙動を固定・呼び出しを記録)"]
    style F fill:#e8f5e9

サンプル

考え方はC#デザインパターンコースのMoq(手動フェイク)と同じです。Moqがやっていることを、このITaskRepositoryに対して手で書きます。

// 手動フェイク: 本物のように振る舞うが、中身は単純なListだけ
public class FakeTaskRepository : ITaskRepository
{
    private readonly List<TaskItem> _tasks = new();
    public int AddCallCount { get; private set; }   // 「何回呼ばれたか」を記録できるのがFakeの強み

    public void Add(TaskItem task) { _tasks.Add(task); AddCallCount++; }
    public IReadOnlyList<TaskItem> All() => _tasks;
    public TaskItem? FindById(Guid id) => _tasks.FirstOrDefault(t => t.Id == id);
}
  • 速い: メモリ上のListだけなので、DB接続やファイルI/Oを待たない
  • 決定的: 毎回同じ結果になる(DBの残留データに左右されない)
  • DBが無くても書ける: サーバー担当が実装を書く前でも、Fakeさえあればテストを書き始められる
  • おまけにAddCallCountのように「呼ばれた回数」まで記録できる(MoqのVerifyに相当)

やってみよう

FakeTaskRepositoryFindByIdが呼ばれた回数を数えるFindByIdCallCountも足して、何回検索されたか確認してみましょう。

演習

var fake = new FakeTaskRepository();
var t1 = TaskFactory.Create("牛乳を買う");
fake.Add(t1);
fake.Add(TaskFactory.Create("卵を買う"));

Assert(fake.AddCallCount == 2, "Addが2回呼ばれた");
Assert(fake.All().Count == 2, "2件保持している");
Assert(fake.FindById(t1.Id)?.Title.Value == "牛乳を買う", "IDで取得できる");
Console.WriteLine("全テスト成功");

// TODO: ITaskRepository を実装し、Add が呼ばれた回数を AddCallCount で数える
//       FakeTaskRepository を実装してください
___

void Assert(bool c, string n) { if (!c) throw new Exception($"FAIL: {n}"); }

// --- 型のコピー ---
public readonly struct TaskTitle : IEquatable<TaskTitle>
{
    public string Value { get; }
    public TaskTitle(string value)
    {
        if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("タイトルは空にできません");
        if (value.Length > 50) throw new ArgumentException("タイトルは50文字以内です");
        Value = value.Trim();
    }
    public bool Equals(TaskTitle other) => Value == other.Value;
    public override string ToString() => Value;
}
public enum Priority { Low, Medium, High }
public class TaskItem
{
    public Guid Id { get; }
    public TaskTitle Title { get; private set; }
    public Priority Priority { get; private set; }
    public bool IsCompleted { get; private set; }
    public TaskItem(Guid id, TaskTitle title, Priority priority)
    { Id = id; Title = title; Priority = priority; }
    public void Complete() => IsCompleted = true;
    public void Rename(TaskTitle newTitle) => Title = newTitle;
}
public static class TaskFactory
{
    public static TaskItem Create(string title, Priority priority = Priority.Medium)
        => new TaskItem(Guid.NewGuid(), new TaskTitle(title), priority);
}
public interface ITaskRepository
{
    void Add(TaskItem task);
    IReadOnlyList<TaskItem> All();
    TaskItem? FindById(Guid id);
}
  • 期待される出力: 全テスト成功
ヒント1を見る

public class FakeTaskRepository : ITaskRepository { private readonly List<TaskItem> _tasks = new(); public int AddCallCount { get; private set; } public void Add(TaskItem task) { _tasks.Add(task); AddCallCount++; } public IReadOnlyList<TaskItem> All() => _tasks; public TaskItem? FindById(Guid id) => _tasks.FirstOrDefault(t => t.Id == id); }

ヒント2を見る

Addの中で_tasks.Add(task)のあとにAddCallCount++を忘れずに

まとめ

  • Fakeは「速い・決定的・DB不要」でテストを書けるようにする手動の偽物
  • ITaskRepositoryを実装する点は本物と同じなので、テスト対象からは区別がつかない
  • 呼び出し回数のような「検証」用の情報も自由に足せる

次回: TaskServiceにこのITaskRepositoryをコンストラクタで注入します。

実際に動かしてみよう

本文のサンプルや演習のコードは、コードブロック右上の「コピー」ボタンでコピーして、下のエディタに貼り付ければそのまま実行できます。

C# — ブラウザ内で実行

ブラウザ内でC#を動かす環境を読み込みます(初回のみ数秒)。
スクロールして表示された時点でも自動で読み込まれます。