werkcrappit computes an experimental Mutation-CRAP score per JVM method from the JaCoCo complexity of the original class files and the PIT mutation score. The specification is docs/pitest-mutation-crap-coding-agent-auftrag.md. This commit holds only the skeleton: Gradle 9.8.0 wrapper, Java 21 toolchain with JUnit Jupiter and AssertJ, the modules werkcrappit-pit and werkcrappit-gradle-plugin (with a no-op plugin class), README, AGENTS.md and LICENSE. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
60 KiB
Implementierungsauftrag: Mutation-CRAP als eigenständiges PIT-Plugin
Adressat: Codex oder ein anderer Coding Agent
Arbeitsname: pitest-mutation-crap
Stand: 1. Oktober 2026
Ziel: Ein funktionierendes, getestetes Plugin implementieren, nicht nur einen Entwurf erstellen.
1. Auftrag und Motivation
Implementiere in einem eigenständigen Git-Repository ein Reporting-Plugin für PIT / PITest, das für JVM-Methoden einen mutationsbasierten CRAP-Wert berechnet. Die Integration und die unterstützten Anwender-Workflows erfolgen ausschließlich über Gradle. Java hat höchste Priorität; Kotlin/JVM ist ein nachgeordnetes Kompatibilitätsziel.
Gemeint ist PIT für Mutation Testing auf der JVM, nicht das Python-Testframework pytest.
Die Motivation: Wir möchten Methoden identifizieren, bei denen hohe strukturelle Komplexität mit einer geringen Fähigkeit der Tests zusammenkommt, die von PIT erzeugten Verhaltensänderungen zu erkennen. Dafür kombinieren wir Mutationsergebnisse aus PIT mit der zyklomatischen Komplexität derselben Methode aus JaCoCo Core.
Die vorgeschlagene Mutation-CRAP-Formel ist für dieses Projekt eine experimentelle, ausdrücklich definierte Metrik. Behaupte weder, sie sei ein offizieller CRAP-Nachfolger, noch, ihre Überlegenheit oder ein bestimmter Grenzwert seien empirisch bewiesen. Die erste Version soll die praktische Erprobung ermöglichen. Eine spätere Evaluation darf die Definition ändern, aber nur mit neuer Metrikversion und dokumentierter Entscheidung.
Arbeite in überprüfbaren vertikalen Schritten: zuerst ein echter Gradle/PIT-Durchstich, anschließend Berechnung, Reports und optional einschaltbare Build-Prüfung. Erzeuge nicht zuerst eine umfangreiche Architektur ohne funktionierenden Integrationsnachweis.
2. Verbindlicher Rahmen und Abgrenzung
2.1 Bereits festgelegte Anforderungen
- Eigenständiges Repository und unabhängig versioniertes PIT-Plugin; kein Fork und keine Änderung am PIT-Core als Voraussetzung.
- Gradle als einziges unterstütztes Buildsystem. Kein Maven-Plugin, kein Maven-Beispielprojekt und keine Maven-Testmatrix erforderlich.
- JVM als Zielplattform, Java als verbindliches erstes Sprachziel. Kotlin/JVM nach Möglichkeit unterstützen; andere JVM-Sprachen nicht pauschal als unterstützt bewerben.
- Mutationsergebnisse direkt aus der PIT-Listener-/Plugin-Schnittstelle beziehen, nicht aus einem nachgeschalteten Import von
mutations.xml. - Komplexität mit JaCoCo Core aus den originalen, unmutierten Class-Dateien ermitteln.
- Beide Datenquellen über Klassenname, JVM-Methodenname und JVM-Methodendeskriptor verbinden.
- Kein eigener Coverage-Testlauf, kein JaCoCo-Agent und keine JaCoCo-
.exec-Datei als Voraussetzung für diese Metrik. - Kein zusätzlicher PIT-Lauf, den die Metrikberechnung auslöst. PITs eigene Testausführung und vorbereitende Coverage-Ermittlung bleiben davon unberührt.
- Maschinenlesbarer Report und ein in CI gut lesbarer Report; zusätzlich eine optional einschaltbare Grenzwertprüfung.
- Gradle-Multi-Project-Builds berücksichtigen, zunächst mit getrennten Auswertungen pro Teilprojekt beziehungsweise PIT-Analyse.
- Explizite Ausschlüsse ermöglichen. Statusbehandlung, fehlende Daten und Aussagegrenzen müssen nachvollziehbar sein.
Ein PIT-Plugin ist hier ein von PIT geladenes JAR. Das vorhandene Gradle-PIT-Plugin kann solche Abhängigkeiten laden; ein eigenes Gradle-Plugin ist deshalb nicht die Voraussetzung für die Metrikimplementierung. [S1][S9]
2.2 Nicht Bestandteil der ersten Version
Keine eigene Mutation Engine, keine neuen Mutatoren, keine automatische Erkennung äquivalenter Mutanten und keine eigene Implementierung zyklomatischer Komplexität. Ebenso wenig: eigenständige CLI, zentraler Server, Webdienst, SonarQube-Integration, IDE-Plugin, Git-Hosting-Anbindung oder automatische PR-Kommentare.
Ein projektübergreifender Gesamtreport, Baselines und ein Gate ausschließlich für geänderte Methoden sind mögliche spätere Erweiterungen. Implementiere sie nicht vor dem funktionierenden Java-MVP. Addiere nicht einfach Methodenwerte zu einer angeblich etablierten „Projekt-CRAP-Metrik“.
Kein kostenpflichtiges Add-on darf für den Java-MVP erforderlich sein. Insbesondere soll das optionale Kotlin-Ziel den Java-MVP weder blockieren noch stillschweigend eine zusätzliche Lizenz voraussetzen.
Die Veröffentlichung eines JARs in einem Maven-kompatiblen Artefakt-Repository ist von Maven als Buildsystem zu unterscheiden und ist zulässig. Eine Veröffentlichung auf externen Diensten oder das Anlegen eines Remote-Repositories gehört ohne gesonderten Auftrag nicht zur Ausführung dieses Arbeitsauftrags.
2.3 Konkrete Arbeitsentscheidungen dieser Spezifikation
Die folgenden Details sind bewusst gewählte Startentscheidungen, nicht bereits vorher getroffene Anwenderentscheidungen:
- Name des PIT-Ausgabeformats:
MUTATION_CRAP. - Metrikkennung:
mutation-crap; initiale Metrikversion:1. - Statuspolicy:
strict-v1, definiert in Abschnitt 5. - Primäre Ausgabe: JSON; lesbare Ausgabe: Markdown plus kurze Konsolenzusammenfassung.
- Standardmodus: Report erstellen, aber keinen Build allein wegen eines hohen Scores abbrechen.
- Ein aktiviertes Score-Gate verlangt einen ausdrücklich konfigurierten Grenzwert. 30 ist nur ein Testbeispiel, keine Empfehlung und kein impliziter Standardwert.
- Neue Produktionslogik vorzugsweise in Java, Buildskripte vorzugsweise als Gradle Kotlin DSL. Java-Code mit
varbeziehungsweisefinal var, wo dies mit der gewählten Java-Version und der Lesbarkeit vereinbar ist. - Tests mit JUnit Jupiter und AssertJ; Mockito nur dort, wo ein Testdouble sinnvoller ist als ein kleines reales Fixture.
Dokumentiere notwendige Änderungen dieser Arbeitsentscheidungen. Ändere nicht unbemerkt die Metrikformel, ihre Statuspolicy oder die verbindlichen Rahmenbedingungen.
3. Zuerst die technische Machbarkeit nachweisen
3.1 Was bereits belegt ist — und was nicht
PIT bietet MutationResultListenerFactory als Erweiterungspunkt für Ergebnis-Plugins. Listener werden als Service registriert; ein Ausgabeformat kann über outputFormats aktiviert werden. Die dokumentierten Erweiterungsschnittstellen sind allerdings nicht als über alle Versionen unveränderlich zugesagt. [S1][S2]
Der untersuchte Listener besitzt runStart(), handleMutationResult(ClassMutationResults) und runEnd(). Eine Klasse kann auf mehrere ClassMutationResults-Instanzen verteilt sein. Daher darf ein einzelner Callback nicht als vollständiges Klassenergebnis behandelt werden. [S3]
JaCoCo stellt die Analyse von Class-Dateien und Methodencounter bereit. Der Analyzer behandelt auch Klassen ohne vorhandene Ausführungsdaten; die statische Komplexität ist damit ohne zusätzlichen Coverage-Lauf ableitbar. Für die Berechnung ist der gesamte Complexity-Counter zu verwenden, nicht dessen abgedeckter Anteil. [S6][S7]
Noch kein ausgeführter Integrationsnachweis liegt dafür vor, dass die hier geplante Kombination mit einer konkreten Versionenauswahl in Gradle funktioniert. Diesen Nachweis musst du als ersten Implementierungsschritt liefern.
3.2 Versionen reproduzierbar auswählen
Prüfe die zum Implementierungszeitpunkt verfügbaren stabilen Versionen von PIT, info.solidsoft.pitest, JaCoCo, dem PIT-JUnit-5-Plugin, JUnit und Gradle. Wähle eine tatsächlich kompatible Kombination und pinne sie einschließlich Gradle Wrapper und Java Toolchain.
Unterscheide Build-JDK, Laufzeit-JDK des PIT-Prozesses und Bytecode-Zielversion. Behaupte keine Kompatibilität allein aufgrund einer Abhängigkeitsauflösung oder eines erfolgreichen Kompiliervorgangs.
Die Quellen am Ende dieses Auftrags verweisen teilweise auf master oder trunk. Sie dienen zur Orientierung; überprüfe die benötigten APIs am gewählten Release-Tag oder an dessen veröffentlichten Artefakten. Keine dynamischen Versionen oder Snapshot-Abhängigkeiten für den regulären Build. Dokumentiere die erste erfolgreich getestete Versionenkombination in docs/compatibility.md.
3.3 Minimaler Durchstich
Baue ein kleines Java-Anwenderprojekt mit einer verzweigenden Methode und JUnit-Jupiter-Tests. Lade das wirklich gebaute Plugin-JAR über den PIT-Tool-Classpath. Ein Test, der nur direkt die Listenerklasse instanziiert, reicht dafür nicht.
Der Durchstich muss nachweisen, dass:
- PIT die Factory aus dem ausgelieferten Service-Eintrag findet und
MUTATION_CRAPaktiviert. - Mutationsergebnisse mit vollständiger Methodenidentität ankommen.
- das Plugin genau die zugehörigen originalen Class-Dateien erhält und JaCoCo sie analysiert.
- beide Datenquellen eindeutig zusammengeführt werden und ein Report im vorgesehenen Ausgabeverzeichnis entsteht.
- normale PIT-Ausgaben parallel möglich bleiben und keine zusätzliche Testausführung für JaCoCo stattfindet.
Verwende zunächst eine einzige unterstützte Versionenkombination. Erst danach eine kleine, begründete Kompatibilitätsmatrix ergänzen.
3.4 Bytecodezugriff nicht voraussetzen
ListenerArguments.getLocator() ist kein Bytecodezugriff: Es liefert im untersuchten Stand einen SourceLocator, der Quelltext als Reader sucht. Über diese API dürfen keine .class-Bytes erwartet werden. [S4]
Der untersuchte Stand bietet dagegen über ListenerArguments.data() Zugriff auf ReportOptions; dort existieren unter anderem Classpath- und Codepath-Informationen. Das ist ein Kandidat für einen gekapselten Adapter, noch keine Zusage für jede PIT-Version. [S4][S5]
Prüfe in dieser Reihenfolge:
- Gibt es in der gewählten PIT-Version einen geeigneten verfügbaren Bytecode-/Classpath-Zugang für das Ergebnis-Plugin?
- Lassen sich ansonsten die im PIT-Aufruf vorhandenen Classpath- und Codepath-Daten verwenden, ohne PITs private Felder oder Reflection-Hacks zu benötigen?
- Falls nötig: Lass eine dünne Gradle-Integration explizit die kompilierten Produktionsausgaben und erforderlichen JAR-Pfade an das Plugin übergeben. Leite diese aus dem tatsächlichen Buildmodell ab; hardcodiere keine
build/classes/java/main-Annahme.
Lade die Zielklassen nicht per Reflection, um ihre Bytes zu untersuchen. Analysiere keine mutierten Klassen und keine zusätzlich für Coverage instrumentierte Fassung. Erhalte Classpath-Reihenfolge, Klassenfilter und die eindeutige Herkunft der Bytes.
Ein ergänzender PIT-Erweiterungspunkt ist nur nach einem konkreten Nachweis der Notwendigkeit zu erwägen. Er darf keine versteckte Pflichtänderung am PIT-Core einführen. Ist keine tragfähige Lösung innerhalb des Rahmens möglich, liefere den reproduzierbaren Blocker, den funktionsfähigen Teil und eine kleine begründete Architekturentscheidung. Wechsle nicht stillschweigend zu einem XML-Tool und verkaufe es als das bestellte Plugin.
Halte das Ergebnis dieses Durchstichs in docs/adr/0001-pit-integration.md fest: Versionen, API-Zugang, Packaging, getesteter Ablauf und verbleibende Risiken.
4. Architektur und Datenermittlung
4.1 Verantwortlichkeiten
Trenne fachlich mindestens diese Verantwortlichkeiten, ohne für jede davon ein eigenes Gradle-Subprojekt anzulegen:
| Baustein | Aufgabe |
|---|---|
| Metrikkern | Methodenidentitäten, Statuspolicy, Berechnung und Gate-Entscheidung; unabhängig von PIT- und Gradle-Typen |
| JaCoCo-Adapter | Originalbytecode analysieren und Methodenkomplexität liefern |
| PIT-Adapter | Factory/Listener, Konfiguration, Klassenherkunft, Ergebnisaggregation und Lebenszyklus |
| Reportausgabe | Versioniertes JSON, Markdown, Zusammenfassung |
| Gradle-Anbindung | Plugin laden, Konfiguration durchreichen und optional ein Gate nach erfolgreichem PIT-Lauf ausführen |
Ein oder zwei Produktionsmodule reichen zunächst; ergänze ein dünnes Gradle-Modul nur bei tatsächlichem Nutzen. PIT-spezifische und JaCoCo-spezifische Datentypen sollen nicht durch den gesamten Metrikkern weitergereicht werden.
4.2 Herkunft der Komplexität
Verwende die öffentliche JaCoCo-Analyse-API mit einem leeren ExecutionDataStore, einem Analyzer und einem geeigneten Coverage-Visitor, beispielsweise CoverageBuilder. Lies für jede auswertbare IMethodCoverage die Gesamtkomplexität über getComplexityCounter().getTotalCount() aus. Der Methodenname und getDesc() dienen der Zuordnung. [S6][S7]
Definiere die Größe ausdrücklich als JaCoCo-Zyklomatik, nicht als Cognitive Complexity und nicht als beliebige quelltextbasierte McCabe-Implementierung. JaCoCo verwendet B - D + 1; Exception-Handling-Kanten erhöhen in dieser Definition die Komplexität nicht. Konstruktoren und statische Initialisierer können als JVM-Methoden vorkommen. [S8]
JaCoCos Filter für compilererzeugten Code beeinflussen die Analyse. Übernimm keine eigenen, scheinbar präziseren Quelltextwerte und implementiere keinen alternativen Kontrollflussgraphen. Weise die JaCoCo-Version im Report aus. Insbesondere bei Kotlin können sich Filter und damit Ergebnisse zwischen Versionen verändern. [S12]
Analysiere jede benötigte Klassenversion höchstens einmal pro PIT-Lauf. Vermeide globale statische Caches über mehrere Builds hinweg. Verwende für die Herkunftsprüfung einen Hash der analysierten Class-Datei oder eine gleichwertig belastbare Identität.
4.3 Methodenidentität
Die kanonische Identität innerhalb einer Analyse besteht aus:
- JVM-Klassenname in einer dokumentierten Normalform;
- JVM-Methodenname, einschließlich spezieller Namen wie
<init>; - vollständigem JVM-Descriptor, einschließlich Rückgabetyp.
Normalisiere Klassenbezeichnungen mit Punkt beziehungsweise Schrägstrich an genau einer Adaptergrenze. Innere Klassen behalten ihre JVM-Identität. Verwende weder Quelltextzeilen noch den Methodennamen allein als Schlüssel. PITs Location enthält diese identifizierenden Informationen. [S10]
Außerhalb einer einzelnen Analyse ergänze die Identität um den Analysekontext, etwa Gradle-Projektpfad plus PIT-Taskpfad. Gleiche Klassen- und Methodennamen aus unterschiedlichen Modulen dürfen nicht zusammenfallen.
Quelltextdatei, Zeilennummern und eine lesbare Signatur sind optionale Anzeigeinformationen. Ihr Fehlen darf einen sonst möglichen JVM-Join nicht verhindern.
4.4 Auswertungsscope und Methoden ohne Ergebnisse
Bewerte ausschließlich den ausdrücklich gewählten Produktionscode. Scanne nicht den vollständigen Test- und Abhängigkeitsclasspath als zu bewertende Anwendung.
Erstelle für diesen Scope soweit möglich ein Methodeninventar aus den originalen Produktionsklassen. Respektiere dabei die Klassen-/Methodenauswahl des PIT-Laufs und die eigenen dokumentierten Ausschlüsse. Ein Listener allein liefert keine verlässliche vollständige Liste sämtlicher Methoden des Projekts; die Vollständigkeit muss aus der Klassenauswahl und deren Analyse nachgewiesen werden.
Unterscheide unbedingt:
- Methode mit einem oder mehreren
NO_COVERAGE-Mutanten; - Methode ohne Mutationsergebnisse;
- Methode mit ausschließlich aus der Scoreberechnung ausgeschlossenen Mutanten;
- nicht eindeutig zuordenbare oder von JaCoCo nicht auswertbare Methode.
Fehlende Mutanten sind keine 100-Prozent-Testwirksamkeit und auch kein Mutation Score von null. Ist eine Methode im Inventar, hat aber keine Ergebnisse, bekommt sie einen nicht numerischen Auswertungszustand. Behaupte ohne weitere Informationen nicht, ob der Grund ein PIT-Filter, fehlende Mutationsmöglichkeiten oder eine begrenzte Analyse war.
Wenn eine vollständige Inventarisierung nicht zuverlässig möglich ist, kennzeichne den Scope als unvollständig. Ein Report darf dann nicht vorgeben, alle Methoden des Projekts zu bewerten. Das aktivierte strikte Gate muss einen unerwartet unvollständigen Scope als Analyseproblem behandeln.
Ein explizit auf bestimmte Klassen beschränkter, innerhalb dieser Auswahl vollständiger Lauf ist dagegen kein Fehler. Seine begrenzte Aussage muss im Report sichtbar sein.
4.5 Ergebnisaggregation und Nebenwirkungen
Sammle Ergebnisse bis zum Ende des Laufs. Mehrere Ergebnisblöcke einer Klasse müssen zusammengeführt werden; die Reihenfolge darf den Report nicht beeinflussen. [S3]
Identische erneut gelieferte Mutationsergebnisse anhand der PIT-Mutantenidentität nicht doppelt zählen. Widersprüchliche Endergebnisse derselben Identität sind ein Analyseproblem, kein Anlass für „last write wins“. Verwechsle vorläufige interne Zustände nicht mit zusätzlichen Mutanten.
Prüfe das tatsächliche Threading-Modell der gewählten API. Verwende einen pro Lauf isolierten Zustand und sichere ihn bei Bedarf gegen nebenläufige Aufrufe. Speichere möglichst Identitäten und Zähler statt unnötig vollständiger PIT-Objektgraphen.
Das Plugin ist ein Beobachter: Es darf weder Mutanten herausfiltern noch deren Status verändern, Testauswahl überschreiben oder PITs Mutator-Konfiguration austauschen. Eigene Reporting-Ausschlüsse wirken nur auf diese Metrik.
4.6 Packaging
Registriere die Factory korrekt unter META-INF/services/org.pitest.mutationtest.MutationResultListenerFactory. Stelle sicher, dass die zur Laufzeit benötigten Abhängigkeiten mitgeladen werden. PIT unterscheidet Tool-Classpath und Classpath des untersuchten Projekts. [S1]
Bündle keine zweite PIT-Distribution in das Plugin. Prüfe mögliche Versionskonflikte insbesondere mit ASM, JaCoCo und anderen PIT-Plugins. Shading/Relocation nur bei nachgewiesenem Bedarf; Service-Einträge und Lizenzhinweise müssen erhalten bleiben.
Ein Packaging-Test muss das veröffentlichungsfähige JAR samt Metadaten und Laufzeitabhängigkeiten aus einem isolierten lokalen Test-Repository konsumieren. Verlasse dich nicht auf zufällige Klassen aus der Entwicklungsumgebung.
5. Exakte Definition der ersten Metrikversion
5.1 Formel
Für eine auswertbare Methode m gelten:
C(m) = zyklomatische Komplexität nach JaCoCo, mindestens 1
K(m) = Anzahl der Mutanten mit Status KILLED
S(m) = Anzahl der Mutanten mit Status SURVIVED
N(m) = Anzahl der Mutanten mit Status NO_COVERAGE
E(m) = K(m) + S(m) + N(m)
Wenn E(m) > 0 ist und keine in Abschnitt 5.2 definierte Uneindeutigkeit vorliegt:
MS(m) = K(m) / E(m)
mutationCrap(m) = C(m)^2 × (1 − MS(m))^3 + C(m)
MS ist ein Verhältnis im Intervall [0, 1], kein Prozentwert im Intervall [0, 100]. Verwende keine ganzzahlige Division. Berechne mit hinreichender numerischer Genauigkeit; runde erst für die Anzeige, nicht vor der Grenzwertprüfung.
Definiere eine nachvollziehbare numerische Vergleichsstrategie. Ein mathematisch auf dem Grenzwert liegendes Ergebnis darf nicht aufgrund eines vermeidbaren Gleitkommaartefakts durchfallen. Da die Eingangszähler ganzzahlig sind, ist auch ein Vergleich anhand der rationalen Formel möglich. Du musst dafür kein allgemeines Numerikframework bauen.
| C | K | S | N | MS | Mutation-CRAP |
|---|---|---|---|---|---|
| 1 | 0 | 1 | 0 | 0 | 2 |
| 10 | 0 | 10 | 0 | 0 | 110 |
| 10 | 5 | 5 | 0 | 0.5 | 22.5 |
| 10 | 8 | 2 | 0 | 0.8 | 10.8 |
| 10 | 10 | 0 | 0 | 1 | 10 |
| 30 | 10 | 0 | 0 | 1 | 30 |
Diese Zahlen sind berechnete Testorakel, keine empirischen Qualitätsklassen. Bei vollständiger Mutantenerkennung entspricht der Score weiterhin der Komplexität. Eine Methode mit C > Grenzwert kann das entsprechende Gate daher auch mit MS = 1 nicht bestehen.
5.2 Statuspolicy strict-v1
Verwende eine explizite Zuordnung. Übernimm nicht pauschal DetectionStatus.isDetected() als Zähler: Im untersuchten PIT-Stand ist dieses Flag auch für verschiedene Fehlerzustände und EQUIVALENT wahr. [S11]
| PIT-Status | Behandlung für diese Metrik |
|---|---|
KILLED |
In Zähler und Nenner |
SURVIVED |
Nur im Nenner |
NO_COVERAGE |
Nur im Nenner; nicht herausfiltern |
EQUIVALENT, sofern die PIT-Version diesen Status liefert |
Weder Zähler noch Nenner; separat ausweisen |
NON_VIABLE |
Weder Zähler noch Nenner; separat ausweisen |
TIMED_OUT |
Uneindeutig: betroffene Methode erhält keinen regulären Score |
MEMORY_ERROR, RUN_ERROR |
Uneindeutig: betroffene Methode erhält keinen regulären Score |
NOT_STARTED, STARTED, falls am Ende noch vorhanden |
Unvollständig: betroffene Methode erhält keinen regulären Score |
| Jeder nicht ausdrücklich unterstützte Status | Nicht stillschweigend werten; als Analyseproblem ausweisen |
Diese konservative Policy ist eine Projektentscheidung, keine Behauptung über PITs eigene Scoredefinition. Ein Timeout kann durch eine wirksam erkannte Endlosschleife entstehen, ist hier aber ohne zusätzliche Evidenz nicht gleichbedeutend mit einem durch eine Assertion getöteten Mutanten. [S11]
Ein uneindeutiger Endstatus macht den regulären Score der betroffenen Methode ungültig, selbst wenn es daneben genügend KILLED- oder SURVIVED-Ergebnisse gibt. Andernfalls könnte das Weglassen problematischer Ergebnisse den Score verbessern. Die Rohzähler bleiben sichtbar.
Erfinde keine automatische Äquivalenzerkennung. Nur tatsächlich so klassifizierte Ergebnisse dürfen unter EQUIVALENT erscheinen. Das Vorhandensein dieses Status in einer API bedeutet nicht, dass PIT jeden äquivalenten Mutanten erkennen kann.
Halte Statusmapping und Formel getrennt und teste sie separat. Eine spätere Timeout-Policy darf ergänzt werden, benötigt aber eine andere Policykennung und darf nicht unbemerkt unter strict-v1 laufen.
5.3 Nicht numerische Auswertungszustände
Verwende mindestens unterscheidbare Zustände mit maschinenlesbaren Gründen:
SCORED: gültige Komplexität und auswertbare Mutanten vorhanden.NO_MUTATION_RESULTS: Methode im Inventar, aber ohne Mutationsergebnisse.NO_ELIGIBLE_MUTANTS: Ergebnisse vorhanden, aber nach der Policy kein Mutant im Nenner.INCONCLUSIVE: uneindeutige, unvollständige, unbekannte oder widersprüchliche Ergebnisse.COMPLEXITY_UNAVAILABLE: Ergebnis vorhanden, aber Komplexität beziehungsweise eindeutige Zuordnung fehlt.EXCLUDED: ausdrücklich von dieser Auswertung ausgenommen.
Nicht auswertbare Werte sind im JSON null, nicht 0, nicht 100 %, nicht NaN und nicht Infinity. Eine gültig ermittelte Komplexität darf auch bei fehlendem Mutation Score ausgewiesen werden.
Abstrakte und native Methoden ohne analysierbaren Methodenrumpf werden nicht mit einer erfundenen Komplexität bewertet. Für compilererzeugte oder durch JaCoCo gefilterte Methoden darf ein begründeter Ausschluss verwendet werden, sofern er nachgewiesen und im Report erkennbar ist. Eine beliebige fehlgeschlagene Zuordnung darf nicht als „generated“ wegdefiniert werden.
5.4 Aussagegrenzen
Zeige neben dem Score immer Komplexität, Mutation Score und Mutantenzahl. Ein Verhältnis aus einem einzelnen Mutanten ist keine gleich starke Evidenz wie dasselbe Verhältnis aus vielen Mutanten; die Metrik modelliert diese Unsicherheit nicht statistisch.
Vergleiche Werte nur unter Berücksichtigung von Mutatoren, Filtern, Testauswahl, Bytecode und Toolversionen. Eine Änderung dieser Bedingungen kann den Score verändern, ohne dass Produktions- oder Testcode verbessert wurde. Ein guter Score ist weder ein Korrektheitsbeweis noch ein Ersatz für Review.
Der verwendete Score schließt NO_COVERAGE ein. Er ist daher nicht einfach mit einem Wert gleichzusetzen, der nur bereits abgedeckte Mutanten betrachtet; PIT beschreibt die entsprechenden Ergebnisunterschiede in seinen Grundlagen. [S13]
6. Reports, Konfiguration und Gradle-Gate
6.1 JSON als belastbarer Vertrag
Liefere mutation-crap.json mit dokumentiertem JSON-Schema. Mindestens enthalten sein müssen:
| Ebene | Inhalte |
|---|---|
| Definition | schemaVersion, metricId, metricVersion, scorePolicyId, Kennung und Version des Komplexitätsproviders |
| Herkunft | Plugin- und PIT-Version, Analyse-/Projektkontext, relevante Eingabeidentitäten |
| Scope | ausgewählte Produktionsklassen beziehungsweise Scope-Beschreibung, Inventarvollständigkeit, Ausschlüsse |
| Konfiguration | wirksame metrikeigene Optionen; verfügbare PIT-Mutator-/Filter-/Testauswahl oder ausdrücklicher Hinweis auf fehlende Provenienz |
| Laufstatus | regulär abgeschlossener Report oder unvollständige Analyse; Diagnosegründe |
| Methode | kanonische ID, gegebenenfalls Anzeigeinformationen, Komplexität, Rohzähler je Status, Nenner, Mutation Score, Mutation-CRAP, Auswertungszustand und Gründe |
| Zusammenfassung | Anzahl bewerteter, unbewerteter, uneindeutiger und ausgeschlossener Methoden; größte Scores und gegebenenfalls Gate-Verletzungen |
Ein Konfigurationsfingerprint darf nur als vollständig bezeichnet werden, wenn die dafür relevanten Einstellungen tatsächlich bekannt sind. Dump nicht unkontrolliert sämtliche JVM-Properties oder Umgebungsvariablen; darunter könnten Zugangsdaten sein.
Sortiere Ausgaben deterministisch. Für dieselben Eingabedaten und dieselbe Konfiguration müssen die fachlichen Reportdaten identisch sein. Zeitstempel und Lauf-IDs sind, sofern vorhanden, ausdrücklich davon getrennte Metadaten.
Schreibe Reports so, dass ein abgebrochener Lauf nicht versehentlich einen alten erfolgreichen Report als seinen eigenen hinterlässt. Verwende beispielsweise ein laufbezogenes Zwischenartefakt und einen erst bei erfolgreichem Abschluss veröffentlichten finalen Report. Ein vorhandener Dateiname allein ist kein Abschlussnachweis.
6.2 Lesbare CI-Ausgabe
Liefere mutation-crap.md mit einer kurzen Zusammenfassung und einer nach absteigendem Score sortierten Methodenübersicht. Zeige unbewertbare Methoden und Diagnosegründe in einem eigenen Abschnitt. Include- und Exclude-Regeln müssen nachvollziehbar bleiben.
Die Konsole zeigt einen begrenzten Ausschnitt und den Reportpfad, nicht tausende Zeilen. Markdown, JSON und Konsole müssen dieselbe Berechnung verwenden. Normale PIT-HTML-/XML-Reports dürfen nicht überschrieben werden.
Eine HTML-Ausgabe oder JUnit-XML für CI-Systeme ist später möglich, aber keine Voraussetzung für den ersten Abschluss.
6.3 Konfiguration
Dokumentiere eine kleine, eindeutig benannte Konfiguration mit mindestens:
- Aktivierung des Ausgabeformats und Reportziel über den unterstützten PIT-/Gradle-Weg;
- optionalem Score-Gate und dessen maximalem Methodenwert;
- Reporting-Ausschlüssen für Klassen und Methoden, mit definierter Mustersemantik;
- Analysekontext für Teilprojekt beziehungsweise PIT-Task;
- bei Bedarf expliziten Originalbytecodepfaden;
- ausdrücklicher Freigabe leerer Auswertungen für absichtlich leere Module.
Halte Gate-Optionen in der Gradle-Anbindung, wenn sie dort gebraucht werden; die gesamte Konfiguration muss nicht künstlich durch PIT geschleust werden. Werte ungültige Zahlen, widersprüchliche Optionen und unbekannte metrikeigene Konfigurationsschlüssel nicht stillschweigend aus.
Schließe triviale Methoden nicht automatisch aufgrund von Namen wie get* aus. Nutzer dürfen solche Ausschlüsse konfigurieren; standardmäßig sollen echte auswertbare Methoden sichtbar bleiben. Synthetisch bedeutet insbesondere bei Lambdas nicht automatisch fachlich unwichtig.
6.4 Gate nach abgeschlossenem PIT-Lauf
Bevorzuge einen dünnen Gradle-Verification-Task, etwa mutationCrapCheck, der von der vorhandenen PIT-Task abhängt und den eigenen fertigen JSON-Report auswertet. Das ist kein Wechsel zur Auswertung des PIT-XMLs.
Die Metrikberechnung bleibt im gemeinsamen Kern beziehungsweise PIT-Plugin. Der Gradle-Task enthält keine zweite Formelimplementierung. Er wertet den Reportvertrag mit einer gemeinsamen Gate-Logik aus.
Begründung dieser Architekturentscheidung: Ein Abbruch direkt in runEnd() eines Listeners könnte die Finalisierung anderer Reports beeinträchtigen. Belege einen solchen direkten Fehlerpfad durch Integrationstests, falls du statt des nachgeschalteten Tasks dennoch diesen Weg wählst. Der sichere Ablauf ist: PIT beendet sich regulär, Reports sind fertig, danach entscheidet das eigene Gate.
Die Gate-Regeln der ersten Version:
- Ohne aktiviertes Gate kein Build-Abbruch allein wegen hoher Scores.
- Bei aktivem Gate verletzt eine bewertete, nicht ausgeschlossene Methode die Grenze genau dann, wenn
mutationCrap > maxMutationCrap. Gleichheit besteht. INCONCLUSIVE, unerwartet fehlende Komplexität, unerwartet unvollständiger Scope oder ein unbrauchbarer Report lassen das strikte Gate als Analysefehler scheitern. Das ist keine behauptete schlechte Codequalität.NO_MUTATION_RESULTSundNO_ELIGIBLE_MUTANTSsind sichtbar unbewertet. Sie verletzen allein keinen numerischen Grenzwert. Das Gate darf sie nicht als erfolgreich getestete Methoden darstellen.- Hat eine aktiv geprüfte Analyse insgesamt keine einzige bewertbare Methode, scheitert sie als leere Auswertung, außer dies ist ausdrücklich erlaubt.
- Fehlgeschlagene PIT-Ausführung, fehlender/abgebrochener Report oder nicht unterstützte Schemaversion dürfen nicht zu einem grünen Gate durch Lesen einer alten Datei führen.
In CI müssen die bereits erstellten Reports auch bei einer Grenzwertverletzung als Artefakte verfügbar bleiben. Hänge die teure PIT-Analyse nicht ungefragt an jeden normalen check-Aufruf; dokumentiere die ausdrückliche Aktivierung des Mutation-Gates im CI-Workflow.
6.5 Gradle-Anwenderintegration
Nutze zunächst das bestehende info.solidsoft.pitest. Dessen Dokumentation beschreibt das Laden generischer PIT-Plugins sowie zusätzliche Source-Sets und Multi-Project-Konfigurationen. Diese Konfigurationen müssen nicht durch eine eigene PIT-Orchestrierung ersetzt werden. [S9]
Liefere mindestens ein lauffähiges Java-Beispiel mit Kotlin DSL und eine überprüfte Groovy-DSL-Anleitung. Falls ein eigenes dünnes Gradle-Plugin erforderlich oder sinnvoll ist, beschränke es auf die oben genannten Integrationsaufgaben.
Vorhandene Unit- und Integrationstest-Source-Sets müssen über die tatsächliche PIT-Konfiguration verwendbar bleiben. Führe sie nicht selbst über einen zusätzlichen JaCoCo- oder Gradle-Testlauf aus. Einbezogene Tests werden von PIT im Rahmen seiner normalen Arbeitsweise ausgeführt.
In Multi-Project-Builds zunächst getrennte Reports und Gates pro Analyse liefern. Reportpfade dürfen bei parallelen Teilprojekten nicht kollidieren. Ein Root-Task darf diese Teilprüfungen koordinieren, ohne Methodenwerte oder Mutation Scores zu einem neuen Gesamtwert zu vermischen.
Deklariere für eigene Gradle-Tasks Eingaben, Ausgaben und Konfigurationsabhängigkeiten korrekt. Eine Änderung des Gate-Grenzwerts muss neu geprüft werden, benötigt aber keinen erneuten Mutationstest, wenn ein für dieselben Analyseinputs gültiger PIT-Report vorliegt. Verifiziere dieses Verhalten; verlasse dich nicht auf bloße Dateiexistenz.
7. Kotlin/JVM: begrenztes, nachgeordnetes Ziel
Nach dem Java-Durchstich ergänze ein kleines Kotlin/JVM-Fixture mit einer gewöhnlichen Methode, Verzweigungen und überladenen Funktionen. Prüfe die Zuordnung anhand tatsächlicher JVM-Namen und Descriptoren.
Betrachte Default-Argumente, Top-Level-Funktionen, Properties, Lambdas, Bridge-/synthetische Methoden, Inline-Funktionen und Suspend-Funktionen anschließend als gesonderte Kompatibilitätsfälle. Eine Quelltextfunktion muss nicht genau einer JVM-Methode entsprechen. Fasse mehrere JVM-Methoden nicht allein aufgrund gleicher Quelltextzeilen zusammen.
JaCoCo besitzt Kotlin-spezifische Filter; PIT verweist zusätzlich auf erweiterte Kotlin-Unterstützung durch Arcmutate. Daraus folgt weder eine Garantie für alle Kotlin-Konstrukte im gewählten Setup noch, dass gewöhnlicher Kotlin-Bytecode grundsätzlich nicht funktionieren kann. Prüfe das konkrete freie Setup. [S12][S14]
Kennzeichne das Ergebnis präzise, beispielsweise „Kotlin/JVM-Basismethoden mit den dokumentierten Compiler-/PIT-Versionen getestet“. Kein pauschales „Kotlin vollständig unterstützt“ aufgrund eines einzelnen grünen Tests. Nicht abbildbare Fälle erhalten eine dokumentierte Einschränkung oder einen sichtbaren nicht numerischen Zustand, keinen erfundenen Score.
Ein belegter Kotlin-Blocker darf den Java-MVP nicht blockieren. Der Abschlussbericht muss dann den tatsächlich getesteten Stand nennen.
8. Arbeitsweise, Tests und Lieferumfang
8.1 Gherkin sinnvoll einsetzen
Die folgenden Features beschreiben beobachtbare Fähigkeiten aus Sicht von Entwicklern und CI, nicht betriebswirtschaftliche Vorgänge. Das passt zu Gherkins Feature-/Scenario-Modell. Interne Klassenstrukturen oder private Methoden gehören dagegen nicht in diese Akzeptanzszenarien. [S15]
Übernimm die Featureblöcke aus Abschnitt 9 in einzelne .feature-Dateien unter docs/features/. Die Tags @MC-... sind stabile Anforderungs-IDs. Implementiere die zugehörigen automatisierten Nachweise mit JUnit Jupiter; ein Cucumber-Laufzeitframework ist nicht vorgeschrieben.
Führe docs/acceptance-test-map.md, das jede ID auf konkrete Testklasse/Testmethode und ihren Status abbildet. Eine syntaktisch gültige Featuredatei ist noch kein Testnachweis. Wo kein Gherkin-Runner eingesetzt wird, sage ausdrücklich, dass JUnit-Tests die Szenarien prüfen. Prüfe die Feature-Syntax mit einem geeigneten Parser oder einem gleichwertigen reproduzierbaren Test.
Die mit @optional-kotlin markierten Szenarien gehören nicht zum blockierenden Java-MVP. Alle anderen Szenarien sind verbindliche Akzeptanzanforderungen; Tabellenzeilen eines Szenariogrundrisses sind getrennte Testfälle.
8.2 Testebenen
Metrikkern: Formelwerte, Grenzgleichheit, alle Statusfälle, unzulässige Eingangswerte, leere Nenner und Eigenschaften wie C <= mutationCrap <= C² + C für gültige Werte. Für konstantes C darf der Score bei steigendem MS nicht steigen.
Bytecode-/Adaptertests: Kompilierte Java-Fixtures mit Verzweigungen, Schleifen, Switch, Überladungen, Konstruktoren, Lambdas sowie abstrakten/native Methoden. Prüfe auch Klassen ohne Debuginformationen. Teste die notwendige Normalisierung und bewusst fehlende oder falsche Class-Dateien. Bekannte kleine Referenzwerte und JaCoCo-Counter dienen als Orakel; keine zweite eigene Complexity-Implementierung aufbauen.
Listener-/Reporttests: Gestückelte und umgeordnete Ergebnisse, Duplikate, widersprüchliche Status, stabile Ausgabe, Escaping, Schema, Abbruch-/Fehlerfälle und sichere Finalisierung.
Gradle-Funktionstests: Nutze nach Möglichkeit Gradle TestKit und reale kleine Anwenderprojekte. TestKit ist für Tests von Buildlogik vorgesehen. [S16] Prüfe insbesondere das gepackte Plugin, JUnit-Jupiter-Integration, beide Gate-Ausgänge, zusätzliche Test-Source-Sets und zwei Teilprojekte.
Optionaler Eigen-Mutationstest: PIT für den Metrikkern ist sinnvoll, sobald der normale Build steht. Vermeide rekursive Builds, bei denen der Eigen-Mutationstest wieder die vollständigen PIT-Anwender-Funktionstests startet. Kein pauschales 100-Prozent-Mutationsziel ohne Behandlung äquivalenter oder ungeeigneter Mutanten.
8.3 Umsetzungsschritte
- Repository und Gradle Wrapper aufsetzen, kompatible Versionen pinnen, minimalen realen Durchstich umsetzen und ADR schreiben.
- Metrikkern mit Referenzwerten und
strict-v1testgetrieben implementieren. - Originalbytecode, Methodeninventar und JaCoCo-Komplexität zuverlässig anbinden.
- PIT-Ergebnisse vollständig aggregieren und Identitäts-/Fehlerfälle testen.
- JSON-Schema, JSON-/Markdown-Report und Konsolenzusammenfassung liefern.
- Optional einschaltbares Gradle-Gate und Multi-Project-Fixture fertigstellen.
- Kotlin-Basissupport prüfen und Grenzen dokumentieren.
- Packaging aus isoliertem Test-Repository prüfen, Dokumentation vervollständigen und tatsächliche Testresultate zusammenfassen.
Setze offene, nicht blockierende Details begründet selbst fest, statt die Umsetzung mit einer langen Liste von Rückfragen zu stoppen. Halte Architekturentscheidungen fest und behalte die Fachsemantik dieser Spezifikation bei.
8.4 Erwartete Repository-Artefakte
Liefere mindestens Produktionscode, Tests, Wrapper/Builddateien, korrekt gepacktes PIT-Plugin, Beispielprojekt und eine CI-taugliche lokale Buildanleitung. Hinzu kommen:
README.md: Zweck, experimenteller Status, Installation, Gradle-Konfiguration, Reports, Gate und ein vollständiger Quickstart.docs/metric.md: Formel,strict-v1, N/A-Zustände, Grenzwerte und Aussagegrenzen.docs/compatibility.mdund das Integrations-ADR.docs/features/*.featureunddocs/acceptance-test-map.md.- Versioniertes JSON-Schema sowie kleine erwartete Beispielreports.
- Dokumentierter Umgang mit Runtime-Abhängigkeiten und deren Lizenzhinweisen.
Wähle ohne bekannte Eigentümerangaben keine fremde reale Group-ID oder reservierte Plugin-ID. Verwende nachvollziehbare Platzhalter beziehungsweise einen lokalen Entwicklungsnamensraum. Lege eine Projektlizenz nicht im Namen des Eigentümers fest, falls sie nicht vorgegeben ist; Abhängigkeitshinweise sind dennoch zu dokumentieren.
8.5 Definition of Done
Der Java-MVP ist fertig, wenn ein sauberes Checkout mit dokumentierten Voraussetzungen reproduzierbar baut, das gepackte Plugin in einem realen Gradle/PIT-Lauf funktioniert und alle verbindlichen Szenarien automatisiert nachgewiesen sind. Reports müssen echte pro-Methode-Werte aus PIT und JaCoCo enthalten, nicht nur aus simulierten Testdaten.
Berichte am Ende, was implementiert und mit welchen Versionen getestet wurde, welche Befehle tatsächlich ausgeführt wurden, welche Akzeptanztests bestanden haben und welche Einschränkungen bleiben. Behaupte weder ausgeführte Tests ohne Ausführung noch Kompatibilität ohne Nachweis. Eine fehlende lokale Voraussetzung oder ein echter API-Blocker ist konkret mit Reproduktionsschritten anzugeben.
9. Gherkin-Features und Akzeptanzszenarien
Die folgenden Blöcke sind in jeweils eine eigene Featuredatei zu übernehmen. Englische Status- und Feldnamen sind technische Identifikatoren; die Szenariosprache ist Deutsch.
9.1 01-metric.feature
# language: de
Funktionalität: Mutation-CRAP pro JVM-Methode berechnen
Als Entwickler möchte ich Komplexität und Mutationsergebnisse gemeinsam sehen,
damit ich Methoden für eine genauere Prüfung priorisieren kann.
@MC-001
Szenariogrundriss: Der Score entspricht der definierten Formel
Angenommen eine Methode hat die JaCoCo-Komplexität <komplexitaet>
Und es liegen <killed> KILLED-, <survived> SURVIVED- und <uncovered> NO_COVERAGE-Ergebnisse vor
Und es liegen keine weiteren Mutationsergebnisse vor
Wenn die Methode mit der Policy "strict-v1" ausgewertet wird
Dann ist ihr Zustand "SCORED"
Und ihr Mutation Score ist <mutationScore>
Und ihr Mutation-CRAP ist <crap>
Beispiele:
| komplexitaet | killed | survived | uncovered | mutationScore | crap |
| 1 | 0 | 1 | 0 | 0 | 2 |
| 10 | 0 | 10 | 0 | 0 | 110 |
| 10 | 5 | 5 | 0 | 0.5 | 22.5 |
| 10 | 8 | 2 | 0 | 0.8 | 10.8 |
| 10 | 10 | 0 | 0 | 1 | 10 |
| 30 | 10 | 0 | 0 | 1 | 30 |
@MC-002
Szenario: Ausschließlich nicht wertbare Mutanten ergeben keinen Score
Angenommen eine Methode hat die JaCoCo-Komplexität 4
Und ihre einzigen Ergebnisse sind 2 EQUIVALENT und 1 NON_VIABLE
Wenn die Methode mit der Policy "strict-v1" ausgewertet wird
Dann ist ihr Zustand "NO_ELIGIBLE_MUTANTS"
Und ihr Mutation Score und ihr Mutation-CRAP sind null
Und ihre Komplexität und ihre Rohzähler bleiben im Report sichtbar
@MC-003
Szenario: Eine Methode ohne Ergebnisse wird nicht als gut getestet dargestellt
Angenommen eine Methode gehört zum ausgewerteten Methodeninventar
Und sie hat die JaCoCo-Komplexität 12
Und es liegt kein Mutationsergebnis für diese Methode vor
Wenn der Report erstellt wird
Dann ist ihr Zustand "NO_MUTATION_RESULTS"
Und ihr Mutation Score und ihr Mutation-CRAP sind null
Und der Report behauptet weder null noch vollständige Testwirksamkeit
@MC-004
Szenariogrundriss: Ungültige Recheneingaben werden nicht kaschiert
Angenommen eine Recheneingabe enthält <fehler>
Wenn eine numerische Metrik daraus berechnet werden soll
Dann wird die ungültige Eingabe mit einem konkreten Grund zurückgewiesen
Und es wird kein gültiger Methodenwert erzeugt
Beispiele:
| fehler |
| eine negative Anzahl getöteter Mutanten |
| einen negativen Komplexitätswert |
| Komplexität 0 für eine numerisch bewertete Methode |
9.2 02-status-policy.feature
# language: de
Funktionalität: Mutationsergebnisse nachvollziehbar gewichten
Als Nutzer möchte ich erkennen, welche Ergebnisse in den Score eingehen,
damit Ausführungsprobleme nicht als Teststärke erscheinen.
@MC-005
Szenario: Nicht abgedeckte Mutanten bleiben im Nenner
Angenommen eine Methode hat die JaCoCo-Komplexität 10
Und sie hat 4 KILLED-, 1 SURVIVED- und 5 NO_COVERAGE-Ergebnisse
Wenn sie mit der Policy "strict-v1" ausgewertet wird
Dann beträgt der Nenner 10
Und ihr Mutation Score beträgt 0.4
Und ihr Mutation-CRAP beträgt 31.6
@MC-006
Szenariogrundriss: Explizit ausgeschlossene Status erhöhen nicht die Teststärke
Angenommen eine Methode hat 1 KILLED- und 1 SURVIVED-Ergebnis
Und zusätzlich 8 Ergebnisse mit Status <status>
Wenn sie mit der Policy "strict-v1" ausgewertet wird
Dann beträgt ihr Mutation Score 0.5
Und ihr Nenner beträgt 2
Und die 8 ausgeschlossenen Ergebnisse sind separat ausgewiesen
Beispiele:
| status |
| EQUIVALENT |
| NON_VIABLE |
@MC-007
Szenariogrundriss: Uneindeutige Endergebnisse verhindern einen regulären Score
Angenommen eine Methode hat 9 KILLED-Ergebnisse
Und am Ende der Analyse ein Ergebnis mit Status <status>
Wenn sie mit der Policy "strict-v1" ausgewertet wird
Dann ist ihr Zustand "INCONCLUSIVE"
Und ihr Mutation Score und ihr Mutation-CRAP sind null
Und der auslösende Status und alle Rohzähler sind sichtbar
Beispiele:
| status |
| TIMED_OUT |
| MEMORY_ERROR |
| RUN_ERROR |
| NOT_STARTED |
| STARTED |
| UNBEKANNTER_NEUER_STATUS |
9.3 03-complexity-and-identity.feature
# language: de
Funktionalität: Komplexität und Mutanten derselben Methode verbinden
Als Entwickler möchte ich verlässliche Methodenwerte,
auch bei Überladungen oder fehlenden Quelltextinformationen.
@MC-008
Szenario: Komplexität ohne zusätzlichen Coverage-Lauf ermitteln
Angenommen ein Java-Fixture enthält eine Methode mit einer einfachen if-Verzweigung
Und die originale Class-Datei liegt vor
Und es gibt weder eine JaCoCo-exec-Datei noch einen für diese Auswertung aktiven JaCoCo-Agenten
Wenn das Plugin die Klasse statisch analysiert
Dann wird für diese Methode die JaCoCo-Gesamtkomplexität 2 ermittelt
Und dafür wird kein Test gestartet
Und es wird nicht versehentlich die abgedeckte Komplexität 0 verwendet
@MC-009
Szenario: Fehlende Debuginformationen verhindern den Join nicht
Angenommen dasselbe kleine Java-Fixture wird mit und ohne Debuginformationen kompiliert
Und für beide Varianten liegen passende Mutationsergebnisse vor
Wenn beide Varianten jeweils ausgewertet werden
Dann werden die Mutanten jeweils der korrekten JVM-Methode zugeordnet
Und die Methode hat in beiden Varianten dieselbe Gesamtkomplexität
Und fehlende Quelltextzeilen werden als nicht verfügbar dargestellt
@MC-010
Szenario: Überladene Methoden bleiben getrennt
Angenommen eine Klasse besitzt die Methoden "calculate(I)I" und "calculate(Ljava/lang/String;)I"
Und für beide Methoden liegen verschiedene Komplexitäten und Mutationsergebnisse vor
Wenn der Report erstellt wird
Dann enthält er zwei getrennte Methodenidentitäten
Und jede Methode verwendet nur ihre eigene Komplexität und ihre eigenen Mutanten
@MC-011
Szenario: Fehlender Originalbytecode wird nicht durch einen Fantasiewert ersetzt
Angenommen ein Mutationsergebnis verweist auf eine Methode
Und deren Originalklasse kann nicht zuverlässig gelesen werden
Wenn die Analyse abgeschlossen wird
Dann wird der betroffene Eintrag als "COMPLEXITY_UNAVAILABLE" ausgewiesen
Und kein Komplexitätswert wird aus Namen oder Mutantenzahl geschätzt
Und ein aktiviertes striktes Gate scheitert mit einem Analysefehler
@MC-012
Szenario: Eine Klasse ohne Ergebnis-Callback bleibt im Inventar sichtbar
Angenommen eine Produktionsklasse gehört zum ausdrücklich ausgewählten Analysescope
Und ihre ausführbaren Methoden können statisch analysiert werden
Und PIT liefert für diese Klasse keine Mutationsergebnisse
Wenn das Plugin den ausgewählten Scope vollständig inventarisiert
Dann erscheinen ihre Methoden mit Zustand "NO_MUTATION_RESULTS"
Und sie werden nicht als erfolgreich mutationsgetestet gezählt
@MC-013
Szenario: Reporting-Ausschlüsse verändern PIT nicht
Angenommen eine Methode ist durch eine ausdrückliche Reporting-Regel ausgeschlossen
Und PIT hat für diese Methode Mutationsergebnisse erzeugt
Wenn das Plugin den Report erstellt
Dann sind Ausschluss und angewendete Regel nachvollziehbar
Und diese Methode nimmt nicht am Score-Gate teil
Und die ursprünglichen PIT-Ergebnisse werden weder verändert noch entfernt
9.4 04-pit-lifecycle.feature
# language: de
Funktionalität: Ergebnisse eines PIT-Laufs vollständig und ohne Nebenwirkungen auswerten
Als CI-Nutzer möchte ich einen einzigen normalen PIT-Lauf verwenden,
ohne dass das zusätzliche Reporting die Mutationstests verändert.
@MC-014
Szenario: Mehrere Ergebnisblöcke derselben Klasse werden zusammengeführt
Angenommen eine Methode hat die JaCoCo-Komplexität 10
Und ein Ergebnisblock enthält 4 ihrer KILLED-Ergebnisse
Und ein späterer Block enthält weitere 4 KILLED- und 2 SURVIVED-Ergebnisse
Wenn der PIT-Lauf abgeschlossen ist
Dann beträgt ihr Nenner 10
Und ihr Mutation Score beträgt 0.8
Und ihr Mutation-CRAP beträgt 10.8
Und die Methodenauswertung wird nicht durch den letzten Block überschrieben
@MC-015
Szenario: Die Reihenfolge der Ergebnisblöcke beeinflusst das Ergebnis nicht
Angenommen zwei Auswertungen erhalten dieselben eindeutigen Endergebnisse
Und die Ergebnisblöcke kommen in unterschiedlicher Reihenfolge an
Wenn beide Auswertungen abgeschlossen werden
Dann sind ihre fachlichen JSON-Inhalte identisch
Und die Methoden erscheinen in derselben stabilen Sortierung
@MC-016
Szenario: Wiederholte identische Ergebnisse werden nicht doppelt gezählt
Angenommen dieselbe PIT-Mutantenidentität wird zweimal mit Endstatus KILLED geliefert
Wenn die Ergebnisse aggregiert werden
Dann erhöht diese Identität den KILLED-Zähler nur einmal
@MC-017
Szenario: Widersprüchliche Endergebnisse sind ein Analyseproblem
Angenommen dieselbe PIT-Mutantenidentität wird mit KILLED und mit SURVIVED geliefert
Wenn die Auswertung abgeschlossen wird
Dann wird der Widerspruch als "INCONCLUSIVE" dokumentiert
Und nicht einfach das zuletzt eingegangene Ergebnis verwendet
@MC-018
Szenario: Das ausgelieferte Plugin ergänzt einen normalen PIT-Lauf
Angenommen das Plugin-JAR und seine Abhängigkeiten sind über den PIT-Tool-Classpath geladen
Und MUTATION_CRAP sowie die normalen PIT-Ausgaben HTML und XML sind aktiviert
Wenn das Java-Beispielprojekt seine PIT-Analyse ausführt
Dann entstehen der eigene JSON- und Markdown-Report sowie die normalen PIT-Reports
Und das Plugin hat keine zusätzliche PIT-Analyse gestartet
Und das Plugin hat keinen zusätzlichen Coverage-Testlauf gestartet
Und es hat die Mutantenauswahl nicht verändert
9.5 05-reports-and-gate.feature
# language: de
Funktionalität: Nachvollziehbare Reports und ein bewusst aktiviertes Build-Gate liefern
Als CI-Nutzer möchte ich hohe Scores und Analyseprobleme unterscheiden,
ohne fehlende Daten mit guter Qualität zu verwechseln.
@MC-019
Szenario: Der Standardmodus erstellt Reports ohne Score-bedingten Build-Abbruch
Angenommen die Analyse ist regulär abgeschlossen
Und eine Methode hat Mutation-CRAP 110
Und es ist kein Score-Gate aktiviert
Wenn das Ergebnis ausgegeben wird
Dann bleibt der Build wegen dieses Scores erfolgreich
Und der hohe Wert ist im Report sichtbar
@MC-020
Szenariogrundriss: Nur eine echte Überschreitung verletzt den Grenzwert
Angenommen eine Methode hat den gültigen Mutation-CRAP-Wert <score>
Und ein Score-Gate mit Grenzwert 30 ist aktiviert
Und es liegen keine weiteren Verstöße oder Analyseprobleme vor
Wenn das Gate ausgewertet wird
Dann ist das Build-Ergebnis <ergebnis>
Beispiele:
| score | ergebnis |
| 29 | erfolgreich |
| 30 | erfolgreich |
| 31 | fehlgeschlagen |
@MC-021
Szenario: Anzeigerundung beeinflusst das Gate nicht
Angenommen eine Methode hat Komplexität 10, 79 KILLED- und 21 SURVIVED-Ergebnisse
Und ihr exakter Mutation-CRAP-Wert beträgt 10.9261
Und für die Anzeige wird auf eine Nachkommastelle gerundet
Und der aktivierte Grenzwert beträgt 10.92
Wenn das Gate ausgewertet wird
Dann scheitert es wegen der tatsächlichen Überschreitung
Und der gerundete Anzeigewert 10.9 wird nicht für den Vergleich verwendet
@MC-022
Szenariogrundriss: Ein striktes Gate kann Analyseprobleme nicht als Erfolg werten
Angenommen alle numerisch bewerteten Methoden liegen unter ihrem Grenzwert
Aber es besteht <problem>
Wenn das aktivierte strikte Gate ausgewertet wird
Dann scheitert es mit einem konkreten Analysefehler
Und der Fehler wird nicht als schlechte Testqualität bezeichnet
Beispiele:
| problem |
| ein INCONCLUSIVE-Ergebnis |
| unerwartet fehlende Methodenkomplexität |
| ein unerwartet unvollständig inventarisierter Scope |
| ein fehlender oder unlesbarer aktueller Report |
| eine nicht unterstützte Report-Schemaversion |
@MC-023
Szenario: Eine leere Auswertung wird nicht stillschweigend grün
Angenommen ein Score-Gate ist aktiviert
Und im ausgewählten Scope ist keine einzige Methode numerisch bewertbar
Und leere Auswertungen sind nicht ausdrücklich erlaubt
Wenn das Gate ausgewertet wird
Dann scheitert es mit dem Hinweis auf eine leere Auswertung
@MC-024
Szenario: Ein absichtlich leeres Modul kann ausdrücklich erlaubt werden
Angenommen die Analyse eines Moduls ist regulär abgeschlossen
Und es gibt keine numerisch bewertbaren Methoden und keine Analysefehler
Und leere Auswertungen sind ausdrücklich erlaubt
Wenn das aktivierte Gate ausgewertet wird
Dann scheitert es nicht allein wegen der leeren Auswertung
Und der Report bezeichnet das Modul weiterhin als unbewertet
@MC-025
Szenario: Nach einer Grenzwertverletzung bleiben Reports verfügbar
Angenommen PIT und alle aktivierten Reporter sind regulär abgeschlossen
Und ein Methodenwert verletzt den aktivierten Grenzwert
Wenn der nachgeschaltete Gate-Task ausgeführt wird
Dann schlägt der Build fehl
Und der JSON-Report bleibt vollständig und schemakonform lesbar
Und der Markdown-Report und die normalen PIT-Reports bleiben verfügbar
@MC-026
Szenario: Maschinenlesbare Werte sind eindeutig und nachrechenbar
Angenommen eine Auswertung enthält bewertete und unbewertete Methoden
Wenn der JSON-Report geschrieben wird
Dann enthält er Metrikversion, Policykennung, Toolversionen und Analysescope
Und er enthält die verwendeten Methodenidentitäten und Status-Rohzähler
Und nicht definierte numerische Werte werden als null geschrieben
Und kein Feld enthält NaN oder Infinity
Und der Report entspricht seinem versionierten JSON-Schema
9.6 06-gradle-integration.feature
# language: de
Funktionalität: Das PIT-Plugin in Gradle-Projekten einsetzen
Als Java-Entwickler möchte ich meine vorhandene PIT-Konfiguration weiterverwenden,
statt Mutationstests für die neue Metrik separat orchestrieren zu müssen.
@MC-027
Szenario: Unit- und Integrationstests bleiben über PIT konfigurierbar
Angenommen ein Java-Projekt verwendet JUnit Jupiter
Und seine PIT-Konfiguration berücksichtigt Unit- und Integrationstest-Source-Sets
Und das Mutation-CRAP-Plugin ist aktiviert
Wenn der dokumentierte Mutation-CRAP-Workflow gestartet wird
Dann wird genau eine PIT-Analyse für diese Konfiguration angefordert
Und deren Ergebnisse einschließlich der einbezogenen Integrationstests werden verwendet
Und das Plugin startet keine zusätzlichen Gradle-Testtasks zur Komplexitätsermittlung
@MC-028
Szenario: Gleiche Methodenidentitäten aus zwei Teilprojekten bleiben getrennt
Angenommen zwei Gradle-Teilprojekte enthalten jeweils dieselbe JVM-Klasse und Methodensignatur
Und die beiden Methoden haben unterschiedliche Auswertungsergebnisse
Wenn beide Teilprojekte parallel ihre PIT-Analyse durchführen
Dann erhält jedes Teilprojekt seinen eigenen Report
Und die Einträge tragen unterscheidbare Analysekontexte
Und keine Ergebnisse oder Dateien werden gegenseitig überschrieben
@MC-029
Szenariogrundriss: Ein Anwender konsumiert das tatsächlich gepackte Plugin
Angenommen das Plugin wurde in ein isoliertes lokales Test-Artefakt-Repository publiziert
Und ein separates Anwenderprojekt verwendet <dsl>
Und es kennt keine Klassenverzeichnisse aus dem Plugin-Entwicklungsbuild
Wenn der dokumentierte Gradle/PIT-Aufruf gestartet wird
Dann wird die Service-Factory aus dem Plugin-JAR gefunden
Und alle erforderlichen Runtime-Abhängigkeiten werden geladen
Und ein Report mit echten Java-Methodenwerten entsteht
Beispiele:
| dsl |
| Kotlin DSL |
| Groovy DSL |
@MC-030
Szenario: Ein geänderter Grenzwert benötigt keine erneuten Mutationstests
Angenommen ein für unveränderte Analyseinputs gültiger PIT-Report liegt vor
Und die zugehörige PIT-Task kann korrekt als aktuell behandelt werden
Wenn nur der Gate-Grenzwert geändert und der Gate-Task ausgeführt wird
Dann wird die Gate-Entscheidung mit dem neuen Grenzwert neu berechnet
Und dafür wird keine zusätzliche PIT-Analyse gestartet
@MC-031
Szenario: Ein gescheiterter PIT-Lauf darf keinen alten Report als Erfolg verwenden
Angenommen ein erfolgreicher Report aus einem früheren Lauf existiert
Und die aktuelle PIT-Analyse scheitert vor dem regulären Abschluss
Wenn der dokumentierte CI-Workflow ausgeführt wird
Dann bleibt der Build fehlgeschlagen
Und es wird kein aktueller Gate-Erfolg aus dem alten Report abgeleitet
9.7 07-kotlin-jvm.feature
# language: de
@optional-kotlin
Funktionalität: Kotlin-JVM-Methoden ohne falsche Quelltextzusammenfassung auswerten
Als Kotlin-Nutzer möchte ich erkennen, welche tatsächlich erzeugten JVM-Methoden
mit der dokumentierten Compiler- und PIT-Kombination ausgewertet werden können.
@MC-032
Szenario: Gewöhnliche Kotlin-Methoden mit Verzweigungen auswerten
Angenommen ein Kotlin-JVM-Fixture enthält gewöhnliche überladene Methoden mit Verzweigungen
Und die gewählte PIT-Konfiguration erzeugt dafür auswertbare Mutanten
Wenn das Plugin die zugehörigen Originalklassen mit JaCoCo analysiert
Dann werden die Ergebnisse anhand von JVM-Klasse, Methodenname und Descriptor verbunden
Und die Überladungen erhalten getrennte nachvollziehbare Auswertungen
Und die verwendeten Kotlin-, PIT- und JaCoCo-Versionen sind dokumentiert
@MC-033
Szenario: Generierte Methoden nicht aufgrund gemeinsamer Quelltextzeilen zusammenfassen
Angenommen eine Kotlin-Quelltextfunktion erzeugt mehrere JVM-Methoden
Wenn das Plugin deren Komplexität und Mutationsergebnisse auswertet
Dann werden JVM-Methoden nicht allein aufgrund gemeinsamer Quelltextzeilen vereinigt
Und jede bewertete Methode hat eine nachweisbar passende Komplexität
Und nicht auswertbare oder ausdrücklich gefilterte Fälle werden mit Gründen gekennzeichnet
10. Quellen und Prüfhinweise
Recherchestand: 1. Oktober 2026. Dies sind Primärquellen zu den verwendeten Erweiterungspunkten und Werkzeugen, keine Belege für die wissenschaftliche Validierung der neu definierten Metrik. Definitionen und Defaultentscheidungen in diesem Auftrag sind dort nicht automatisch vorgeschrieben.
Die Quellen wurden zur Spezifikation herangezogen; der beschriebene Plugin-Prototyp wurde im Rahmen der Dokumenterstellung nicht implementiert oder ausgeführt. Der Machbarkeitsnachweis bleibt deshalb ein ausdrücklicher erster Arbeitsauftrag.
[S1] PIT — Advanced Usage / Plugins / Mutation Result Listener. Service-Registrierung, Ausgabeformate, Tool-/Client-Classpath und Hinweise auf mögliche API-Änderungen.
https://pitest.org/quickstart/advanced/
[S2] PIT — MutationResultListenerFactory. Factory-Vertrag im untersuchten Entwicklungsstand. Am ausgewählten Release verifizieren.
https://github.com/hcoles/pitest/blob/master/pitest-entry/src/main/java/org/pitest/mutationtest/MutationResultListenerFactory.java
[S3] PIT — Listener und klassenweise Ergebnisblöcke. Insbesondere der ausdrückliche Hinweis, dass Ergebnisse derselben Klasse auf mehrere Instanzen verteilt sein können.
https://github.com/hcoles/pitest/blob/master/pitest-entry/src/main/java/org/pitest/mutationtest/MutationResultListener.java
https://github.com/hcoles/pitest/blob/master/pitest-entry/src/main/java/org/pitest/mutationtest/ClassMutationResults.java
[S4] PIT — ListenerArguments und SourceLocator. Verfügbare Listenerargumente sowie Quelltextzugriff, der nicht mit Bytecodezugriff verwechselt werden darf.
https://github.com/hcoles/pitest/blob/master/pitest-entry/src/main/java/org/pitest/mutationtest/ListenerArguments.java
https://github.com/hcoles/pitest/blob/master/pitest-entry/src/main/java/org/pitest/mutationtest/SourceLocator.java
[S5] PIT — ReportOptions. Kandidat für Classpath-/Codepath-Informationen; Nutzbarkeit und Kopplung an der gepinnten Version prüfen.
https://github.com/hcoles/pitest/blob/master/pitest-entry/src/main/java/org/pitest/mutationtest/config/ReportOptions.java
[S6] JaCoCo — Analyzer-API und Implementierung. Statische Analyse von Class-Dateien; die Implementierung behandelt fehlende Ausführungsdaten.
https://www.jacoco.org/jacoco/trunk/doc/api/org/jacoco/core/analysis/Analyzer.html
https://www.jacoco.org/jacoco/trunk/coverage/org.jacoco.core/org.jacoco.core.analysis/Analyzer.java.html
[S7] JaCoCo — IMethodCoverage und ICounter. JVM-Descriptor, Complexity-Counter und Gesamtanzahl.
https://www.jacoco.org/jacoco/trunk/doc/api/org/jacoco/core/analysis/IMethodCoverage.html
https://www.jacoco.org/jacoco/trunk/doc/api/org/jacoco/core/analysis/ICounter.html
[S8] JaCoCo — Coverage Counters. Zyklomatische Komplexität, fehlende Debuginformationen und Behandlung von Exception-Handling.
https://www.jacoco.org/jacoco/trunk/doc/counters.html
[S9] Gradle-PIT-Plugin — offizielle Projektdokumentation. Generische PIT-Plugins, Source-Sets, JUnit und Multi-Project-Builds. Versionsbeispiele der Webseite nicht ungeprüft übernehmen.
https://gradle-pitest-plugin.solidsoft.info/
[S10] PIT — Location. Klassenname, Methodenname und JVM-Methodendeskriptor.
https://github.com/hcoles/pitest/blob/master/pitest/src/main/java/org/pitest/mutationtest/engine/Location.java
[S11] PIT — DetectionStatus. Tatsächliche Statuswerte und deren isDetected()-Semantik. Am ausgewählten Release prüfen; nicht jede ältere Version enthält dieselben Enum-Werte.
https://github.com/hcoles/pitest/blob/master/pitest/src/main/java/org/pitest/mutationtest/DetectionStatus.java
[S12] JaCoCo — Change History. Insbesondere Änderungen an Filtern für Kotlin- und compilererzeugten Bytecode.
https://www.jacoco.org/jacoco/trunk/doc/changes.html
[S13] PIT — Basic Concepts. Bedeutung von Mutanten-Ergebnissen und nicht abgedeckten Mutanten.
https://pitest.org/quickstart/basic_concepts/
[S14] PIT — Projektwebseite. Abgrenzung zur erweiterten Sprach-/Framework-Unterstützung durch Arcmutate.
https://pitest.org/
[S15] Cucumber — Gherkin Reference. Features, Szenarien, Szenariogrundrisse, Beispiele, Tags und Sprachdeklaration.
https://cucumber.io/docs/gherkin/reference/
[S16] Gradle — Testing Build Logic with TestKit. Funktionstests für Gradle-Buildlogik mit separaten Anwenderprojekten.
https://docs.gradle.org/current/userguide/test_kit.html