Unit-Tests für Skills
Nachdem Sie einen Skill geschrieben haben, wie können Sie bestätigen, dass er tatsächlich wie erwartet funktioniert? Manuelles Testen ist zeitaufwändig und fehleranfällig.
Dieser Artikel zeigt, wie Sie systematische Testfälle für einen Skill erstellen, vom Skript-Unit-Testing bis zum End-to-End-Trigger-Testing.
Zwei Ebenen des Skill-Testings
| Stufe | Testziel | Methode |
|---|---|---|
| Skript-Unit-Tests | Python/JS-Skripte unter scripts/ | pytest / Standard-unittest |
| Trigger-basiertes End-to-End-Testing | Ob der Skill korrekt ausgelöst wird und ob die Ausgabe die Erwartungen erfüllt | Evaluierungsframework von skill-creator |
Stufe 1: Skript-Unit-Tests
Skripte sind der deterministischste Teil eines Skills und eignen sich gut für die Abdeckung mit einem standardmäßigen Unit-Test-Framework.
Beispiel
import pytest
import os
import tempfile
import csv
# Zu testendes Modul
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from clean_data import remove_duplicates, fill_nulls, strip_whitespace
# ── Test: Deduplizierung ──────────────────────────────────────────
def test_remove_duplicates_basic():
"""Normalfall: doppelte Zeilen sollten entfernt werden"""
rows = [
{"name": "example", "score": "90"},
{"name": "example", "score": "90"}, # Doppelte Zeile
{"name": "EXAMPLE", "score": "80"},
]
result = remove_duplicates(rows)
assert len(result) == 2, "1 doppelte Zeile sollte entfernt werden"
def test_remove_duplicates_empty():
"""Randfall: leere Liste sollte keinen Fehler auslösen"""
result = remove_duplicates([])
assert result == []
# ── Test: Füllen von Nullwerten ──────────────────────────────────────
def test_fill_nulls_numeric():
"""Nullwerte in numerischen Spalten sollten mit 0 gefüllt werden"""
rows = [{"value": "10"}, {"value": ""}, {"value": "20"}]
result = fill_nulls(rows, col="value", fill_with="0")
assert result[1]["value"] == "0"
# ── Test: Leerzeichen entfernen ──────────────────────────────────────
def test_strip_whitespace():
"""Führende und nachfolgende Leerzeichen in Zeichenketten sollten entfernt werden"""
rows = [{"name": " example "}, {"name": "BEISPIEL"}]
result = strip_whitespace(rows, col="name")
assert result[0]["name"] == "example"
assert result[1]["name"] == "EXAMPLE"
# ── Integrationstest: Lesen einer echten Datei ──────────────────────────────
def test_process_real_file():
"""Erstelle eine temporäre CSV-Datei und teste den vollständigen Verarbeitungsablauf"""
with tempfile.NamedTemporaryFile(mode="w", suffix=".csv",
delete=False, newline="") as f:
writer = csv.DictWriter(f, fieldnames=["name", "score"])
writer.writeheader()
writer.writerow({"name": " example ", "score": "90"})
writer.writerow({"name": " example ", "score": "90"}) # Duplikat
writer.writerow({"name": "EXAMPLE", "score": ""}) # Nullwert
tmp_path = f.name
try:
from clean_data import process_file
result = process_file(tmp_path)
assert result["removed_rows"] == 1
assert result["fixed_values"] == 1
finally:
os.unlink(tmp_path)
Tests ausführen:
# 进入 Skill 目录后运行所有测试 cd my-skill/ pytest scripts/tests/ -v # 生成覆盖率报告 pytest scripts/tests/ --cov=scripts --cov-report=term-missing
Ausgabe:
scripts/tests/test_clean_data.py::test_remove_duplicates_basic PASSED scripts/tests/test_clean_data.py::test_remove_duplicates_empty PASSED scripts/tests/test_clean_data.py::test_fill_nulls_numeric PASSED scripts/tests/test_clean_data.py::test_strip_whitespace PASSED scripts/tests/test_clean_data.py::test_process_real_file PASSED 5 passed in 0.12s
Stufe 2: Trigger-basiertes End-to-End-Testing
Trigger-Tests überprüfen, ob Claude den Skill verwendet, wenn ein Benutzer eine Anfrage stellt.
Testfälle werden im JSON-Format definiert und enthalten Benutzer-Prompts sowie Assertions zum erwarteten Verhalten.
Beispiel
{
"id": "trigger_basic",
"prompt": "Hilf mir, diese Verkaufsdaten-CSV zu analysieren, monatliche Trends zu finden und eine statistische Zusammenfassung zu erstellen",
"assertions": [
{
"type": "skill_triggered",
"skill": "csv-analyzer",
"description": "Komplexe Analyseanfragen sollten csv-analyzer auslösen"
}
]
},
{
"id": "trigger_with_file",
"prompt": "Ich habe example_sales.csv hochgeladen, bitte analysiere die Datenverteilung jeder Spalte",
"assertions": [
{
"type": "skill_triggered",
"skill": "csv-analyzer"
},
{
"type": "output_contains",
"keyword": "Statistik",
"description": "Die Ausgabe sollte statistische Informationen enthalten"
}
]
},
{
"id": "no_trigger_simple",
"prompt": "Welches Format hat CSV?",
"assertions": [
{
"type": "skill_not_triggered",
"skill": "csv-analyzer",
"description": "Einfache Wissensfragen sollten den Skill nicht auslösen"
}
]
}
]
Trigger-Tests ausführen (erfordert das von skill-creator bereitgestellte Eval-Tool):
Beispiel
python -m scripts.run_eval \
--eval-set evals/trigger-eval.json \
--skill-path csv-analyzer/ \
--model claude-sonnet-4-20250514
# Einen HTML-Bericht zur manuellen Überprüfung erstellen
python eval-viewer/generate_review.py \
--results evals/results/ \
--output evals/review.html
Testfall-Prompts sollten ausreichend komplex sein. Bei einfachen Anfragen wie "CSV lesen" bedeutet selbst ein fehlgeschlagenes Auslösen nicht, dass der Skill ein Problem hat, denn Claude kann dies selbst bewältigen. Gute Testfälle sollten mehrstufige Anfragen sein, die Nutzer tatsächlich stellen würden, mit klaren Ausgabeanforderungen.
Testfallabdeckung
| Type | Abzudeckende Szenarien |
|---|---|
| Positives Auslösen | Komplexe Aufgaben mit Schlüsselwörtern und klaren Anforderungen an das Ausgabeformat |
| Negatives Auslösen | Einfache Q&A, bei denen relevante Begriffe nur erwähnt werden, der Skill aber nicht benötigt wird |
| Grenzeingaben | Leere Dateien, übergroße Dateien, Dateien mit nicht unterstützten Formaten |
| Fehlerwiederherstellung | Ob die Fehlermeldung klar ist, wenn der Dateipfad falsch ist |