1
0
mirror of synced 2025-12-20 10:28:40 -05:00
Files
docs/content/copilot/using-github-copilot/guides-on-using-github-copilot/writing-tests-with-github-copilot.md
2024-11-26 20:54:30 +00:00

317 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Writing tests with GitHub Copilot
intro: 'Use {% data variables.product.prodname_copilot_short %} to generate unit and integration tests, and help improve code quality.'
topics:
- Copilot
versions:
feature: copilot
redirect_from:
- /copilot/using-github-copilot/example-use-cases/writing-tests-with-github-copilot
shortTitle: Write tests
---
## Introduction
{% data variables.product.prodname_copilot %} can assist you in developing tests quickly and improving productivity. In this article, well demonstrate how you can use {% data variables.product.prodname_copilot_short %} to write both unit and integration tests. While {% data variables.product.prodname_copilot_short %} performs well when generating tests for basic functions, complex scenarios require more detailed prompts and strategies. This article will walk through practical examples of using {% data variables.product.prodname_copilot_short %} to break down tasks and verify code correctness.
## Prerequisites
Before getting started you must have the following:
* A [{% data variables.product.prodname_copilot %} subscription plan](/copilot/about-github-copilot/subscription-plans-for-github-copilot).
* {% data variables.product.prodname_vs %}, {% data variables.product.prodname_vscode %}, or any JetBrains IDE.
* The [{% data variables.product.prodname_copilot %} extension](/copilot/managing-copilot/configure-personal-settings/installing-the-github-copilot-extension-in-your-environment) installed in your IDE.
## Writing unit tests with {% data variables.product.prodname_copilot_chat_short %}
In this section, well explore how to use {% data variables.product.prodname_copilot_chat %} to generate unit tests for a Python class. This example demonstrates how you can use {% data variables.product.prodname_copilot_short %} to create unit tests for a class like `BankAccount`. We will show you how to prompt {% data variables.product.prodname_copilot_short %} to generate tests, execute them, and verify the results.
### Example class: `BankAccount`
Lets start with a class `BankAccount` that contains methods for depositing, withdrawing, and getting the balance of an account. Create a new file `bank_account.py` in a {% data variables.product.github %} repository and add the following `BankAccount` class in Python.
```python
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
```
### Generating unit tests with {% data variables.product.prodname_copilot_chat_short %}
You can prompt Copilot with a request to write tests that cover a range of scenarios. Be specific with your test requirements to get the best results. The following is an example prompt you can use to generate unit tests for the `BankAccount` class.
**Prompt:** _Develop a comprehensive suite of unit tests for the BankAccount() function in Python. Write multiple test methods that cover a wide range of scenarios, including edge cases, exception handling, and data validation._
This prompt does a few important things:
* **Scope:** It explicitly asks for a comprehensive suite of unit tests, so that {% data variables.product.prodname_copilot_short %} generates more than just basic tests, covering a variety of potential use cases.
* **Scenarios:** The prompt specifies that the tests should include:
* **Edge cases:** Tests for boundary conditions (e.g., negative values for deposits or withdrawals, withdrawing more than the available balance).
* **Exception handling:** Tests for errors that should be raised when certain invalid actions occur (e.g., withdrawing more than the available balance, depositing negative amounts).
* **Data validation:** Ensures that inputs are correctly validated and handled by the methods.
{% data variables.product.prodname_copilot_short %} will give you a response like the following.
```python
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()
```
### Running the tests cases
The test suite generated by {% data variables.product.prodname_copilot_short %} includes a test case for each scenario specified in the prompt. Each function in the `BankAccount` class is tested for a range of conditions, including valid operations like deposits and withdrawals, as well as edge cases such as negative amounts and attempts to withdraw more than the available balance.
Once {% data variables.product.prodname_copilot_short %} has generated the test suite to your satisfaction, add the code to a new file `test_bank_account.py`. You can ask it how to run the tests.
**Prompt:** _"How do I run these unit tests in Python using the unittest framework?"_
{% data variables.product.prodname_copilot_short %} will give you the following bash command.
```bash
python -m unittest test_bank_account.py
```
After running the tests, you will see the output in your terminal or IDE. If all tests pass, you can be confident that your `BankAccount` class is working as expected.
#### Slash command
Additionally, you can prompt {% data variables.product.prodname_copilot_short %} to write a full suite of unit tests with the `/tests` slash command. Ensure that you have the file open on the current tab of your IDE and {% data variables.product.prodname_copilot_short %} will generate unit tests for that file. The tests that {% data variables.product.prodname_copilot_short %} generates may not cover all scenarios, so you should always review the generated code and add any additional tests that may be necessary.
> [!TIP] If you ask {% data variables.product.prodname_copilot_short %} to write tests for a code file that is not already covered by unit tests, you can provide {% data variables.product.prodname_copilot_short %} with useful context by opening one or more existing test files in adjacent tabs in your editor. {% data variables.product.prodname_copilot_short %} will be able to see the testing framework you use and will be more likely to write a test that is consistent with your existing tests.
{% data variables.product.prodname_copilot_short %} will generate a unit test suite such as the following.
```python
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)
```
## Writing integration tests with {% data variables.product.prodname_copilot_short %}
Integration tests are essential for ensuring that the various components of your system work correctly when combined. In this section, well extend our `BankAccount` class to include interactions with an external service `NotificationSystem` and use mocks to test the systems behavior without needing real connections. The goal of the integration tests is to verify the interaction between the `BankAccount` class and the `NotificationSystem` services, ensuring that they work together correctly.
### Example class: `BankAccount` with notification services
Let's update the `BankAccount` class to include interactions with an external service such as a `NotificationSystem` that sends notifications to users. `NotificationSystem` represents the integration that would need to be tested.
Update the `BankAccount` class in the `bank_account.py` file with the following code snippet.
```python
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
```
Here we'll break down our request for {% data variables.product.prodname_copilot_short %} to write integration tests for the `BankAccount` class into smaller, more manageable pieces. This will help {% data variables.product.prodname_copilot_short %} generate more accurate and relevant tests.
**Prompt:** _"Write integration tests for the `deposit` function in the `BankAccount` class. Use mocks to simulate the `NotificationSystem` and verify that it is called correctly after a deposit."_
This prompt does a few important things:
* **Scope:** It specifies integration tests, focusing on the interaction between the `deposit` function and the `NotificationSystem`, rather than just unit tests.
* **Mocks:** It explicitly asks for the use of mocks to simulate the `NotificationSystem`, ensuring that the interaction with external systems is tested without relying on their actual implementation.
* **Verification:** The prompt emphasizes verifying that the `NotificationSystem` is called correctly after a deposit, ensuring that the integration between the components works as expected.
* **Specificity:** The prompt clearly states the method (`deposit`) and the class (`BankAccount`) to be tested.
> [!TIP] If {% data variables.product.prodname_copilot_short %} is producing invalid tests, provide examples of inputs and outputs for the function you want to test. This will help {% data variables.product.prodname_copilot_short %} evaluate the expected behavior of the function.
{% data variables.product.prodname_copilot_short %} will generate a test suite like the following.
```python
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()
```
Add the generated code to a new file `test_bank_account_integration.py`.
### Improving on the test cases
The prompt above generated a single test case that verifies the `NotificationSystem` is called when a valid deposit is made. However, it doesn't cover cases where an error is raised during the deposit. In those scenarios, the `NotificationSystem` should not be called. We need to add a test case that handles invalid deposits and ensure the notification system is not triggered.
**Prompt:** _"Add a test case for invalid deposit amounts to verify the function raises the correct exceptions and that the `NotificationService` is not called."_
{% data variables.product.prodname_copilot_short %} will generate a test case such as the following.
```python
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()
```
### Asking about areas of improvement
Now that we've written test cases to validate the integration functionality for deposits, this is a great opportunity to search for improvements within the test suite. While the current tests are functional, we can prompt {% data variables.product.prodname_copilot_short %} to evalulate code coverage and suggest areas of improvement.
**Prompt:** _"What additional tests should be included to ensure full coverage for the integration between the `BankAccount` class and the `NotificationSystem`?"_
Prompting Copilot with this question can help you identify missing test cases that may have been overlooked.
In this situation, while we tested valid and invalid deposits, we haven't yet covered the withdrawal functionality.
{% data variables.product.prodname_copilot_short %} will generate an updated test suite such as the following.
<details>
<summary>Click to expand the full generated code example</summary>
```python
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()
```
</details>
Once Copilot has generated the test suite to your satisfaction, run the tests with command below to verify the results.
```bash
python -m unittest test_bank_account_integration.py
```