Klara est un outils d'analyse statique pour générer automatiquement un cas de test, basé sur le solveur SMT (Z3), avec un puissant système d'inférence de niveau AST. Klara prendra le fichier Python en entrée et générera un fichier de test correspondant au format PyTest, qui tente de couvrir toutes les valeurs de retour. Par exemple, la fonction suivante dans File 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"générera
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'Voir la documentation de Klara sur https://klara-py.readthedocs.io
Remarque : Klara est encore en phase expérimentale précoce, les fonctionnalités manquantes notables sont la boucle, la compréhension, l'importation de modules, les exceptions et bien d'autres. Voir les limitations de la liste complète. Il ne fonctionnera probablement pas sur des projets réels, il est donc préférable de sélectionner des fonctions intéressantes pour générer le cas de test correspondant.
Klara peut être installé via l'outil pip en utilisant:
pip install klara
Nous pouvons invoquer klara sur n'importe quel fichier source Python, et il générera un fichier de test PyTest correspondant.
$ 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, '' ) == 0Consultez le manuel de démarrage rapide pour plus d'exemples et de conseils. Pour l'utiliser comme bibliothèque d'analyse statique, accédez à l'inférence.
KLARA travaille au niveau AST et n'exécute en aucune façon le code utilisateur, ce qui est une différence très importante par rapport à un outil similaire comme Crosshair et Pynguin qui utilisent une exécution symbolique conforme qui nécessitait l'exécution de code utilisateur qui pourrait provoquer des effets secondaires indésirables. KLARA travaille au niveau AST, combinez avec une analyse du flux de données qui utilise un graphique de flux de contrôle (CFG), une seule affectation statique (SSA), une chaîne d'utilisation-def, etc ... pour construire un puissant système d'inférence Python qui exploite Z3-Solver pour le résolution des contraintes et la vérification de la faisabilité du chemin. Pour cette raison, Klara est capable de fonctionner sur le code source Python2 / 3 à l'aide de Typed_ast. Pour spécifier le code source se trouve dans Python 2, passez l'argument -py 2 . C'est Python 3 par défaut.
Klara peut également être utilisée comme outil d'analyse statique, permettre à l'utilisateur de définir la règle personnalisée pour identifier les bogues de programmation, l'erreur ou l'application de la norme de codage. Avec le support du solveur SMT, l'analyse sera plus précise et réduira considérablement un cas faussement positif. Par exemple
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 ()))Imprimera:
[2, 3]
Parce que z = 1 n'est pas possible en raison de v1 > 4 et v1 < 3 n'est pas satisfaisable
L'architecture du système d'inférence et l'API sont largement inspirées par Astoid, une bibliothèque d'inférence statique utilisée par Pylint.
Klara utilise le système d'inférence pour générer un cas de test, en d'autres termes, il génère un cas de test pour toutes les valeurs de retour possibles de la fonction , au lieu de générer un cas de test pour tout le chemin de contrôle de la fonction.
Pour illustrer le point, considérez la fonction ci-dessous, avec divide by zero vulnérabilités à la ligne 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 générera des entrées de test ci-dessous
import contract_test
def test_foo_0 ():
assert contract_test . foo ( 0 , - 1.0 ) == 0
assert contract_test . foo ( 0 , 0.0 ) == 0.0 Il ne génère pas d'entrée v1 > 10000 , donc le cas de test ne pourrait pas trouver les exceptions. En effet, le s à la ligne 3 n'est pas utilisé dans la valeur de retour.
v1 > 10000 nous modifions la deuxième instruction IF à elif , que nous pourrons retourner le [S] {.
Il s'agit d'une distinction importante avec une autre génération de cas de test automatique disponible maintenant, car en générant uniquement des cas de test pour les valeurs de retour, nous pouvons générer un cas de test minimal, et il est plus facile de personnaliser comment KLARA couvre la fonction.
Par exemple, disons que nous composons un système complexe
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 cellIl n'est pas immédiatement clair pour nous combien il y a de valeurs de retour possibles. Mais nous pouvons utiliser Klara pour générer des entrées instantanément, ci-dessous est le test généré
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 Ci-dessus, 10 résultats totaux, qui est le produit de nnn_right qui ont 2 possibilités et mc qui ont 5 possibilités.
Supposons que l'entrée de 10 tests soit trop, et nous avons déterminé que l'argument options à Component est redondant à tester, nous pouvons utiliser le plugin personnalisé de Klara pour déterminer sélectivement la partie à ignorer dans la génération de tests. Accédez à personnaliser la stratégie de couverture pour plus d'informations.
Après avoir configuré le plugin, Klara générera un test suivant
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 Qui n'est que 2 combinaisons de nnn_right
Étant donné que Klara ne peut pas exécuter dynamiquement le code, il fournira une extension pour spécifier comment déduire le nœud AST ou le type défini par l'utilisateur spécifique pour rendre Klara «plus intelligent». Il est décrit dans l'extension, l'extension du type d'utilisateur et de personnaliser la stratégie de couverture.
Nous utilisons la poésie pour gérer les dépendances. Une fois la poésie installée, exécutez:
$ poetry shell
$ poetry install
Pour exécuter le cas de test, faites:
$ poetry run pytest test
Ce projet est autorisé en vertu des termes de la licence publique générale GNU moins importante.