Mutmut lernen - ein Tool zum Testen von Python-Mutationen

Mit Mutationstests können Sie Fehler identifizieren, die von herkömmlichen Tests nicht abgedeckt werden.



Haben Sie Tests für alle Gelegenheiten? Oder enthält Ihr Projekt-Repository sogar die Hilfe "Über 100% Testabdeckung"? Aber ist im wirklichen Leben alles so einfach und erreichbar?







Bei Unit-Tests ist alles mehr oder weniger klar: Sie müssen geschrieben werden. Manchmal funktionieren sie nicht wie erwartet: Es gibt Fehlalarme oder Tests mit Fehlern, die ohne Codeänderungen "Ja" und "Nein" zurückgeben. Kleine Fehler, die Sie in Unit-Tests finden können, sind wertvoll, werden jedoch häufig vom Entwickler selbst behoben - bevor er sie festlegt. Aber wir sind wirklich besorgt über die Fehler, die oft außer Sicht geraten. Und am schlimmsten ist, dass sie sich oft einen Namen machen, wenn das Produkt in die Hände des Benutzers fällt.



Es ist Mutationstestermöglicht es Ihnen, solche heimtückischen Fehler zu bekämpfen Es ändert den Quellcode auf eine vorbestimmte Weise (Einführung spezieller Fehler - der sogenannten "Mutanten") und prüft, ob diese Mutanten in anderen Tests überleben. Jede Mutante, die den Komponententest überlebt hat, führt zu dem Schluss, dass die Standardtests nicht den entsprechenden modifizierten Code gefunden haben, der den Fehler enthält.



In Python ist Mutmut das Hauptwerkzeug für Mutationstests .



Stellen Sie sich vor, wir müssen einen Code schreiben, der den Winkel zwischen Stunden- und Minutenzeiger in einer analogen Uhr berechnet:



def hours_hand(hour, minutes):
    base = (hour % 12 ) * (360 // 12)
    correction = int((minutes / 60) * (360 // 12))
    return base + correction

def minutes_hand(hour, minutes):
    return minutes * (360 // 60)

def between(hour, minutes):
    return abs(hours_hand(hour, minutes) - minutes_hand(hour, minutes))


Schreiben wir einen grundlegenden Unit-Test:



import angle

def test_twelve():
    assert angle.between(12, 00) == 0


Der Code enthält keine Wenns . Lassen Sie uns prüfen, inwieweit ein solcher Komponententest alle möglichen Situationen abdeckt:



$ coverage run `which pytest`
============================= test session starts ==============================
platform linux -- Python 3.8.3, pytest-5.4.3, py-1.8.2, pluggy-0.13.1
rootdir: /home/moshez/src/mut-mut-test
collected 1 item                                                              

tests/test_angle.py .                                                    [100%]

============================== 1 passed in 0.01s ===============================


Ausgezeichnet! Wie 100% Deckung. Aber was passiert, wenn wir Mutationstests durchführen?







Oh nein! Von den 21 überlebten 16 Mutanten. Wieso das?



Für jeden Mutationstest muss ein Teil des Quellcodes geändert werden, der einen möglichen Fehler simuliert. Ein Beispiel für eine solche Änderung ist das Ändern des Vergleichsoperators ">" in "> =". Wenn es keinen Komponententest für diese Randbedingung gibt, überlebt dieser mutierte Fehler: Dies ist ein potenzieller Fehler, den keiner der üblichen Tests erkennt.



Okay. Alles klar. Wir müssen bessere Unit-Tests schreiben. Lassen Sie uns dann mit dem Befehl results sehen, welche spezifischen Änderungen vorgenommen wurden:



$ mutmut results
<snip>
Survived :( (16)

---- angle.py (16) ----

4-7, 9-14, 16-21
$ mutmut apply 4
$ git diff
diff --git a/angle.py b/angle.py
index b5dca41..3939353 100644
--- a/angle.py
+++ b/angle.py
@@ -1,6 +1,6 @@
 def hours_hand(hour, minutes):
     hour = hour % 12
-    base = hour * (360 // 12)
+    base = hour / (360 // 12)
     correction = int((minutes / 60) * (360 // 12))
     return base + correction


Dies ist ein typisches Beispiel für die Funktionsweise von mumut: Es analysiert den Quellcode und ersetzt einige Operatoren durch andere: zum Beispiel Addition durch Subtraktion oder, wie in diesem Fall, Multiplikation durch Division. Unit-Tests sollten im Allgemeinen Fehler beim Ändern von Anweisungen erkennen. Andernfalls testen sie das Verhalten des Programms nicht effektiv. Dies ist die Logik, an die sich mutmut hält, wenn er bestimmte Änderungen vornimmt.



Wir können den Befehl mutmut apply für die überlebende Mutante verwenden. Wow, es stellte sich heraus, dass wir nicht überprüft haben, ob der Stundenparameter richtig verwendet wurde. Lassen Sie uns das beheben:



$ git diff
diff --git a/tests/test_angle.py b/tests/test_angle.py
index f51d43a..1a2e4df 100644
--- a/tests/test_angle.py
+++ b/tests/test_angle.py
@@ -2,3 +2,6 @@ import angle
 
 def test_twelve():
     assert angle.between(12, 00) == 0
+
+def test_three():
+    assert angle.between(3, 00) == 90


Bisher haben wir nur auf 12 geprüft. Das Hinzufügen eines Schecks für den Wert 3 würde den Tag retten?







Dieser neue Test hat es geschafft, zwei Mutanten abzutöten: Es ist besser als zuvor, aber es muss noch mehr Arbeit geleistet werden. Ich werde derzeit keine Lösung für jeden der 14 verbleibenden Fälle schreiben, da die Idee bereits klar ist (können Sie alle Mutanten selbst töten?).



Zusätzlich zur Messung der Abdeckung können Sie mit Mutationstests auch bewerten, wie umfassend Ihre Tests sind. Auf diese Weise können Sie Ihre Tests verbessern: Jede der überlebenden Mutanten ist ein Fehler, den der Entwickler möglicherweise gemacht hat, sowie ein potenzieller Defekt in Ihrem Produkt. Also, ich wünsche dir, dass du mehr Mutanten tötest!






Werbung



VDSina bietet virtuelle Server unter Linux und Windows an - wählen Sie eines der vorinstallierten Betriebssysteme aus oder installieren Sie es von Ihrem Image.






All Articles