Skip to main content

Escritura de pruebas con GitHub Copilot

Use Copilot para generar pruebas unitarias e de integración y ayudar a mejorar la calidad del código.

Introducción

GitHub Copilot puede ayudarle a desarrollar pruebas rápidamente y mejorar la productividad. En este artículo, se muestra cómo puede usar Copilot para escribir pruebas unitarias y de integración. Aunque Copilot funciona bien al generar pruebas para funciones básicas, los escenarios complejos requieren avisos y estrategias más detallados. En este artículo se describen ejemplos prácticos de uso Copilot para desglosar tareas y comprobar la corrección del código.

Requisitos previos

Antes de comenzar, debe tener lo siguiente:

Creación de pruebas unitarias con Chat de Copiloto

En esta sección, exploraremos cómo usar Copilot Chat de GitHub para generar pruebas unitarias para una clase Python. En este ejemplo se muestra cómo puede usar Copilot para crear pruebas unitarias para una clase como BankAccount. Le mostraremos cómo solicitar Copilot que genere pruebas, ejecútelos 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. Cree un nuevo archivo bank_account.py en un GitHub repositorio y agregue la siguiente BankAccount clase 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 Chat de Copiloto

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 indicación realiza varias tareas importantes:

  • Ámbito: Solicita explícitamente un conjunto completo de pruebas unitarias, por lo que genera más que Copilot solo pruebas básicas, que abarcan 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 dará 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()

Ejecutar los casos de prueba

El conjunto de pruebas generado por Copilot incluye un caso de prueba para cada escenario especificado en la indicación. 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 Copilot haya generado el conjunto de pruebas según sus expectativas, añada el código a un archivo nuevo test_bank_account.py. Puedes preguntarle cómo ejecutar las pruebas.

Prompt:"¿Cómo ejecuto estas pruebas unitarias en Python mediante el marco de pruebas unittest?"

Copilot le dará 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, puede pedir Copilot que escriba un conjunto completo de pruebas unitarias con el /tests comando de barra diagonal. Asegúrese de que tiene el archivo abierto en la pestaña actual del IDE y Copilot generará pruebas unitarias para ese archivo. Es posible que las pruebas que Copilot generen no cubran todos los escenarios, por lo que siempre debe revisar el código generado y agregar las pruebas adicionales que puedan ser necesarias.

Sugerencia

Si pide Copilot escribir pruebas para un archivo de código que aún no está cubierto por pruebas unitarias, puede proporcionar Copilot contexto útil abriendo uno o varios archivos de prueba existentes en las pestañas adyacentes del editor. Copilot podrá ver el marco de pruebas que usa y será más probable que escriba una prueba coherente con las 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 nuestra solicitud para Copilot escribir pruebas de integración para la BankAccount clase en partes más pequeñas y fáciles de administrar. Esto ayudará a Copilot generar pruebas más precisas y pertinentes.

Solicitud:"Escriba pruebas de integración para la función deposit en la clase BankAccount. Use bocetos para simular el NotificationSystem y verifique que se llame correctamente después de un depósito".

Esta indicación realiza varias tareas importantes:

  • Ámbito: especifica pruebas de integración, para lo que se centra en la interacción entre la función deposit y NotificationSystem, 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: el aviso 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.

Sugerencia

Si Copilot genera pruebas no válidas, proporcione ejemplos de entradas y salidas para la función que desea probar. Esto ayudará a Copilot 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 generó un único caso de prueba que verifica 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. Aunque las pruebas actuales son funcionales, podemos solicitar Copilot la validación de la cobertura de código y sugerir á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

Uso de Espacios de Copilot para mejorar las sugerencias de prueba

Espacios de Copilot es una característica que permite organizar y compartir contexto específico de tareas con Copilot. Esto puede ayudar a mejorar la relevancia de las sugerencias que recibes. Al proporcionar Copilot más contexto sobre el proyecto, puede obtener mejores sugerencias de prueba.

Por ejemplo, podrías crear un espacio que incluya:

  • El módulo que estás probando (como payments.js)
  • Conjunto de pruebas actual (como payments.test.js)
  • Un informe de cobertura de ensayos o notas sobre lo que falta

En este espacio, puede hacer Copilot preguntas como:

¿Qué casos de prueba faltan en payments.test.js según la lógica de payments.js?

O:

Escribe una prueba unitaria para la lógica de reembolso en refund.js, siguiendo la estructura del conjunto de pruebas existente.

Para obtener más información sobre cómo usar Espacios de Copilot, consulte Acerca de GitHub Copilot Spaces.