{"meta":{"title":"使用 GitHub Copilot 编写测试","intro":"使用 Copilot 生成单元和集成测试，并帮助提高代码质量。","product":"GitHub Copilot","breadcrumbs":[{"href":"/zh/copilot","title":"GitHub Copilot"},{"href":"/zh/copilot/tutorials","title":"教程"},{"href":"/zh/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/copilot/about-github-copilot/subscription-plans-for-github-copilot)。\n* Visual Studio、Visual Studio Code 或任何 JetBrains IDE。\n* 在您的 IDE 中安装的 [GitHub Copilot 扩展](/zh/copilot/managing-copilot/configure-personal-settings/installing-the-github-copilot-extension-in-your-environment)。\n\n## 使用 副驾驶聊天\n\n编写单元测试\n\n在本部分中，我们将了解如何使用 GitHub Copilot 聊天功能 生成针对 Python 类的单元测试。 此示例演示如何使用 Copilot 创建针对类（例如 `BankAccount`）的单元测试。 我们将展示如何提示 Copilot 生成测试、执行测试并验证结果。\n\n### 示例类：`BankAccount`\n\n让我们从 `BankAccount` 类开始，该类包含存款、取款和获取帐户余额的方法。 在 GitHub 存储库中创建一个新文件`bank_account.py`，并在 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### 使用 副驾驶聊天\n\n生成单元测试\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\nCopilot 生成的测试套件包括提示中指定的每种场景的测试用例。\n`BankAccount` 类中的每个函数都会在一系列条件下进行测试，这些条件包括存款和取款等有效操作，以及负数金额和尝试提取的金额超过可用余额等边缘事例。\n\nCopilot 生成令你满意的测试套件后，请将代码添加到新文件 `test_bank_account.py` 中。 可以询问它如何运行测试。\n\n```\n          **Prompt：**_“如何在 Python 中使用 unittest 框架运行这些单元测试？”_\n```\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 提供有用的上下文。 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空间 改进测试建议\n\nCopilot空间 是一项功能，可用于组织并与 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空间 的更多信息，请参阅 [关于GitHub Copilot Spaces](/zh/copilot/using-github-copilot/copilot-spaces/about-organizing-and-sharing-context-with-copilot-spaces)。"}