{"meta":{"title":"使用 GitHub Copilot 编写测试","intro":"使用 Copilot 生成单元测试和集成测试，并帮助提高代码质量。","product":"GitHub Copilot","breadcrumbs":[{"href":"/zh/enterprise-cloud@latest/copilot","title":"GitHub Copilot"},{"href":"/zh/enterprise-cloud@latest/copilot/tutorials","title":"教程"},{"href":"/zh/enterprise-cloud@latest/copilot/tutorials/write-tests","title":"编写测试"}],"documentType":"article"},"body":"# 使用 GitHub Copilot 编写测试\n\n使用 Copilot 生成单元测试和集成测试，并帮助提高代码质量。\n\n## 简介\n\nGitHub Copilot 可帮助你快速开发测试并提高工作效率。 在本文中，我们将演示如何用于 Copilot 编写单元测试和集成测试。 虽然 Copilot 在生成基本函数的测试时表现良好，但复杂方案需要更详细的提示和策略。 本文将通过实际示例讲解如何使用 Copilot 来分解任务并验证代码的正确性。\n\n## 先决条件\n\n在开始之前，您必须具备以下各项：\n\n* [订阅GitHub Copilot计划](/zh/enterprise-cloud@latest/copilot/get-started/plans)。\n* Visual Studio、Visual Studio Code或任何 JetBrains 集成开发环境。\n* 在 [GitHub Copilot](/zh/enterprise-cloud@latest/copilot/how-tos/set-up/install-copilot-extension) IDE 中安装的扩展。\n\n## 使用 Copilot Chat 编写单元测试\n\n在本节中，我们将探讨如何使用 GitHub Copilot Chat 为 Python 类生成单元测试。 此示例演示了如何使用 Copilot 为像 `BankAccount` 这样的类创建单元测试。 我们将向你展示如何提示 Copilot 生成测试、执行测试并验证结果。\n\n### 示例类：`BankAccount`\n\n让我们从 `BankAccount` 类开始，该类包含存款、取款和获取帐户余额的方法。 在存储库中`bank_account.py`创建新文件GitHub，并在Python中添加以下`BankAccount`类。\n\n```python\nclass BankAccount:\n    def __init__(self, initial_balance=0):\n        if initial_balance < 0:\n            raise ValueError(\"Initial balance cannot be negative.\")\n        self.balance = initial_balance\n\n    def deposit(self, amount):\n        if amount <= 0:\n            raise ValueError(\"Deposit amount must be positive.\")\n        self.balance += amount\n\n    def withdraw(self, amount):\n        if amount <= 0:\n            raise ValueError(\"Withdrawal amount must be positive.\")\n        if amount > self.balance:\n            raise ValueError(\"Cannot withdraw more than the current balance.\")\n        self.balance -= amount\n\n    def get_balance(self):\n        return self.balance\n```\n\n### 使用Copilot Chat生成单元测试\n\n可以通过请求来提示 Copilot 编写涵盖一系列场景的测试。 请明确测试要求，以获得最佳结果。 下面是一个示例提示，可用于生成针对 `BankAccount` 类的单元测试。\n\n提示：在 Python 中开发一套针对 BankAccount() 函数的全面单元测试。\\*\\*\\*\\*\\_\\_ 编写涵盖多种场景（包括边缘事例、异常处理和数据验证）的多种测试方法。\n\n此提示说明一些重要事项：\n\n* **范围：** 它显式要求提供全面的单元测试套件，以便 Copilot 生成不仅仅是基本测试，涵盖各种潜在的用例。\n* 场景：此提示指定测试应包括\\*\\*\\*\\*：\n  * 边缘事例：边界条件（例如存款或取款金额为负值、取款金额超过可用余额）测试\\*\\*\\*\\*。\n  * **异常处理：** 测试当执行某些无效操作（例如取款金额超过可用余额、存入负金额）时应引发的错误。\n  * 数据验证：确保通过方法正确验证和处理输入\\*\\*\\*\\*。\n\nCopilot 将提供如下所示的响应。\n\n```python\nimport unittest\nfrom bank_account import BankAccount\n\nclass TestBankAccount(unittest.TestCase):\n    def setUp(self):\n        self.account = BankAccount()\n\n    def test_initial_balance(self):\n        self.assertEqual(self.account.get_balance(), 0)\n\n    def test_deposit_positive_amount(self):\n        self.account.deposit(100)\n        self.assertEqual(self.account.get_balance(), 100)\n\n    def test_withdraw_within_balance(self):\n        self.account.deposit(100)\n        self.account.withdraw(50)\n        self.assertEqual(self.account.get_balance(), 50)\n\n    def test_deposit_negative_amount_raises_error(self):\n        with self.assertRaises(ValueError):\n            self.account.deposit(-100)\n\n    def test_withdraw_negative_amount_raises_error(self):\n        with self.assertRaises(ValueError):\n            self.account.withdraw(-50)\n\n    def test_withdraw_more_than_balance_raises_error(self):\n        self.account.deposit(100)\n        with self.assertRaises(ValueError):\n            self.account.withdraw(200)\n\n    def test_initial_balance_negative_raises_error(self):\n        with self.assertRaises(ValueError):\n            BankAccount(-100)\n\nif __name__ == '__main__':\n    unittest.main()\n```\n\n### 运行测试用例\n\n生成的 Copilot 测试套件包括提示中指定的每个方案的测试用例。\n`BankAccount` 类中的每个函数都会在一系列条件下进行测试，这些条件包括存款和取款等有效操作，以及负数金额和尝试提取的金额超过可用余额等边缘事例。\n\n当 Copilot 生成的测试套件达到你的预期后，将代码添加到新文件 `test_bank_account.py` 中。 可以询问它如何运行测试。\n\n**Prompt：***“如何在 Python 中使用 unittest 框架运行这些单元测试？”*\n\nCopilot 将为你提供以下 bash 命令。\n\n```bash\npython -m unittest test_bank_account.py\n```\n\n运行测试后，终端或 IDE 会显示输出。 如果所有测试均通过，则可以确信 `BankAccount` 类按预期运作。\n\n#### / 命令\n\n此外，你还可以提示 Copilot 使用 `/tests` 斜杠命令编写一整套单元测试。 确保该文件已在 IDE 的当前标签页中打开，然后 Copilot 将为该文件生成单元测试。 生成的测试 Copilot 可能并不涵盖所有方案，因此应始终查看生成的代码并添加可能需要的任何其他测试。\n\n> \\[!TIP] 如果要求 Copilot 为单元测试尚未涵盖的代码文件编写测试，可以通过在编辑器的相邻选项卡中打开一个或多个现有测试文件来提供 Copilot 有用的上下文。\n> Copilot 将能够查看所使用的测试框架，并且更有可能编写与现有测试一致的测试。\n\nCopilot 将生成单元测试套件，如下所示。\n\n```python\nimport unittest\nfrom bank_account import BankAccount\n\nclass TestBankAccount(unittest.TestCase):\n    def setUp(self):\n        self.account = BankAccount()\n\n    def test_initial_balance(self):\n        self.assertEqual(self.account.get_balance(), 0)\n```\n\n## 使用 Copilot 编写集成测试\n\n集成测试对于确保系统的各个组件在组合时能正常工作至关重要。 在本部分中，我们将扩展 `BankAccount` 类，使其包含与外部服务 `NotificationSystem` 的交互，并在无需实际连接的情况下使用模拟来测试系统行为。 集成测试的目标是验证 `BankAccount` 类与 `NotificationSystem` 服务之间的交互，确保它们能够正常协作。\n\n### 示例类：具有通知服务的 `BankAccount`\n\n让我们更新 `BankAccount` 类，使其包含与外部服务（例如向用户发送通知的 `NotificationSystem`）的交互。\n`NotificationSystem` 表示需要测试的集成。\n\n使用下面的代码片段更新 `BankAccount` 文件中的 `bank_account.py` 类。\n\n```python\nclass BankAccount:\n    def __init__(self, initial_balance=0, notification_system=None):\n        if initial_balance < 0:\n            raise ValueError(\"Initial balance cannot be negative.\")\n        self.balance = initial_balance\n        self.notification_system = notification_system\n\n    def deposit(self, amount):\n        if amount <= 0:\n            raise ValueError(\"Deposit amount must be positive.\")\n        self.balance += amount\n        if self.notification_system:\n            self.notification_system.notify(f\"Deposited {amount}, new balance: {self.balance}\")\n\n    def withdraw(self, amount):\n        if amount <= 0:\n            raise ValueError(\"Withdrawal amount must be positive.\")\n        if amount > self.balance:\n            raise ValueError(\"Cannot withdraw more than the current balance.\")\n        self.balance -= amount\n\n        if self.notification_system:\n            self.notification_system.notify(f\"Withdrew {amount}, new balance: {self.balance}\")\n\n    def get_balance(self):\n        return self.balance\n```\n\n在这里，我们将把请 Copilot 为 `BankAccount` 类编写集成测试这一请求拆分成更小、更易于处理的部分。 这将有助于 Copilot 生成更准确的相关测试。\n\n提示：“为 \\*\\*\\*\\* 类的 `deposit` 函数编写集成测试。使用模拟来模拟 `BankAccount`，并验证在存款后对其的调用是否正确。”\n\n此提示说明一些重要事项：\n\n* 范围：它指定集成测试，侧重于 \\*\\*\\*\\* 函数与 `deposit` 之间的交互，而不仅仅是单元测试`NotificationSystem`。\n* **模拟：** 它明确要求使用模拟对象进行`NotificationSystem`，以确保测试与外部系统的交互，而无需依赖其实际实现。\n* **验证：** 此提示着重于验证存款后是否正确调用了 `NotificationSystem`，并确保组件之间的集成正常工作。\n* 具体性：此提示明确地说明了要测试的方法 (\\*\\*\\*\\*) 和类 (`deposit`)`BankAccount`。\n\n> \\[!TIP] 如果 Copilot 生成无效的测试，请提供要测试的函数的输入和输出示例。 这有助于 Copilot 评估函数的预期行为。\n\nCopilot 将生成如下所示的测试套件。\n\n```python\nimport unittest\nfrom unittest.mock import Mock\nfrom bank_account import BankAccount\n\nclass TestBankAccountIntegration(unittest.TestCase):\n    def setUp(self):\n        self.notification_system = Mock()\n\n    def test_deposit_with_notification(self):\n        account = BankAccount(initial_balance=100, notification_system=self.notification_system)\n        account.deposit(50)\n        self.assertEqual(account.get_balance(), 150)\n        self.notification_system.notify.assert_called_once_with(\"Deposited 50, new balance: 150\")\n\nif __name__ == '__main__':\n    unittest.main()\n```\n\n将生成的代码添加到新文件 `test_bank_account_integration.py` 中。\n\n### 我们正在改进测试用例\n\n上述提示生成了一个测试用例，用于验证在进行有效存款时是否调用了 `NotificationSystem`。 但是，它不包括在存款过程中出现错误的情况。 在这些情况下，不应调用 `NotificationSystem`。 我们需要添加一个可处理无效存款的测试用例，并确保不会触发通知系统。\n\n提示：“添加一个针对无效存款金额的测试用例，以验证函数是否引发正确的异常，并且确保 \\*\\*\\*\\* 没有被调用。”\n\nCopilot 将生成如下所示的测试用例。\n\n```python\n    def test_deposit_negative_amount_raises_error(self):\n        account = BankAccount(initial_balance=100, notification_system=self.notification_system)\n        with self.assertRaises(ValueError):\n            account.deposit(0)\n        self.notification_system.notify.assert_not_called()\n```\n\n### 询问可以改进的领域\n\n我们已经编写了测试用例来验证存款的集成功能，现在是一个很好机会来寻找测试套件的改进。 虽然当前测试可正常运行，但我们可以提示 Copilot 评估代码覆盖率并提出改进建议。\n\n提示：“应包括哪些附加测试，以确保全面覆盖 \\*\\*\\*\\* 类与 `BankAccount` 之间的集成？”`NotificationSystem`\\_\\_\n\n向 Copilot 提出这个问题可以帮助你识别可能已被忽略的缺失测试用例。\n在这种情况下，虽然我们测试了有效和无效存款，但尚未涵盖取款功能。\n\nCopilot 将生成更新的测试套件，如下所示。\n\n<details>\n  <summary>单击以展开完整生成的代码示例</summary>\n\n```python\nimport unittest\nfrom unittest.mock import Mock\nfrom bank_account import BankAccount\n\nclass TestBankAccountIntegration(unittest.TestCase):\n    def setUp(self):\n        self.notification_system = Mock()\n\n    def test_deposit_with_notification(self):\n        account = BankAccount(initial_balance=100, notification_system=self.notification_system)\n        account.deposit(50)\n        self.assertEqual(account.get_balance(), 150)\n        self.notification_system.notify.assert_called_once_with(\"Deposited 50, new balance: 150\")\n\n    def test_deposit_negative_amount_raises_error(self):\n        account = BankAccount(initial_balance=100, notification_system=self.notification_system)\n        with self.assertRaises(ValueError):\n            account.deposit(-50)\n        self.notification_system.notify.assert_not_called()\n\n    def test_deposit_zero_amount_raises_error(self):\n        account = BankAccount(initial_balance=100, notification_system=self.notification_system)\n        with self.assertRaises(ValueError):\n            account.deposit(0)\n        self.notification_system.notify.assert_not_called()\n\n    def test_withdraw_with_notification(self):\n        account = BankAccount(initial_balance=100, notification_system=self.notification_system)\n        account.withdraw(30)\n        self.assertEqual(account.get_balance(), 70)\n        self.notification_system.notify.assert_called_once_with(\"Withdrew 30, new balance: 70\")\n\n    def test_withdraw_exceeding_balance_raises_error(self):\n        account = BankAccount(initial_balance=100, notification_system=self.notification_system)\n        with self.assertRaises(ValueError):\n            account.withdraw(150)\n        self.notification_system.notify.assert_not_called()\n\n    def test_withdraw_negative_amount_raises_error(self):\n        account = BankAccount(initial_balance=100, notification_system=self.notification_system)\n        with self.assertRaises(ValueError):\n            account.withdraw(-30)\n        self.notification_system.notify.assert_not_called()\n\n    def test_withdraw_zero_amount_raises_error(self):\n        account = BankAccount(initial_balance=100, notification_system=self.notification_system)\n        with self.assertRaises(ValueError):\n            account.withdraw(0)\n        self.notification_system.notify.assert_not_called()\n\n    def test_initial_negative_balance_raises_error(self):\n        with self.assertRaises(ValueError):\n            BankAccount(initial_balance=-100, notification_system=self.notification_system)\n\nif __name__ == '__main__':\n    unittest.main()\n```\n\n</details>\n\n在 Copilot 生成令你满意的测试套件后，请使用以下命令运行测试以验证结果。\n\n```bash\npython -m unittest test_bank_account_integration.py\n```\n\n## 使用 Copilot Spaces 改进测试建议\n\nCopilot Spaces 是一项功能，可让你整理任务相关的上下文并与 Copilot 共享。 这有助于提升收到的建议的相关性。 通过提供有关 Copilot 项目的更多上下文，可以获取更好的测试建议。\n\n例如，你可以创建一个包含以下内容的空间：\n\n* 要测试的模块（如 `payments.js`）\n* 当前测试套件（如 `payments.test.js`）\n* 测试覆盖率报告或关于缺失内容的备注\n\n在该空间中，您可以询问 Copilot 如下这类问题：\n\n> 根据 `payments.test.js` 中的逻辑，`payments.js` 中缺少哪些测试用例？\n\n或：\n\n> 请根据现有测试套件的结构，为 `refund.js` 中的退款逻辑编写一个单元测试。\n\n有关使用 Copilot Spaces的详细信息，请参阅 [关于GitHub Copilot Spaces](/zh/enterprise-cloud@latest/copilot/concepts/context/spaces)。"}