简介
GitHub Copilot 可以帮助您快速编写测试并提升工作效率。本文将演示如何使用 Copilot 编写单元测试和集成测试。虽然 Copilot 在为基础函数生成测试时表现良好,但复杂场景需要更详尽的提示和策略。本文将通过实际示例展示如何借助 Copilot 拆解任务并验证代码的正确性。
先决条件
在开始之前,您需要具备以下条件
- 一个 GitHub Copilot 订阅计划。
- Visual Studio、Visual Studio Code 或任意 JetBrains IDE。
- 在您的 IDE 中安装了 GitHub Copilot 扩展。
使用 Copilot Chat 编写单元测试
在本节中,我们将探讨如何使用 GitHub Copilot Chat 为 Python 类生成单元测试。此示例展示了如何使用 Copilot 为类似 BankAccount 的类创建单元测试。我们将向您演示如何提示 Copilot 生成测试、执行测试并验证结果。
示例类:BankAccount
让我们从一个包含存款、取款以及获取账户余额方法的 BankAccount 类开始。在 GitHub 仓库中创建一个新文件 bank_account.py,并添加以下 Python 实现的 BankAccount 类。
class BankAccount:
def __init__(self, initial_balance=0):
if initial_balance < 0:
raise ValueError("Initial balance cannot be negative.")
self.balance = initial_balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("Deposit amount must be positive.")
self.balance += amount
def withdraw(self, amount):
if amount <= 0:
raise ValueError("Withdrawal amount must be positive.")
if amount > self.balance:
raise ValueError("Cannot withdraw more than the current balance.")
self.balance -= amount
def get_balance(self):
return self.balance
使用 Copilot Chat 生成单元测试
您可以向 Copilot 提出编写覆盖多种场景的测试请求。对测试需求要具体,以获得最佳结果。下面是一个可用于为 BankAccount 类生成单元测试的示例提示。
提示: 为 Python 中的 BankAccount() 函数开发一套完整的单元测试。编写多个测试方法,覆盖各种场景,包括边界情况、异常处理和数据验证。
此提示包含了若干重要要点
- 范围:它明确要求提供一套完整的单元测试,使 Copilot 不仅生成基础测试,还能覆盖多种潜在使用场景。
- 场景:提示明确指出测试应包括
- 边界情况:针对边界条件的测试(例如,存款或取款的负值、取款金额超过可用余额)。
- 异常处理:当出现特定无效操作时应抛出的错误的测试(例如,取款金额超过可用余额、存入负数金额)。
- 数据验证:确保方法对输入进行正确的验证和处理。
Copilot 将给出类似以下的响应。
import unittest
from bank_account import BankAccount
class TestBankAccount(unittest.TestCase):
def setUp(self):
self.account = BankAccount()
def test_initial_balance(self):
self.assertEqual(self.account.get_balance(), 0)
def test_deposit_positive_amount(self):
self.account.deposit(100)
self.assertEqual(self.account.get_balance(), 100)
def test_withdraw_within_balance(self):
self.account.deposit(100)
self.account.withdraw(50)
self.assertEqual(self.account.get_balance(), 50)
def test_deposit_negative_amount_raises_error(self):
with self.assertRaises(ValueError):
self.account.deposit(-100)
def test_withdraw_negative_amount_raises_error(self):
with self.assertRaises(ValueError):
self.account.withdraw(-50)
def test_withdraw_more_than_balance_raises_error(self):
self.account.deposit(100)
with self.assertRaises(ValueError):
self.account.withdraw(200)
def test_initial_balance_negative_raises_error(self):
with self.assertRaises(ValueError):
BankAccount(-100)
if __name__ == '__main__':
unittest.main()
运行测试用例
Copilot 生成的测试套件为提示中指定的每个场景都提供了测试用例。BankAccount 类的每个方法都经过了一系列条件的测试,包括存款和取款等有效操作,以及负数金额和试图取款超过可用余额等边界情况。
当 Copilot 生成的测试套件令您满意后,将代码添加到新文件 test_bank_account.py 中。您可以询问如何运行这些测试。
提示: “如何使用 unittest 框架在 Python 中运行这些单元测试?”
Copilot 将提供以下 Bash 命令。
python -m unittest test_bank_account.py
运行测试后,您将在终端或 IDE 中看到输出。如果所有测试均通过,您即可确信 BankAccount 类按预期工作。
斜杠命令
此外,您可以使用 /tests 斜杠命令提示 Copilot 编写完整的单元测试套件。确保在 IDE 的当前标签页中打开该文件,Copilot 将为该文件生成单元测试。Copilot 生成的测试可能未覆盖所有场景,因而您应始终审查生成的代码并根据需要添加其他测试。
提示
如果您请 Copilot 为尚未编写单元测试的代码文件生成测试,可以通过在编辑器的相邻标签页中打开一个或多个已有的测试文件,为 Copilot 提供有用的上下文。Copilot 将能够识别您使用的测试框架,并更有可能编写与现有测试保持一致的测试代码。
Copilot 将生成如下单元测试套件。
import unittest
from bank_account import BankAccount
class TestBankAccount(unittest.TestCase):
def setUp(self):
self.account = BankAccount()
def test_initial_balance(self):
self.assertEqual(self.account.get_balance(), 0)
使用 Copilot 编写集成测试
集成测试对于确保系统各组件组合后能够正确工作至关重要。在本节中,我们将扩展 BankAccount 类,使其与外部服务 NotificationSystem(向用户发送通知)进行交互,并使用 mock 对系统行为进行测试,无需真实连接。集成测试的目标是验证 BankAccount 类与 NotificationSystem 服务之间的交互,确保它们能够协同工作。
示例类:带有通知服务的 BankAccount
让我们更新 BankAccount 类,使其能够与外部服务 NotificationSystem(向用户发送通知)进行交互。NotificationSystem 代表需要测试的集成部分。
在 bank_account.py 文件中使用以下代码片段更新 BankAccount 类。
class BankAccount:
def __init__(self, initial_balance=0, notification_system=None):
if initial_balance < 0:
raise ValueError("Initial balance cannot be negative.")
self.balance = initial_balance
self.notification_system = notification_system
def deposit(self, amount):
if amount <= 0:
raise ValueError("Deposit amount must be positive.")
self.balance += amount
if self.notification_system:
self.notification_system.notify(f"Deposited {amount}, new balance: {self.balance}")
def withdraw(self, amount):
if amount <= 0:
raise ValueError("Withdrawal amount must be positive.")
if amount > self.balance:
raise ValueError("Cannot withdraw more than the current balance.")
self.balance -= amount
if self.notification_system:
self.notification_system.notify(f"Withdrew {amount}, new balance: {self.balance}")
def get_balance(self):
return self.balance
这里我们将对 Copilot 编写针对 BankAccount 类的集成测试的请求进行拆分,分解为更小、更易处理的部分。这将帮助 Copilot 生成更准确、相关性更高的测试。
提示: “为 BankAccount 类中的 deposit 方法编写集成测试。使用 mock 模拟 NotificationSystem,并验证在存款后该系统被正确调用。”
此提示包含了若干重要要点
- 范围:它指定了集成测试,关注
deposit方法与NotificationSystem之间的交互,而非仅仅单元测试。 - Mock:它明确要求使用 mock 来模拟
NotificationSystem,确保在不依赖实际实现的情况下测试与外部系统的交互。 - 验证:提示强调在存款后验证
NotificationSystem被正确调用,以确保组件之间的集成按预期工作。 - 具体性:提示明确指出要测试的方法(
deposit)和类(BankAccount)。
提示
如果 Copilot 生成的测试无效,请提供该函数的输入输出示例。这将帮助 Copilot 评估函数的期望行为。
Copilot 将生成如下测试套件。
import unittest
from unittest.mock import Mock
from bank_account import BankAccount
class TestBankAccountIntegration(unittest.TestCase):
def setUp(self):
self.notification_system = Mock()
def test_deposit_with_notification(self):
account = BankAccount(initial_balance=100, notification_system=self.notification_system)
account.deposit(50)
self.assertEqual(account.get_balance(), 150)
self.notification_system.notify.assert_called_once_with("Deposited 50, new balance: 150")
if __name__ == '__main__':
unittest.main()
将生成的代码添加到新文件 test_bank_account_integration.py 中。
改进测试案例
上述提示生成了一个测试用例,用于验证在进行有效存款时调用了 NotificationSystem。但它未涵盖存款过程中抛出错误的情况。在此类情形下,不应调用 NotificationSystem。我们需要添加一个处理无效存款的测试用例,并确保通知系统未被触发。
提示: “添加一个针对无效存款金额的测试用例,以验证函数抛出正确的异常,并且 NotificationService 未被调用。”
Copilot 将生成如下测试用例。
def test_deposit_negative_amount_raises_error(self):
account = BankAccount(initial_balance=100, notification_system=self.notification_system)
with self.assertRaises(ValueError):
account.deposit(0)
self.notification_system.notify.assert_not_called()
询问改进空间
既然我们已经编写了用于验证存款集成功能的测试用例,这正是审视测试套件并寻找改进之处的好时机。虽然当前测试能够工作,但我们可以提示 Copilot 评估代码覆盖率并提出改进建议。
提示: “为了确保 BankAccount 类与 NotificationSystem 之间的集成得到全面覆盖,还应添加哪些测试?”
向 Copilot 提出此问题可以帮助您发现可能被忽略的缺失测试用例。在本例中,虽然我们已测试了有效和无效的存款,但尚未覆盖取款功能。
Copilot 将生成如下更新后的测试套件。
点击展开完整生成的代码示例
import unittest
from unittest.mock import Mock
from bank_account import BankAccount
class TestBankAccountIntegration(unittest.TestCase):
def setUp(self):
self.notification_system = Mock()
def test_deposit_with_notification(self):
account = BankAccount(initial_balance=100, notification_system=self.notification_system)
account.deposit(50)
self.assertEqual(account.get_balance(), 150)
self.notification_system.notify.assert_called_once_with("Deposited 50, new balance: 150")
def test_deposit_negative_amount_raises_error(self):
account = BankAccount(initial_balance=100, notification_system=self.notification_system)
with self.assertRaises(ValueError):
account.deposit(-50)
self.notification_system.notify.assert_not_called()
def test_deposit_zero_amount_raises_error(self):
account = BankAccount(initial_balance=100, notification_system=self.notification_system)
with self.assertRaises(ValueError):
account.deposit(0)
self.notification_system.notify.assert_not_called()
def test_withdraw_with_notification(self):
account = BankAccount(initial_balance=100, notification_system=self.notification_system)
account.withdraw(30)
self.assertEqual(account.get_balance(), 70)
self.notification_system.notify.assert_called_once_with("Withdrew 30, new balance: 70")
def test_withdraw_exceeding_balance_raises_error(self):
account = BankAccount(initial_balance=100, notification_system=self.notification_system)
with self.assertRaises(ValueError):
account.withdraw(150)
self.notification_system.notify.assert_not_called()
def test_withdraw_negative_amount_raises_error(self):
account = BankAccount(initial_balance=100, notification_system=self.notification_system)
with self.assertRaises(ValueError):
account.withdraw(-30)
self.notification_system.notify.assert_not_called()
def test_withdraw_zero_amount_raises_error(self):
account = BankAccount(initial_balance=100, notification_system=self.notification_system)
with self.assertRaises(ValueError):
account.withdraw(0)
self.notification_system.notify.assert_not_called()
def test_initial_negative_balance_raises_error(self):
with self.assertRaises(ValueError):
BankAccount(initial_balance=-100, notification_system=self.notification_system)
if __name__ == '__main__':
unittest.main()
当 Copilot 生成的测试套件令您满意后,使用以下命令运行测试以验证结果。
python -m unittest test_bank_account_integration.py
使用 Copilot Spaces 改进测试建议
Copilot Spaces 是一种功能,允许您组织并与 Copilot 共享特定任务的上下文。这有助于提升收到的建议的相关性。通过向 Copilot 提供更多项目上下文,您可以获得更佳的测试建议。
例如,您可以创建一个包含以下内容的空间:
- 您正在测试的模块(例如
payments.js) - 当前的测试套件(例如
payments.test.js) - 测试覆盖率报告或关于缺失内容的备注
在该空间中,您可以向 Copilot 提问,例如:
基于
payments.js中的逻辑,payments.test.js缺少哪些测试用例?
或
为
refund.js中的退款逻辑编写单元测试,遵循现有测试套件的结构。
欲了解有关使用 Copilot Spaces 的更多信息,请参阅 关于 GitHub Copilot Spaces。