導入
いよいよ本題です。「登録する(Add)」「全件取る(All)」「IDで探す(FindById)」の3つだけを持つ ITaskRepository を定義し、List<TaskItem> で保持する InMemoryTaskRepository をTDDで実装します。
図解
classDiagram
class ITaskRepository {
<<interface>>
+Add(TaskItem) void
+All() IReadOnlyList~TaskItem~
+FindById(Guid) TaskItem
}
class InMemoryTaskRepository {
-List~TaskItem~ _tasks
}
ITaskRepository <|.. InMemoryTaskRepository
サンプル
このコースを通じて使う契約は次の形に統一します(以降の章もこの3メソッドだけを使います)。
public interface ITaskRepository
{
void Add(TaskItem task);
IReadOnlyList<TaskItem> All();
TaskItem? FindById(Guid id);
}
Addはタスクを1件追加する(戻り値なし)Allは今持っている全タスクを読み取り専用のリストとして返すFindByIdは見つからなければnullを返す(TaskItem?という戻り値がそれを表す)
やってみよう
InMemoryTaskRepository を実装したら、TaskFactory.Createで3件タスクを作ってAddし、All().Countで件数を確認してみましょう。
演習
var repo = new InMemoryTaskRepository();
var t1 = TaskFactory.Create("牛乳を買う");
var t2 = TaskFactory.Create("レポートを書く", Priority.High);
repo.Add(t1);
repo.Add(t2);
Assert(repo.All().Count == 2, "2件登録されている");
Assert(repo.FindById(t1.Id)?.Title.Value == "牛乳を買う", "IDで取得できる");
Assert(repo.FindById(Guid.NewGuid()) == null, "存在しないIDはnull");
Console.WriteLine("全テスト成功");
// TODO: ITaskRepository を実装する InMemoryTaskRepository を完成させてください
// 内部は List<TaskItem> で保持します
___
void Assert(bool c, string n) { if (!c) throw new Exception($"FAIL: {n}"); }
// --- 第2〜3章の型(コピー。シグネチャは変更しない) ---
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 InMemoryTaskRepository : ITaskRepository { private readonly List<TaskItem> _tasks = new(); public void Add(TaskItem task) => _tasks.Add(task); public IReadOnlyList<TaskItem> All() => _tasks; public TaskItem? FindById(Guid id) => _tasks.FirstOrDefault(t => t.Id == id); }
ヒント2を見る
FindByIdはFirstOrDefault(見つからなければnull)でリストの中から探します
まとめ
ITaskRepositoryはAdd/All/FindByIdの3メソッドだけの小さな契約InMemoryTaskRepositoryはList<TaskItem>で保持する素直な実装TaskItemはクラス(参照型)なので、FindByIdで取り出した参照を書き換えると保存済みのデータもそのまま更新される(次章で活用します)
次回: 本物ではなく偽物のFakeTaskRepositoryでテストを書く理由です。