Klara es una herramienta de análisis estático para generar un caso de prueba automático, basado en el solucionador SMT (Z3), con un poderoso sistema de inferencia de nivel AST. Klara tomará el archivo de Python como entrada y generará el archivo de prueba correspondiente en formato Pytest, que intentan cubrir todos los valores de retorno. Por ejemplo, la siguiente función en el archivo 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"generará
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'Vea la documentación de Klara en https://klara-py.readthedocs.io
Nota : Klara todavía está en la etapa experimental temprana, las características faltantes notables son el bucle, la comprensión, la importación del módulo, las excepciones y muchas más. Ver limitaciones para la lista completa. Probablemente no se ejecutará en proyectos del mundo real, por lo que es mejor elegir algunas funciones interesantes para generar el caso de prueba correspondiente.
Klara se puede instalar a través de la herramienta pip utilizando:
pip install klara
Podemos invocar klara en cualquier archivo fuente de Python, y generará un archivo de prueba de Pytest correspondiente.
$ 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, '' ) == 0Consulte el manual de inicio rápido para obtener más ejemplos y orientación. Para usarlo como una biblioteca de análisis estático, vaya a la inferencia.
Klara funciona a nivel AST y no ejecuta el código de usuario de ninguna manera, lo cual es una diferencia muy importante en comparación con una herramienta similar como Crosshair y PynGuin que utilizan la ejecución simbólica concólica que requirió la ejecución del código de usuario que podría causar efectos secundarios no deseados. Klara trabaja a nivel AST, combine con el análisis de flujo de datos que utiliza el gráfico de flujo de control (CFG), la asignación única estática (SSA), la cadena de uso de uso, etc ... para construir un poderoso sistema de inferencia de Python que aprovecha el solucionador Z3 para la resolución de restricciones y la verificación de viabilidad de la ruta. Debido a esto, Klara puede operar tanto en el código fuente de Python2/3 con la ayuda de Typed_ast. Para especificar, el código fuente está en Python 2, pase en -py 2 argumento. Es Python 3 por defecto.
Klara también se puede utilizar como una herramienta de análisis estático, permitiendo al usuario definir una regla personalizada para identificar errores de programación, error o hacer cumplir el estándar de codificación. Con el soporte de solucionadores SMT, el análisis será más preciso y reducirá en gran medida el caso falso positivo. Por ejemplo
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 ()))Imprimirá:
[2, 3]
Porque z = 1 no es posible debido a v1 > 4 y v1 < 3 es insatisfactorio
La arquitectura y la API del sistema de inferencia se inspiran en gran medida en el Atroides, una biblioteca de inferencia estática utilizada por Pylint.
Klara utiliza el sistema de inferencia para generar un caso de prueba, en otras palabras, genere un caso de prueba para todos los valores de retorno posibles de la función , en lugar de generar un caso de prueba para toda la ruta de control de la función.
Para ilustrar el punto, considere la función a continuación, con divide by zero vulnerabilidades en la línea 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 generará entradas de prueba a continuación
import contract_test
def test_foo_0 ():
assert contract_test . foo ( 0 , - 1.0 ) == 0
assert contract_test . foo ( 0 , 0.0 ) == 0.0 No genera entrada v1 > 10000 , por lo que el caso de prueba no podría averiguar las excepciones. Esto se debe a que el s en la línea 3 no se usa en el valor de retorno.
Si modificamos la segunda instrucción IF a elif , que podremos devolver el [S] {. Title-Ref} en la línea 3, Klara generará entradas de prueba que cubran el caso v1 > 10000 .
Esta es una distinción importante con otra generación de casos de prueba automáticos disponibles ahora, porque solo generar un caso de prueba para los valores de retorno, podemos generar un caso de prueba mínimo, y es más fácil personalizar cómo Klara cubre la función.
Por ejemplo, digamos que estamos componiendo un sistema complejo
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 cellNo nos está claro de inmediato cuántos valores de retorno posibles hay. Pero podemos utilizar Klara para generar entradas al instante, a continuación se encuentra la prueba generada
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 Arriba generó 10 resultados totales, que es producto de nnn_right que tienen 2 posibilidades y mc que tienen 5 posibilidades.
Supongamos que 10 entrada de pruebas es demasiado, y hemos determinado que el argumento options al Component es redundante de probar, podemos usar el complemento personalizado de Klara para determinar selectivamente qué parte ignorar en la generación de pruebas. Vaya a personalizar la estrategia de cobertura para obtener más información.
Después de haber configurado el complemento, Klara generará la siguiente prueba
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 Que son solo 2 combinaciones de nnn_right
Debido a que Klara no puede ejecutar dinámicamente el código, proporcionará una extensión para especificar cómo inferir el nodo AST específico o el tipo definido por el usuario para hacer que Klara sea 'más inteligente'. Se describe para extender, extender el tipo de usuario y personalizar la estrategia de cobertura.
Usamos poesía para administrar las dependencias. Después de instalar la poesía, ejecute:
$ poetry shell
$ poetry install
Para ejecutar el caso de prueba, hacer:
$ poetry run pytest test
Este proyecto tiene licencia bajo los términos de la Licencia Pública General Menor de GNU.