演習 - サンプル データを使って C# のコンソール アプリケーションを確認してテストする

完了

この演習では、Starter プロジェクトのコードを確認してテストし、ロジックの問題を切り分けて修正したうえで、更新されたアプリケーションが想定どおりに動作していることを確認します。

この演習では、次のタスクを実行します。

  1. コード レビュー: Program.cs ファイルの内容を確認します。

    Program.cs には、次のコード セクションが含まれています。

    • トップレベルのステートメント: トップレベルのステートメントは、testData の配列または多数のランダムに生成された取引を使用して、一連の取引をシミュレートします。
    • LoadTillEachMorning: LoadTillEachMorning メソッドが、事前に定義された各額面の紙幣の枚数を使ってレジを構成するために使用されます。
    • MakeChange: MakeChange メソッドが、購入取引中のレジを管理するために使用されます。
    • LogTillStatus: LogTillStatus メソッドが、現在のレジ内にある各額面の紙幣の枚数を表示するために使用されます。
    • TillAmountSummary: TillAmountSummary メソッドが、レジ内の現金額を示すメッセージを表示するために使用されます。
  2. 初期テスト: MakeChange 配列を使用して取引をシミュレートするときに、testData でレジの残高が正常に処理されることを確認します。

  3. コード デバッグ: ランダムに生成されたデータを使用するときに明らかになるロジックの問題を切り分けて修正します。

  4. 検証テスト: この演習で開発したコードに対して検証テストを実行します。

Program.cs ファイルの内容を確認する

このタスクでは、Starter プロジェクト コードを一通り見ていきます。 Program.cs ファイルには、毎日の取引の状況をシミュレートするアプリケーションが含まれています。 このアプリケーションでは、MakeChange メソッドを呼び出して各取引中のレジを管理します。 その他のメソッドは、レジを初期化し、レポート メッセージを生成するために使用されます。

  1. Visual Studio Code で GuidedProject フォルダーが開かれていることを確認します。

  2. エクスプローラー ビューで、GuidedProject および Starter フォルダーを展開します。

    Starter フォルダーには、このガイド付きプロジェクト モジュールのサンプル アプリケーションが含まれています。

  3. Visual Studio Code エディターで Program.cs ファイルを開きます。

  4. [表示] メニューで [コマンド パレット] を選択してください。

  5. コマンド プロンプトで「.net: g」と入力し、[.NET:ビルドとデバッグ用の資産を生成する] を選択してください。

  6. 起動するプロジェクトを選択するプロンプトで、Starter プロジェクトを選択します。

    作成された launch.json ファイルには、Starter プロジェクトの構成が含まれます。

  7. 数分かけて、このアプリケーションのトップレベルのステートメントを確認します。

    /*
    This application manages transactions at a store check-out line. The
    check-out line has a cash register, and the register has a cash till
    that is prepared with a number of bills each morning. The till includes
    bills of four denominations: $1, $5, $10, and $20. The till is used
    to provide the customer with change during the transaction. The item 
    cost is a randomly generated number between 2 and 49. The customer 
    offers payment based on an algorithm that determines a number of bills
    in each denomination. 
    
    Each day, the cash till is loaded at the start of the day. As transactions
    occur, the cash till is managed in a method named MakeChange (customer 
    payments go in and the change returned to the customer comes out). A 
    separate "safety check" calculation that's used to verify the amount of
    money in the till is performed in the "main program". This safety check
    is used to ensure that logic in the MakeChange method is working as 
    expected.
    */
    
    string? readResult = null;
    bool useTestData = true;
    
    Console.Clear();
    
    int[] cashTill = new int[] { 0, 0, 0, 0 };
    int registerCheckTillTotal = 0;
    
    // registerDailyStartingCash: $1 x 50, $5 x 20, $10 x 10, $20 x 5 => ($350 total)
    int[,] registerDailyStartingCash = new int[,] { { 1, 50 }, { 5, 20 }, { 10, 10 }, { 20, 5 } };
    
    int[] testData = new int[] { 6, 10, 17, 20, 31, 36, 40, 41 };
    int testCounter = 0;
    
    LoadTillEachMorning(registerDailyStartingCash, cashTill);
    
    registerCheckTillTotal = registerDailyStartingCash[0, 0] * registerDailyStartingCash[0, 1] + registerDailyStartingCash[1, 0] * registerDailyStartingCash[1, 1] + registerDailyStartingCash[2, 0] * registerDailyStartingCash[2, 1] + registerDailyStartingCash[3, 0] * registerDailyStartingCash[3, 1];
    
    // display the number of bills of each denomination currently in the till
    LogTillStatus(cashTill);
    
    // display a message showing the amount of cash in the till
    Console.WriteLine(TillAmountSummary(cashTill));
    
    // display the expected registerDailyStartingCash total
    Console.WriteLine($"Expected till value: {registerCheckTillTotal}\n\r");
    
    var valueGenerator = new Random((int)DateTime.Now.Ticks);
    
    int transactions = 10;
    
    if (useTestData)
    {
        transactions = testData.Length;
    }
    
    while (transactions > 0)
    {
        transactions -= 1;
        int itemCost = valueGenerator.Next(2, 20);
    
        if (useTestData)
        {
            itemCost = testData[testCounter];
            testCounter += 1;
        }
    
        int paymentOnes = itemCost % 2;                 // value is 1 when itemCost is odd, value is 0 when itemCost is even
        int paymentFives = (itemCost % 10 > 7) ? 1 : 0; // value is 1 when itemCost ends with 8 or 9, otherwise value is 0
        int paymentTens = (itemCost % 20 > 13) ? 1 : 0; // value is 1 when 13 < itemCost < 20 OR 33 < itemCost < 40, otherwise value is 0
        int paymentTwenties = (itemCost < 20) ? 1 : 2;  // value is 1 when itemCost < 20, otherwise value is 2
    
        // display messages describing the current transaction
        Console.WriteLine($"Customer is making a ${itemCost} purchase");
        Console.WriteLine($"\t Using {paymentTwenties} twenty dollar bills");
        Console.WriteLine($"\t Using {paymentTens} ten dollar bills");
        Console.WriteLine($"\t Using {paymentFives} five dollar bills");
        Console.WriteLine($"\t Using {paymentOnes} one dollar bills");
    
        // MakeChange manages the transaction and updates the till 
        string transactionMessage = MakeChange(itemCost, cashTill, paymentTwenties, paymentTens, paymentFives, paymentOnes);
    
        // Backup Calculation - each transaction adds current "itemCost" to the till
        if (transactionMessage == "transaction succeeded")
        {
            Console.WriteLine($"Transaction successfully completed.");
            registerCheckTillTotal += itemCost;
        }
        else
        {
            Console.WriteLine($"Transaction unsuccessful: {transactionMessage}");
        }
    
        Console.WriteLine(TillAmountSummary(cashTill));
        Console.WriteLine($"Expected till value: {registerCheckTillTotal}\n\r");
        Console.WriteLine();
    }
    
    Console.WriteLine("Press the Enter key to exit");
    do
    {
        readResult = Console.ReadLine();
    
    } while (readResult == null);
    

    トップレベルのステートメントのコードは、次のタスクを完了します。

    • MakeChange メソッドのテストに使用するアプリケーション データと環境変数を構成します。
    • LoadTillEachMorning()LogTillStatus()TillAmountSummary() の各メソッドを呼び出してレジを準備し、ステータス メッセージをコンソールに出力します。
    • while ループを使用して、一連の取引をシミュレートします。
    • MakeChange ループのコード ブロック内から while メソッドを呼び出します。
    • 各取引後のレジの状態を報告します。

    Note

    トップレベルのステートメントには、Console.ReadLine() ステートメントが含まれます。 デバッグ前に launch.json ファイルを更新する必要があります。

  8. 少し時間をとって LoadTillEachMorning() メソッドを確認します。

    static void LoadTillEachMorning(int[,] registerDailyStartingCash, int[] cashTill)
    {
        cashTill[0] = registerDailyStartingCash[0, 1];
        cashTill[1] = registerDailyStartingCash[1, 1];
        cashTill[2] = registerDailyStartingCash[2, 1];
        cashTill[3] = registerDailyStartingCash[3, 1];
    }
    
  9. 数分かけて MakeChange() メソッドを確認します。

    static string MakeChange(int cost, int[] cashTill, int twenties, int tens = 0, int fives = 0, int ones = 0)
    {
        string transactionMessage = "";
    
        cashTill[3] += twenties;
        cashTill[2] += tens;
        cashTill[1] += fives;
        cashTill[0] += ones;
    
        int amountPaid = twenties * 20 + tens * 10 + fives * 5 + ones;
        int changeNeeded = amountPaid - cost;
    
        if (changeNeeded < 0)
            transactionMessage = "Not enough money provided.";
    
        Console.WriteLine("Cashier Returns:");
    
        while ((changeNeeded > 19) && (cashTill[3] > 0))
        {
            cashTill[3]--;
            changeNeeded -= 20;
            Console.WriteLine("\t A twenty");
        }
    
        while ((changeNeeded > 9) && (cashTill[2] > 0))
        {
            cashTill[2]--;
            changeNeeded -= 10;
            Console.WriteLine("\t A ten");
        }
    
        while ((changeNeeded > 4) && (cashTill[1] > 0))
        {
            cashTill[2]--;
            changeNeeded -= 5;
            Console.WriteLine("\t A five");
        }
    
        while ((changeNeeded > 0) && (cashTill[0] > 0))
        {
            cashTill[0]--;
            changeNeeded--;
            Console.WriteLine("\t A one");
        }
    
        if (changeNeeded > 0)
            transactionMessage = "Can't make change. Do you have anything smaller?";
    
        if (transactionMessage == "")
            transactionMessage = "transaction succeeded";
    
        return transactionMessage;
    }
    

    MakeChange メソッドは、各購入取引中のレジを管理します。 取引プロセスは、次のリソースと条件に依存します。

    • 現金取引: MakeChange メソッドは、顧客からの現金支払いを受け取り、おつりとして顧客に返す必要がある各額面の紙幣の枚数を決定します。 MakeChange では、顧客が取引に充当する十分な金額を支払っていることを最初に確認する必要があります。 支払いが十分な場合、最も大きい額面の紙幣から始まって最も小さい額面までが "おつりを用意する" プロセスで処理されます。 各段階で、MakeChange は、現在の額面が、必要なおつりよりも小さいことを確認します。 MakeChange はまた、顧客に返すおつりに追加する前に、必要な額面の使用できる紙幣がレジにあることを確認します。

    • 入力パラメーター: MakeChange メソッドには次の入力パラメーターを使用します。

      • 購入する商品の代金を表す整数: itemCost
      • レジ内にある各額面の紙幣の枚数を含む整数の配列: cashTill
      • 顧客から受け取った支払い (各額面の紙幣の枚数を個別に指定): paymentTwentiespaymentTenspaymentFivespaymentOnes
    • レジ内の使用できる現金: 顧客から支払いとして受け取った紙幣は、おつりに使用できる各額面の紙幣に含める必要があります。

    • 顧客に渡すおつり: 顧客に渡すおつりは、顧客が支払った金額から商品の代金を差し引いて計算されます。

    • 過少支払い: 顧客から十分な支払いを受け取っていない場合は、MakeChange から説明メッセージが返され、取引が取り消されます。

    • レジが不足: レジで正確なおつりを用意できない場合は、MakeChange から説明メッセージが返され、取引は取り消されます。

  10. 少し時間をとって LogTillStatus() メソッドを確認します。

    static void LogTillStatus(int[] cashTill)
    {
        Console.WriteLine("The till currently has:");
        Console.WriteLine($"{cashTill[3] * 20} in twenties");
        Console.WriteLine($"{cashTill[2] * 10} in tens");
        Console.WriteLine($"{cashTill[1] * 5} in fives");
        Console.WriteLine($"{cashTill[0]} in ones");
        Console.WriteLine();
    }
    

    LogTillStatus メソッドは、cashTill 配列を使用してレジの現在の内容を報告します。

  11. 少し時間をとって TillAmountSummary() メソッドを確認します。

    static string TillAmountSummary(int[] cashTill)
    {
        return $"The till has {cashTill[3] * 20 + cashTill[2] * 10 + cashTill[1] * 5 + cashTill[0]} dollars";
    
    }
    

    TillAmountSummary メソッドは cashTill 配列を使用して、レジ内の使用できる現在の現金残高を計算します。

これで、既存のコード プロジェクトのレビューは完了です。

MakeChange 配列の使用時に testData で金銭が正常に管理されることを確認する

このタスクでは、testData 配列を使用して取引をシミュレートし、MakeChange でレジの残高が正常に処理されることを確認します。

  1. Visual Studio Code で [実行] メニューの [デバッグの開始] を選択します。

  2. IOException エラーが発生することに注目してください。

    デバッグ コンソールで Console.Clear() または Console.ReadLine() メソッドはサポートされていません。 デバッグする前に、launch.json ファイルを更新する必要があります。

  3. [デバッグ] ツール バー[停止] を選択します。

  4. エクスプローラー ビューを使用して、launch.json ファイルを開きます。

  5. launch.json ファイルで、console 属性を次のように更新します。

    // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
    "console":"integratedTerminal",
    

    console 属性の既定値は internalConsole であり、[デバッグ コンソール] パネルに合わせて配置されます。 残念ながら、[デバッグ コンソール] パネルでは、一部のコンソール メソッドはサポートされていません。 integratedTerminal 設定では、コンソールの入力と出力をサポートする [ターミナル] パネルに合わせて配置されます。

  6. 変更内容を launch.json ファイルに保存します。

  7. Visual Studio Code で [実行] メニューの [デバッグの開始] を選択します。

  8. [ターミナル] パネルで、アプリケーションによって生成された出力を確認します。

    [デバッグ コンソール] パネルからターミナル パネルに切り替えて、出力を確認します。

  9. MakeChange 配列を使用して取引をシミュレートするときに、testData で残高が正常に処理されることに注目してください。

    報告された出力の下部に次の行が表示されていることがわかります。

    The till has 551 dollars
    Expected till value: 551
    
    
    Press the Enter key to exit
    

    報告されたレジの値と想定される値の両方が 551 であることに注目してください。

  10. アプリケーションを終了するには、Enter キーを押します。

ロジックの問題を特定して修正する

このタスクでは、シミュレートされた取引を使用してコード ロジックの問題を明らかにし、Visual Studio Code デバッガー ツールを使用して問題を切り分けて修正します。

  1. ランダムに生成された取引を使用してコードを実行するには、useTestData に割り当てた値を false に変更します。

    useTestData 変数は、トップレベルのステートメントの先頭付近にあります。

  2. Program.cs ファイルを保存し、デバッガーでアプリケーションを実行します。

  3. [ターミナル] パネルで出力を確認します。

  4. レジ残高の不一致に注目してください。

    MakeChange によって計算された最終的なレジ残高と、トップレベルのステートメントで維持されている残高が、出力の下部に報告されます。 次に例を示します。

    Transaction successfully completed.
    The till has 379 dollars
    Expected till value: 434
    
    
    Press the Enter key to exit
    

    Note

    アプリケーションは購入商品の代金をランダムに生成します。 したがって、出力で報告されるレジの値は異なります。

  5. アプリケーションを終了するには、Enter キーを押します。

  6. [ターミナル] パネルを閉じます。

コードをデバッグする

このタスクでは、Visual Studio Code デバッガー ツールを使用して、ロジックの問題を切り分けて修正します。

  1. トップレベルのステートメントの末尾付近で、次のコード行を見つけます。

    Console.WriteLine();
    
  2. 選択したコード行にブレークポイントを設定します。

  3. Visual Studio Code で [実行] メニューの [デバッグの開始] を選択します。

  4. ブレークポイントでコードの実行が一時停止していることに注目してください。

  5. [デバッグ コントロール] ツール バーの [ステップ インする] を選択します。

  6. [ターミナル] パネルで出力を確認します。

  7. 報告されたレジ値と想定されるレジ値が等しい場合は、[デバッグ コントロール] ツール バーの [続行] を選択します。

  8. 報告されたレジ値と想定されるレジ値の間に不一致が見つかるまで前の手順を繰り返します。

  9. 不一致が見つかったら、少し時間をかけて取引の詳細を調べます。

  10. 報告された受け取り済み現金と返されたおつりは正しいにもかかわらず、レジに 5 ドル足りないことに注目してください。

    この不足は、報告は正しいものの、cashTill 配列が正しく更新されていないことを示しています。

  11. デバッグ セッションを停止し、[ターミナル] パネルを閉じます。

  12. MakeChange メソッドの一番下までスクロールします。

    "おつりを用意する" ために使用する while ステートメントは、MakeChange メソッドの末尾にあります。

  13. おつりを用意するために使用される while ステートメントのコード ブロックを確認します。

    レジは 5 ドル不足しているため、5 ドル紙幣を返すために使用される while コード ブロックに問題がある可能性があります。

  14. 次のコードに注意してください。

    while ((changeNeeded > 4) && (cashTill[1] > 0))
    {
        cashTill[2]--;
        changeNeeded -= 5;
        Console.WriteLine("\t A five");
    }    
    

    cashTill[] 配列は、現在使用できる各額面の紙幣の枚数を格納するために使用されます。 配列要素 1 は、レジ内の 5 ドル紙幣の枚数を管理するために使用されます。 while ステートメント内の式は cashTill[1] を正しく参照しています。 ただし、コード ブロック内のステートメントは、cashTill[2] ではなく cashTill[1] をデクリメントします。 2 のインデックス値を指定することは、レジから 5 ドル紙幣ではなく 10 ドル紙幣が取り出されることを意味します。

  15. while コード ブロックを次のように更新します。

    while ((changeNeeded > 4) && (cashTill[1] > 0))
    {
        cashTill[1]--;
        changeNeeded -= 5;
        Console.WriteLine("\t A five");
    }    
    
  16. Program.cs ファイルを保存します。

作業を確認

このタスクでは、アプリケーションを実行し、更新されたコードが意図したとおりに動作することを確認します。

  1. Visual Studio Code の [実行] メニューで、[すべてのブレークポイントを削除する] を選択します。

  2. [実行] メニューで、[デバッグの開始] を選択します。

  3. [ターミナル] パネルで出力を確認します。

  4. 報告されたレジの値が想定されるレジの値と等しいことを確認します。

    MakeChange によって計算された最終的なレジ残高と、トップレベルのステートメントで維持されている残高が、出力の下部に報告されます。 次に例を示します。

    Transaction successfully completed.
    The till has 452 dollars
    Expected till value: 452
    
    
    Press the Enter key to exit
    

    アプリケーションは購入商品の代金をランダムに生成します。 したがって、出力で報告されるレジの値は異なります。 2 つの値が等しければ、ロジックの問題の修正は成功しています。