Introducción
GitHub Copilot puede ayudarte a desarrollar pruebas con rapidez y a mejorar la productividad. En este artículo, vamos a demostrar cómo puedes usar Copilot para escribir pruebas tanto unitarias como de integración. Aunque Copilot funciona bien en la generación de pruebas para funciones básicas, los escenarios complejos requieren indicaciones y estrategias más detalladas. En este artículo, se describen ejemplos prácticos del uso de Copilot para desglosar las tareas y comprobar la corrección del código.
Requisitos previos
Antes de comenzar, necesitarás lo siguiente:
- Un plan de suscripción a GitHub Copilot.
- Visual Studio, Visual Studio Code o cualquier IDE de JetBrains.
- La extensión de GitHub Copilot extension instalada en el IDE.
Escritura de pruebas unitarias con el Copilot Chat
En esta sección, vamos a explorar cómo usar GitHub Copilot Chat a fin de generar pruebas unitarias para una clase de Python. En este ejemplo, se muestra cómo puedes usar Copilot a fin de crear pruebas unitarias para una clase como BankAccount
. Te mostraremos cómo solicitar a Copilot que genere pruebas, las ejecute y compruebe los resultados.
Clase de ejemplo: BankAccount
Vamos a comenzar con una clase BankAccount
que contiene métodos para realizar depósitos, retirar fondos y obtener el saldo de una cuenta. Crea un archivo bank_account.py
en un repositorio de GitHub y agrega la siguiente clase BankAccount
en 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
Generación de pruebas unitarias con el Copilot Chat
Puedes solicitar a Copilot que escriba pruebas que cubran una variedad de escenarios. Debes especificar bien los requisitos de la prueba para obtener los mejores resultados. A continuación, se muestra una solicitud de ejemplo que puedes usar a fin de generar pruebas unitarias para la clase BankAccount
.
Solicitud: Desarrolla un conjunto completo de pruebas unitarias para la función BankAccount() en Python. Escribe varios métodos de prueba que cubran una amplia gama de escenarios e incluyan casos perimetrales, control de excepciones y validación de datos.
Esta solicitud hace algunas cosas importantes:
- Ámbito: pide de forma explícita un conjunto integral de pruebas unitarias, por lo que Copilot no se limita a generar solo pruebas básicas y abarca una variedad de posibles casos de uso.
- Escenarios: la solicitud especifica que las pruebas deben incluir:
- Casos perimetrales: comprueba las condiciones de límite (por ejemplo, valores negativos para los depósitos o retiros, cuando se retira una cantidad superior al saldo disponible).
- Control de excepciones: pruebas para errores que deben activarse cuando se producen determinadas acciones no válidas (por ejemplo, retirar una cantidad superior al saldo disponible, depositar cantidades negativas).
- Validación de datos: garantiza que los métodos validen y controlen las entradas correctamente.
Copilot le proporcionará una respuesta similar a la siguiente.
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()
Ejecución de los casos de pruebas
El conjunto de pruebas que genera Copilot incluye un caso de prueba para cada escenario especificado en la solicitud. Se prueba cada una de las funciones de la clase BankAccount
para ver si se cumplen una serie de condiciones, incluidas operaciones válidas como los depósitos y retiros, además de casos perimetrales como importes negativos e intentos de retirar cantidades superiores al saldo disponible.
Una vez que estés satisfecho con el conjunto de pruebas que Copilot ha generado, agrega el código a un nuevo archivo test_bank_account.py
. Puedes preguntarle cómo ejecutar las pruebas.
Solicitud: "¿Cómo puedo ejecutar estas pruebas unitarias en Python con el marco unittest?"
Copilot te proporcionará el siguiente comando de Bash.
python -m unittest test_bank_account.py
Después de ejecutar las pruebas, verás la salida en tu terminal o IDE. Si se superan todas las pruebas, no hay duda de que la clase BankAccount
funciona según lo previsto.
Comando de barra oblicua
Además, puedes solicitar a Copilot que escriba un conjunto completo de pruebas unitarias con el comando de barra diagonal /tests
. Asegúrate de tener abierto el archivo en la pestaña actual del IDE y Copilot generará pruebas unitarias para ese archivo. Puede que las pruebas que Copilot genera no abarquen todos los escenarios, por lo que siempre debes revisar el código generado y agregar cualquier prueba adicional que pueda ser necesaria.
Tip
Si pides a Copilot que escriba pruebas para un archivo de código que no cubren las pruebas unitarias, puedes abrir uno o varios archivos de prueba existentes en pestañas adyacentes del editor para proporcionar a Copilot contexto útil. Copilot podrá ver el marco de pruebas que usas, por lo que será más probable que escriba una prueba coherente con tus pruebas existentes.
Copilot generará un conjunto de pruebas unitarias como el siguiente.
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)
Escritura de pruebas de integración con Copilot
Las pruebas de integración son esenciales para garantizar que los distintos componentes del sistema funcionan de forma correcta al combinarse. En esta sección, ampliaremos nuestra clase BankAccount
para incluir interacciones con un servicio NotificationSystem
externo y usaremos simulacros para probar el comportamiento del sistema sin necesidad de establecer conexiones reales. El objetivo de las pruebas de integración es comprobar la interacción entre la clase BankAccount
y los servicios NotificationSystem
para garantizar que funcionan correctamente de forma conjunta.
Clase de ejemplo: BankAccount
con servicios de notificación
Vamos a actualizar la clase BankAccount
para incluir interacciones con un servicio externo como NotificationSystem
, que envía notificaciones a los usuarios. NotificationSystem
representa la integración que debe probarse.
Actualiza la clase BankAccount
en el archivo bank_account.py
con el siguiente fragmento de código.
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
Aquí desglosaremos la solicitud que hemos hecho a Copilot respecto a escribir pruebas de integración para la clase BankAccount
en partes más pequeñas y manejables. Esto ayudará a Copilot a generar pruebas más precisas y pertinentes.
Solicitud: "Escribe pruebas de integración para la función deposit
en la clase BankAccount
. Usa simulaciones de NotificationSystem
y comprueba que se le llama correctamente después de un depósito".
Esta solicitud hace algunas cosas importantes:
- Ámbito: especifica pruebas de integración, para lo que se centra en la interacción entre la función
deposit
yNotificationSystem
, en lugar de solo pruebas unitarias. - Simulacros: solicita de forma explícita el uso de simulacros para la simulación de
NotificationSystem
con la garantía de que se prueba la interacción con los sistemas externos sin depender de su implementación real. - Verificación: la solicitud enfatiza que hay que comprobar que se llama correctamente a
NotificationSystem
después de un depósito, lo que garantiza que la integración entre los componentes funciona según lo previsto. - Especificidad: la solicitud indica claramente el método (
deposit
) y la clase (BankAccount
) que se van a probar.
Tip
Si Copilot genera pruebas no válidas, proporciona ejemplos de entradas y salidas para la función que quieres probar. Esto ayudará a Copilot a evaluar el comportamiento esperado de la función.
Copilot generará un conjunto de pruebas como el siguiente.
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()
Agrega el código generado a un nuevo archivo test_bank_account_integration.py
.
Mejora de los casos de prueba
La solicitud anterior ha generado un único caso de prueba que comprueba que se llama a NotificationSystem
cuando se realiza un depósito válido. Sin embargo, no cubre los casos en los que se genera un error durante el depósito. En esos escenarios, no debe llamarse a NotificationSystem
. Debemos agregar un caso de prueba que aborde los depósitos no válidos y garantice que no se desencadene el sistema de notificación.
Solicitud: "Agrega un caso de prueba de importes de depósito no válidos para comprobar que la función genera las excepciones correctas y que no se llama a NotificationService
".
Copilot generará un caso de prueba como el siguiente.
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()
Preguntas sobre las áreas de mejora
Ahora que hemos escrito casos de prueba para validar la funcionalidad de integración de los depósitos, se nos presenta una gran oportunidad para buscar mejoras en el conjunto de pruebas. Si bien las pruebas actuales son funcionales, podemos solicitar a Copilot que evalúe la cobertura de código y sugiera áreas de mejora.
Solicitud: "¿Qué otras pruebas deben incluirse para garantizar la cobertura completa de la integración entre la clase BankAccount
y NotificationSystem
?"
Plantear esta pregunta a Copilot puede ayudarte a identificar casos de prueba no señalados que pueden haberse pasado por alto. En esta situación, si bien se han probado los depósitos válidos y no válidos, no hemos cubierto aún la funcionalidad de retirada de fondos.
Copilot generará un conjunto de pruebas actualizado como el siguiente.
Haz clic para expandir el ejemplo de código generado completo.
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()
Una vez que Copilot haya generado un conjunto de pruebas que cumpla tus expectativas, ejecuta las pruebas con el comando siguiente para comprobar los resultados.
python -m unittest test_bank_account_integration.py