练习 - 使用示例数据评审和测试 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. 确保 GuidedProject 文件夹在 Visual Studio Code 中处于打开状态。

  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 方法。
    • 经过每次交易后报告收银机的状态。

    注意

    顶级语句包括 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
    

    注意

    该应用程序随机生成所购商品的价格。 因此,输出中报告的收银机值是不同的。

  5. 若要退出应用程序,请按 Enter。

  6. 关闭“终端”面板。

调试代码

在此任务中,你使用 Visual Studio Code 调试程序工具查找和修复逻辑问题。

  1. 在顶级语句的末尾附近,找到以下代码行:

    Console.WriteLine();
    
  2. 在选定的代码行上设置断点。

  3. 在 Visual Studio Code 的“运行”菜单上,选择“开始调试”。

  4. 请注意,代码会在断点处暂停运行。

  5. 在“调试控件”工具栏上,选择“单步执行”。

  6. 在“终端”面板中查看输出。

  7. 如果报告的收银机值和预期的收银机值相等,请在“调试控件”工具栏上选择“继续”。

  8. 重复上一步,直到报告的收银机值和预期的收银机值之间存在出入。

  9. 发现出入后,请花一点时间查看交易的详细信息。

  10. 请注意,报告的所收现金和退还零钱是正确的,但收银机少了五美元。

    这个缺额表明虽然报告是正确的,但 cashTill 数组未正确更新。

  11. 停止调试会话并关闭“终端”面板。

  12. 滚动到 MakeChange 方法的底部。

    用于“找零”的 while 语句位于 MakeChange 方法的末尾。

  13. 查看用于找零的 while 语句代码块。

    收银机少了五美元,问题很可能出在用于退还五美元钞票的 while 代码块。

  14. 请注意以下代码:

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

    cashTill[] 数组用于存储当前可用的每种面额的钞票数量。 数组元素 1 用于管理收银机中五美元钞票的数量。 while 语句中的表达式正确引用了 cashTill[1]。 但是,代码块内的语句扣减了 cashTill[2] 而不是 cashTill[1]。 指定索引值 2 表示从收银机中取出的是一张十美元的钞票,而不是一张五美元的钞票。

  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
    

    该应用程序随机生成所购商品的价格。 因此,输出中报告的收银机值是不同的。 当这两个值相等,你就成功修复了逻辑问题。