Klara ist eine statische Analyse -Tools, um automatisch zu testen, dass ein Testerfall auf Basis eines SMT (Z3) -Lösers mit einem leistungsstarken AST -Level -Inferenzsystem basiert. Klara nimmt die Python -Datei als Eingabe ein und generiert entsprechende Testdatei im PyTest -Format, die versuchen, alle Rückgabeteile abzudecken. Beispielsweise folgen Sie die Funktion in Datei test.py
def triangle ( x : int , y : int , z : int ) -> str :
if x == y == z :
return "Equilateral triangle"
elif x == y or y == z or x == z :
return "Isosceles triangle"
else :
return "Scalene triangle"wird erzeugen
import test
def test_triangle_0 ():
assert test . triangle ( 0 , 0 , 0 ) == 'Equilateral triangle'
assert test . triangle ( 0 , 0 , 1 ) == 'Isosceles triangle'
assert test . triangle ( 2 , 0 , 1 ) == 'Scalene triangle'Siehe die Dokumentation des Klara unter https://klara-py.readthedocs.io
Hinweis : Klara befindet sich noch in einem frühen experimentellen Stadium. Bemerkenswerte fehlende Merkmale sind Schleifen, Verständnis, Modulimport, Ausnahmen und viele mehr. Siehe Einschränkungen für die vollständige Liste. Es wird wahrscheinlich nicht bei realen Projekten auslaufen, daher ist es am besten, ein paar interessante Funktionen zu besetzen, um den entsprechenden Testfall zu generieren.
Klara kann über pip -Tool mithilfe von: installiert werden:
pip install klara
Wir können klara in jeder Python -Quelldatei aufrufen, und sie generiert eine entsprechende PyTest -Testdatei.
$ cat source.py
def foo(x: int, y: int, z: str):
if x + y > 2:
return x + y + 12
elif x < y:
return x + y
elif (z + " me " ) == " some " :
return z + " thing "
else:
return x - y
$ klara source.py
$ cat test_source.py
import contract_test
def test_foo_0 ():
assert contract_test.foo(0, 3, '' ) == 15
assert contract_test.foo(0, 1, '' ) == 1
assert contract_test.foo(0, 0, ' so ' ) == ' sothing '
assert contract_test.foo(0, 0, '' ) == 0Weitere Beispiele und Anleitungen finden Sie in der Quick Start -Handbuch. Um es als statische Analysebibliothek zu verwenden, gehen Sie zu Inferenz.
Klara arbeitet auf AST -Ebene und führt den Benutzercode in keiner Weise aus. Dies ist ein sehr wichtiger Unterschied in Bezug auf ähnliches Tool wie Crosshair und Pynguin, die eine konkolische symbolische Ausführung verwenden, die die Ausführung von Benutzercode erfordert, die unerwünschte Nebenwirkungen verursachen könnten. Klara arbeitet auf AST-Ebene, kombinieren Sie sich mit der Datenflussanalyse, die den Kontrollflussdiagramm (CFG), die statische Einzelzuweisung (SSA), die Anwendungs-Def-Kette usw. verwenden, um ein leistungsstarkes Python-Inferenzsystem zu erstellen, das Z3-Solver für Einschränkungen der Lösung und der Durchführbarkeitsfähigkeit der Pfade nutzt. Aus diesem Grund kann Klara mit Hilfe von typed_ast sowohl auf Python2/3 -Quellcode arbeiten. Um den Quellcode in Python 2 festzulegen, geben Sie das Argument -py 2 weiter. Es ist standardmäßig Python 3.
Klara kann auch als statisches Analyse -Tool verwendet werden. Ermöglicht dem Benutzer die benutzerdefinierte Regel, Programmierfehler, Fehler oder Durchsetzung des Codierungsstandards zu identifizieren. Bei der Unterstützung der SMT-Solver wird die Analyse genauer sein und falsch-positive Fall erheblich reduzieren. Zum Beispiel
import klara
tree = klara . parse ( """
def foo(v1: int):
if v1 > 4:
if v1 < 3:
z = 1
else:
z = 2
else:
z = 3
s = z
""" )
with klara . MANAGER . initialize_z3_var_from_func ( tree . body [ 0 ]):
print ( list ( tree . body [ 0 ]. body [ - 1 ]. value . infer ()))Wird ausdrucken:
[2, 3]
Weil z = 1 aufgrund von v1 > 4 und v1 < 3 nicht möglich ist, ist nicht zufriedenstellbar
Die Inferenzsystemarchitektur und die API sind größtenteils von Astroid inspiriert, einer statischen Inferenzbibliothek, die von Pylinint verwendet wird.
Klara nutzt das Inferenzsystem, um Testfall zu generieren. Mit anderen Worten generiert es einen Testfall für alle möglichen Rückgaberückwerte der Funktion , anstatt Testfall für alle Kontrollpfads der Funktion zu generieren.
Um den Punkt zu veranschaulichen, berücksichtigen Sie die folgende Funktion mit divide by zero -Schwachstellen in Zeile 3
def foo ( v1 : int , v2 : float ):
if v1 > 10000 :
s = v1 / 0 # unused statement
if v1 > v2 :
s = v1
else :
s = v2
return sKlara generiert unten Testeingänge
import contract_test
def test_foo_0 ():
assert contract_test . foo ( 0 , - 1.0 ) == 0
assert contract_test . foo ( 0 , 0.0 ) == 0.0 Es generiert nicht Eingabe v1 > 10000 , sodass der Testfall die Ausnahmen nicht herausfinden kann. Dies liegt daran, dass die s in Zeile 3 im Rückgabewert nicht genutzt werden.
Wenn wir die zweite If-Anweisung an elif ändern, die wir in Zeile 3 die [s] {. Title-ref} zurückgeben können, generiert Klara Testeingänge, die v1 > 10000 Fall abdecken.
Dies ist eine wichtige Unterscheidung bei der jetzt verfügbaren automatischen Testfallgenerierung, da wir durch nur Testfall für Rückgabewerte einen minimalen Testfall generieren können, und es ist einfacher zu anpassen, wie Klara die Funktion abdeckt.
Angenommen, wir verfassen ein komplexes System
def main ( number : int , cm : int , dc : int , wn : int ):
mc = 0
if wn > 2 :
if number > 2 and number > 2 or number > 2 :
if number > 0 :
if wn > 2 or wn > 2 :
mc = 2
else :
mc = 5
else :
mc = 100
else :
mc = 1
nnn = number * cm
if cm <= 4 :
num_incr = 4
else :
num_incr = cm
n_num_incr = nnn / num_incr
nnn_left = dc * num_incr * ( n_num_incr / 2 + n_num_incr % 2 )
nnn_right = nnn - nnn_left
is_flag = nnn_right
if is_flag :
cell = Component ( nnn_right , options = [ mc ])
else :
cell = Component ( nnn_right )
return cellEs ist uns nicht sofort klar, wie viele mögliche Rückgabewerte es gibt. Aber wir können Klara nutzen, um Eingaben sofort zu generieren. Im Folgenden finden Sie den generierten Test
import contract_test
def test_main_0 ():
assert contract_test . main ( 2 , 4 , 1 , 3 ) is not None
assert contract_test . main ( 2 , 4 , - 1 , 6 ) is not None
assert contract_test . main ( 2 , 4 , 1 , 4 ) is not None
assert contract_test . main ( - 2 , 4 , 3 , 4 ) is not None
assert contract_test . main ( - 1 , - 1 , - 1 , 2 ) is not None
assert contract_test . main ( 0 , 0 , 0 , 3 ) is not None
assert contract_test . main ( 0 , 0 , 0 , 6 ) is not None
assert contract_test . main ( 0 , 0 , 0 , 4 ) is not None
assert contract_test . main ( - 2 , 0 , 0 , 4 ) is not None
assert contract_test . main ( 0 , 0 , 0 , 0 ) is not None Oben erzeugte 10 Gesamtergebnisse, die Produkte von nnn_right mit 2 Möglichkeiten und mc enthält, die 5 Möglichkeiten haben.
Angenommen, 10 Testeingaben sind zu stark, und wir haben festgestellt, dass das Argument options für Component überflüssig ist. Wir können das benutzerdefinierte Plugin von Klara verwenden, um selektiv zu bestimmen, welcher Teil in der Testgenerierung ignoriert werden soll. Weitere Informationen finden Sie in die Berichterstattungsstrategie.
Nachdem wir das Plugin eingerichtet haben, wird Klara den folgenden Test erzeugen
import contract_test
def test_main_0 ():
assert contract_test . main ( 1 , 3 , 0 , 0 ) is not None
assert contract_test . main ( 0 , 0 , 0 , 0 ) is not None Das sind nur 2 Kombinationen von nnn_right
Da Klara den Code nicht dynamisch ausführen kann, wird eine Erweiterung angezeigt, um anzugeben, wie Sie einen bestimmten AST -Knoten oder einen benutzerdefinierten Typ abschließen können, um Klara "intelligenter" zu machen. Es wird beschrieben, um den Benutzertyp zu erweitern und die Deckungsstrategie anzupassen.
Wir verwenden Poesie, um Abhängigkeiten zu verwalten. Nach der Installation der Poesie rennen Sie:
$ poetry shell
$ poetry install
Um den Testfall auszuführen, tun Sie:
$ poetry run pytest test
Dieses Projekt ist gemäß den Bedingungen der gnu weniger allgemeinen öffentlichen Lizenz lizenziert.