チーム開発で学ぶ!WinForm ハイローゲーム構築【Test編】TestDeck を用いた依存性注入の具体例

このサンプルは、次の制作編を完了した時点から始めます


1. はじめに

本資料では、WinForm を用いたハイローゲーム構築のサンプルコードにおいて、インターフェースと依存性注入の具体例として、テスト用デッキ(TestDeck)の実装とその利用方法を説明します。
これにより、実際の運用時にはランダムな動作を行う RealDeck を、テスト環境では固定のカード順序を返す TestDeck を簡単に切り替えられる仕組みを実現できます。


2. インターフェースと依存性注入の概要

2.1 IDeck インターフェース

IDeck インターフェースは、以下の機能を定義しています。

  • ShuffleAsync(): 非同期でカードをシャッフルする。
  • DrawCard(): デッキから1枚カードを引いて返す。
  • CardsRemaining: 残りカードの枚数を取得する。

2.2 依存性注入のメリット

HiLowGame クラスは、コンストラクタで IDeck 型のオブジェクトを受け取ることで、どのデッキ実装(RealDeck や TestDeck)を利用するかを外部から決定できます。
これにより、以下が可能になります。

  • 本番環境:ランダムな動作を行う RealDeck を利用して実際のゲームを実現。
  • テスト環境:固定のカード順序を返す TestDeck を利用することで、予測可能な動作と再現性の高いテストが実施可能。

3. TestDeck の実装

以下のコードは、既存の Deck クラスをベースに、固定順序でカードを返す TestDeck の実装例です。

/// <summary>
/// テスト用の Deck 実装です。
/// 生成時に固定のカード順序を設定し、シャッフルは行いません。
/// </summary>
public class TestDeck : IDeck
{
    private List<Card> cards;

    /// <summary>
    /// TestDeck を初期化し、固定順序のカードを生成します。
    /// </summary>
    public TestDeck()
    {
        cards = new List<Card>();

        // 例:すべてのスートに対して、1~3 のカードを順番に追加
        foreach (Suit suit in Enum.GetValues(typeof(Suit)))
        {
            for (int i = 1; i <= 3; i++)
            {
                // 画像ファイルは「images」フォルダ内の例: "hearts_1.png" などを利用
                string imagePath = $"images/{suit.ToString().ToLower()}_{i}.png";
                Image cardImage = Image.FromFile(imagePath);
                cards.Add(new Card(suit, i, cardImage));
            }
        }
        // ※ 必要に応じてカード順序を手動で変更可能です。
    }

    /// <summary>
    /// 非同期にシャッフル処理を行いますが、TestDeck では固定順序を維持するため何もしません。
    /// </summary>
    public async Task ShuffleAsync()
    {
        // テストではシャッフルしない(形式上の実装)
        await Task.CompletedTask;
    }

    /// <summary>
    /// デッキから1枚カードを引き、そのカードをデッキから削除します。
    /// カードがない場合は null を返します。
    /// </summary>
    public Card DrawCard()
    {
        if (cards.Count == 0)
            return null;
        Card card = cards[0];
        cards.RemoveAt(0);
        return card;
    }

    /// <summary>
    /// 残っているカードの枚数を取得します。
    /// </summary>
    public int CardsRemaining => cards.Count;

    /// <summary>
    /// 必要に応じてデッキをリセットするためのメソッド(テスト用)
    /// </summary>
    public void ResetDeck()
    {
        cards.Clear();
        foreach (Suit suit in Enum.GetValues(typeof(Suit)))
        {
            for (int i = 1; i <= 3; i++)
            {
                string imagePath = $"images/{suit.ToString().ToLower()}_{i}.png";
                Image cardImage = Image.FromFile(imagePath);
                cards.Add(new Card(suit, i, cardImage));
            }
        }
    }
}

このメソッドは、テストや特定のシナリオでデッキを既知の状態にリセットするためのものです。具体的には、現在のカードリストをクリアし、各スート(Suit 列挙型の値)ごとに1から3までの番号のカードを再生成してデッキに追加します。通常のゲームではカードの枚数はもっと多いかもしれませんが、テスト用に少ない枚数で動作を確認したり、特定の条件下での挙動を検証する目的で使用されます。


4. 資料全体への適用例

4.1 HiLowGame クラスの実装

HiLowGame クラスは、コンストラクタで IDeck を受け取るため、本番環境では RealDeck、テスト環境では TestDeck を注入できます。

実装済みコードのはずです

public class HiLowGame
{
// ---- 略 ----
    /// <summary>
    /// 新しい HiLowGame インスタンスを初期化し、外部から IDeck を注入します。
    /// </summary>
    /// <param name="deck">ゲームで使用するデッキの実装。</param>
    public HiLowGame(IDeck deck)
    {
        Deck = deck;
    }
// ---- 略 ----
}

4.2 WinForm UI(HiLowForm)の利用例

WinForm のフォームでは、コンストラクタの引数により使用するデッキを切り替えることが可能です。
本番環境では new Deck() を、テスト環境では new TestDeck() を利用します。

コンストラクタ変更前

public HiLowForm()
{
    InitializeComponent();
    // IDeck の実装として Deck クラスを利用
    game = new HiLowGame(new Deck());
    // ここでカード表示用のコントロールを HiLowGame に渡す
    game.CardDisplay = cardControl;
}

コンストラクタ変更後

// useTestDeck 引数で利用するデッキを切り替え
public HiLowForm(bool useTestDeck = false)
{
    InitializeComponent();
    if (useTestDeck)
        game = new HiLowGame(new TestDeck());
    else
        game = new HiLowGame(new Deck());
}

このコードは、C#で作成された「HiLowForm」というフォーム(ウィンドウ)のコンストラクタ(初期化メソッド)です。以下に各部分を詳しく解説します。

1. コンストラクタの宣言

public HiLowForm(bool useTestDeck = false)
  • public
    → このクラス外部からもアクセスできることを意味します。
  • HiLowForm
    → クラスの名前と同じ名前になっており、これがコンストラクタであることを示しています。
  • bool useTestDeck = false
    useTestDeckというブール型のパラメータを受け取ります。ここでは既定値としてfalseが設定されています。つまり、引数を渡さずに呼び出すと、useTestDeckは自動的にfalseになります。

2. InitializeComponent() の呼び出し

InitializeComponent();
  • このメソッドは、フォームの各種コントロールやUI要素の初期化を行います。通常、Windowsフォームアプリケーションで自動生成されるコードです。

3. ゲームオブジェクトの初期化

if (useTestDeck)
    game = new HiLowGame(new TestDeck());
else
    game = new HiLowGame(new Deck());
  • if (useTestDeck)
    useTestDecktrueの場合の処理です。
  • game = new HiLowGame(new TestDeck());
    HiLowGameクラスの新しいインスタンスを作成し、その際にTestDeckというテスト用のデッキを渡します。これは、テスト環境や特定のシナリオで固定されたカード順序を使いたい場合に利用されます。
  • else … new Deck()
    useTestDeckfalseの場合、通常のDeck(おそらくランダムなカード順序を持つ本番用のデッキ)を使用してHiLowGameを初期化します。

まとめ

このコンストラクタは、フォームの初期化とともに、ゲームロジック(HiLowGame)のインスタンスを作成します。引数useTestDeckにより、テスト用のデッキ(TestDeck)を使うか、通常のデッキ(Deck)を使うかを切り替えることができます。これにより、デバッグやテストが容易になり、開発時と本番環境で異なる動作をさせることが可能となっています。

4.3 利用例のまとめ

Program.csファイル

  • 本番環境:
    通常は new Deck() を用いて、ランダムなシャッフル処理とカード管理を行います。
  Application.Run(new HiLowForm());
  • テスト環境:
    テスト用には、固定のカード順序を返す new TestDeck() を利用することで、予測可能な動作と再現性の高いテストが可能となります。
  Application.Run(new HiLowForm(useTestDeck: true));

解説のため、以下にいくつかの代替例を示します。

基本上記のコードで進めます

1. 位置引数を使う書き方

名前付き引数を省略して、単に位置引数として渡す方法です。

Application.Run(new HiLowForm(true));

2. 変数に代入してから実行する方法

フォームのインスタンスを変数に代入してから、Application.Runに渡す方法です。

HiLowForm form = new HiLowForm(true);
Application.Run(form);

どちらの方法も動作は同じで、好みやコードの可読性に合わせて選んでください。


5. まとめ

本資料では、WinForm を用いたハイローゲーム構築のサンプルに対して、

  • インターフェース(IDeck) を活用し、
  • 依存性注入 により本番時は RealDeck、テスト時は TestDeck を利用する具体例

を示しました。これにより、ランダム性を持つ本番環境と、固定された挙動でのテスト環境の両方で柔軟な運用が可能となり、ユニットテストの容易化や将来的な拡張が実現できます。

以上の実装例を参考に、プロジェクト内での役割分担やコードレビューの際に、依存性注入のメリットを十分に活用してください。