简介
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
类按预期运作。
/ 命令
此外,还可以提示 Copilot 使用 /tests
斜杠命令编写一套完整的单元测试。 确保在 IDE 的当前选项卡上打开文件,并且 Copilot 将生成针对该文件的单元测试。 Copilot 生成的测试可能并未涵盖所有场景,因此应始终评审生成的代码并添加可能需要的任何其他测试。
Tip
如果要求 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
的交互,并在无需实际连接的情况下使用模拟来测试系统行为。 集成测试的目标是验证 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
函数编写集成测试。****__ 使用模拟来模拟 NotificationSystem
并验证在存款后是否对其进行正确调用。”
此提示说明一些重要事项:
- 范围:它指定集成测试,侧重于
deposit
函数与NotificationSystem
之间的交互,而不仅仅是单元测试****。 - 模拟:它明确要求使用模拟来模拟
NotificationSystem
,以确保测试与外部系统的交互,而无需依赖其实际实现****。 - 验证:此提示强调验证在存款后是否正确调用
NotificationSystem
,以确保组件之间的集成按预期运作****。 - 具体性:此提示明确地说明了要测试的方法 (
deposit
) 和类 (BankAccount
)****。
Tip
如果 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