Skip to main content

Schreiben von Tests mit GitHub Copilot

Verwende Copilot, um Komponenten- und Integrationstests zu generieren und zu einer besseren Codequalität beizutragen.

Einführung

GitHub Copilot kann dir helfen, Tests schneller zu entwickeln und die Produktivität zu verbessern. In diesem Artikel wird veranschaulicht, wie du mit Copilot Komponenten- und Integrationstests schreibst. Während sich Copilot besonders zum Generieren von Tests für grundlegende Funktionen eignet, erfordern komplexe Szenarios detailliertere Prompts und Strategien. Dieser Artikel enthält praktische Beispiele dafür, wie mit Copilot Aufgaben aufgeschlüsselt und die Codekorrektheit überprüft werden kann.

Voraussetzungen

Zunächst benötigst du jedoch Folgendes:

Schreiben von Komponententests mit Copilot Chat

In diesem Abschnitt erfährst du, wie du mit GitHub Copilot Chat Komponententests für eine Python-Klasse generierst. In diesem Beispiel wird veranschaulicht, wie du mit Copilot Komponententests für eine Klasse wie BankAccount erstellst. Außerdem erfährst du, wie du Copilot aufforderst, Tests zu generieren, diese auszuführen und die Ergebnisse zu überprüfen.

Beispielklasse: BankAccount

Angenommen, du hast eine Klasse BankAccount, die Methoden zum Einzahlen, Auszahlen und Abrufen des Kontostands enthält. Erstelle eine neue Datei bank_account.py in einem GitHub-Repository, und füge die folgende BankAccount-Klasse in Python hinzu.

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

Generieren von Komponententests mit Copilot Chat

Du kannst Copilot dazu auffordern, Tests zu schreiben, die verschiedene Szenarios abdecken. Achte auf spezifische Testanforderungen, um die besten Ergebnisse zu erzielen. Im Folgenden findest du einen Beispielprompt, mit dem du Komponententests für die BankAccount-Klasse generieren kannst.

Prompt: „Schreibe eine umfassende Sammlung von Komponententests für die BankAccount()-Funktion in Python. Schreibe mehrere Testmethoden, die viele verschiedene Szenarios abdecken, einschließlich Grenzfällen, Ausnahmebehandlung und Datenüberprüfung.“

Wichtige Aspekte dieses Prompts:

  • Geltungsbereich: Er fragt explizit nach einer umfassenden Sammlung von Komponententests, damit Copilot mehr als nur grundlegende Tests generiert und viele verschiedene potenzielle Anwendungsfälle abdeckt.
  • Szenarios: Der Prompt gibt an, dass die Tests Folgendes berücksichtigen sollen:
    • Grenzfälle: Es wird auf Grenzbedingungen (z. B. negative Werte für Ein- oder Auszahlungen und Auszahlungen, die den verfügbaren Saldo übersteigen) getestet.
    • Ausnahmebehandlung: Es wird auf Fehler getestet, die ausgelöst werden sollen, wenn bestimmte ungültige Aktionen auftreten (z. B. Auszahlungen, die den verfügbaren Saldo übersteigen und das Einzahlen negativer Beträge).
    • Datenüberprüfung: Es wird sichergestellt, dass Einzahlungen von den Methoden korrekt überprüft und verarbeitet werden.

Die Antwort von Copilot wird der folgenden ähneln.

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()

Ausführen der Testfälle

Die von Copilot generierte Testsammlung enthält einen Testfall für jedes Szenario, das im Prompt angegeben ist. Jede Funktion in der BankAccount-Klasse wird auf verschiedene Bedingungen getestet, einschließlich gültiger Vorgänge wie Ein- und Auszahlungen sowie Grenzfälle wie negative Beträge und versuchte Auszahlungen, die den verfügbaren Saldo übersteigen.

Nachdem Copilot die Testsammlung zu deiner Zufriedenheit generiert hat, füge den Code in eine neue test_bank_account.py-Datei ein. Du kannst fragen, wie die Tests ausgeführt werden.

Prompt: „Wie kann ich diese Komponententests in Python mit dem Framework „unittest“ ausführen?“

Copilot gibt dir den folgenden Bash-Befehl zurück.

python -m unittest test_bank_account.py

Nach dem Ausführen der Tests wird dir die Ausgabe im Terminal oder in der IDE angezeigt. Wenn alle Tests erfolgreich abgeschlossen werden, kannst du sicher sein, dass deine BankAccount-Klasse erwartungsgemäß funktioniert.

Slash-Befehl

Darüber hinaus kannst du Copilot auffordern, eine vollständige Sammlung von Komponententests mit dem Slash-Befehl /tests zu schreiben. Stelle sicher, dass die Datei in der aktuellen Registerkarte deiner IDE geöffnet ist, und Copilot wird Komponententests für diese Datei generieren. Die Tests, die Copilot generiert, decken unter Umständen nicht alle Szenarios ab. Daher solltest du den generierten Code immer überprüfen und zusätzliche Tests hinzufügen, die ggf. erforderlich sind.

Tip

Wenn du Copilot bittest, Tests für eine Codedatei zu schreiben, die nicht von Komponententests abgedeckt wird, kannst du nützlichen Kontext für Copilot bereitstellen, indem du mindestens eine bestehende Testdatei in benachbarten Registerkarten in deinem Editor öffnest. Copilot erkennt das verwendete Testframework und kann mit größerer Wahrscheinlichkeit einen Test schreiben, der zu deinen bestehenden Tests passt.

Copilot generiert eine Komponententestsammlung wie die folgende.

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)

Schreiben von Integrationstests mit Copilot

Integrationstests sind unerlässlich, um sicherzustellen, dass die verschiedenen Komponenten deines Systems zusammen ordnungsgemäß funktionieren. In diesem Abschnitt wird die BankAccount-Klasse um Interaktionen mit einem externen Dienst NotificationSystem erweitert. Außerdem wird Systemverhalten mit Modelltests getestet, ohne dass echte Verbindungen erforderlich sind. Ziel der Integrationstests ist es, die Interaktion zwischen der BankAccount-Klasse und den NotificationSystem-Diensten zu prüfen und sicherzustellen, dass sie ordnungsgemäß zusammenarbeiten.

Beispielklasse: BankAccount mit Benachrichtigungsdiensten

Aktualisiere die BankAccount-Klasse, sodass Interaktionen mit einem externen Dienst einbezogen werden, z. B. ein NotificationSystem-Dienst, der Benachrichtigungen an Benutzer sendet. NotificationSystem stellt die Integration dar, die getestet werden muss.

Aktualisiere die BankAccount-Klasse in der bank_account.py-Datei mit dem folgenden Codeschnipsel.

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

In diesem Fall sollte die Anforderung für Copilot, Integrationstests für die BankAccount-Klasse zu schreiben, in kleinere, besser handhabbare Einheiten aufgeteilt werden. So kann Copilot genauere und relevantere Tests generieren.

Prompt: „Schreibe Integrationstests für die deposit-Funktion in der BankAccount-Klasse. Verwende Modelltests, um NotificationSystem zu simulieren und zu überprüfen, ob der Dienst nach einer Einzahlung richtig aufgerufen wird.“

Wichtige Aspekte dieses Prompts:

  • Geltungsbereich: Er fordert nicht nur Komponententests, sondern Integrationstests, die sich auf die Interaktion zwischen der deposit-Funktion und NotificationSystem konzentrieren.
  • Modelltests: Er fordert explizit die Verwendung von Modelltests, um NotificationSystem zu simulieren und sicherzustellen, dass die Interaktion mit externen Systemen getestet wird, ohne dass diese tatsächlich implementiert werden müssen.
  • Überprüfung: Der Prompt fordert, dass das korrekte Aufrufen der NotificationSystem-Funktion nach einer Einzahlung überprüft werden muss, um sicherzustellen, dass die Integration zwischen den Komponenten erwartungsgemäß funktioniert.
  • Genauigkeit: Der Prompt gibt die zu testende Methode (deposit) und die zu testende Klasse (BankAccount) genau vor.

Tip

Wenn Copilot ungültige Tests erzeugt, stelle Beispiele für Ein- und Ausgaben der Funktion bereit, die du testen möchtest. Dies hilft Copilot. das erwartete Verhalten der Funktion auszuwerten.

Copilot generiert eine Testsammlung wie die folgende.

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()

Füge den generierten Code in eine neue test_bank_account_integration.py-Datei ein.

Verbessern der Testfälle

Der oben genannte Prompt hat einen einzigen Testfall generiert, der überprüft, ob NotificationSystem nach einer gültigen Einzahlung aufgerufen wird. Der Prompt deckt jedoch keine Fälle ab, in denen während der Einzahlung ein Fehler ausgelöst wird. In diesen Szenarios sollte NotificationSystem nicht aufgerufen werden. Daher sollte ein Testfall hinzugefügt werden, der ungültige Einzahlungen verarbeitet und sicherstellt, dass das Benachrichtigungssystem nicht ausgelöst wird.

Prompt: „Füge einen Testfall für ungültige Einzahlungsbeträge hinzu, um sicherzustellen, dass die Funktion die richtigen Ausnahmen auslöst und dass NotificationService nicht aufgerufen wird.“

Copilot generiert einen Testfall wie den folgenden.

    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()

Fragen zu Verbesserungspotenzialen

Da du nun Testfälle geschrieben hast, um die Integrationsfunktionalität für Einzahlungen zu überprüfen, ist dies eine großartige Gelegenheit, nach Verbesserungen der Testsammlung zu suchen. Auch wenn die aktuellen Tests ihren Zweck erfüllen, kannst du Copilot auffordern, die Code Coverage zu testen und Verbesserungen vorzuschlagen.

Prompt: „Welche zusätzlichen Tests sollten einbezogen werden, um die Integration zwischen der BankAccount-Klasse und NotificationSystem vollständig abzudecken?“

Copilot diese Frage zu stellen, kann dir helfen, fehlende Testfälle zu ermitteln, die möglicherweise übersehen wurden. Im vorliegenden Beispiel wurden zwar gültige und ungültige Einzahlungen getestet, jedoch nicht die Auszahlungsfunktion.

Copilot generiert eine aktualisierte Testsammlung wie die folgende.

Klicke hier, um das vollständige generierte Codebeispiel anzuzeigen.
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()

Wenn du mit der von Copilot generierten Testsammlung zufrieden bist, führe die Tests mit dem folgenden Befehl aus, um die Ergebnisse zu überprüfen.

python -m unittest test_bank_account_integration.py