Klara - это статические инструменты анализа для автоматического генерации тестового примера, основанного на решателе SMT (Z3), с мощной системой вывода уровня AST. Клара примет файл Python в качестве входного ввода и генерирует соответствующий тестовый файл в формате Pytest, которые пытаются охватить все возвращаемые значения. Например, следующая функция в файле 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"будет генерировать
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'Смотрите документацию Клары по адресу https://klara-py.readthedocs.io
Примечание . Клара все еще находится на ранней стадии эксперимента, заметными недостающими функциями являются цикл, понимание, импорт модуля, исключения и многие другие. Смотрите ограничения для полного списка. Вероятно, он не будет работать в реальных проектах, поэтому лучше всего подготовить несколько интересных функций для создания соответствующего тестового примера.
Клара может быть установлен через инструмент pip с помощью:
pip install klara
Мы можем вызвать klara в любом исходном файле Python, и он будет генерировать соответствующий файл теста Pytest.
$ 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, '' ) == 0Проконсультируйтесь с руководством быстрого начала для получения дополнительных примеров и руководства. Чтобы использовать его в качестве статической библиотеки анализа, перейдите к выводу.
Klara работает на уровне AST, и он не выполняет код пользователя каким -либо образом, что является очень важным отличием по сравнению с аналогичным инструментом, таким как Crosshair и Pynguin, в котором используется коммерческое символическое выполнение, которое требовало выполнения кода пользователя, которое может вызвать нежелательные побочные эффекты. Клара работает на уровне AST, в сочетании с анализом потока данных, который использует график потока управления (CFG), статическое отдельное назначение (SSA), цепочку использования и т. Д., Чтобы построить мощную систему вывода Python, которая использует Z3-резал для решения ограничений и проверки осуществимости пути. Из -за этого Клара может работать на исходном коде Python2/3 с помощью typed_ast. Чтобы указать исходный код в Python 2, передайте аргумент -py 2 . Это Python 3 по умолчанию.
Клара также может использоваться в качестве инструмента статического анализа, позволяя пользователю определить пользовательское правило для идентификации ошибок программирования, ошибок или обеспечения соблюдения стандарта кодирования. При поддержке SMT Solver анализ будет более точным и значительно уменьшит ложноположительный случай. Например
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 ()))Будет распечатать:
[2, 3]
Поскольку z = 1 невозможно из -за v1 > 4 , а v1 < 3 неудовлетворимо
Архитектура системы вывода и API в значительной степени вдохновлены астроидом, статической библиотекой выводов, используемой Pylint.
Klara использует систему вывода для генерации тестового примера, другими словами, она генерирует тестовый пример для всех возможных возвращаемых значений функции , а не генерировать тестовый пример для всего пути управления функцией.
Чтобы проиллюстрировать эту точку, рассмотрим функцию ниже, с divide by zero уязвимостям в строке 3
def foo ( v1 : int , v2 : float ):
if v1 > 10000 :
s = v1 / 0 # unused statement
if v1 > v2 :
s = v1
else :
s = v2
return sКлара будет генерировать тестовые входные данные ниже
import contract_test
def test_foo_0 ():
assert contract_test . foo ( 0 , - 1.0 ) == 0
assert contract_test . foo ( 0 , 0.0 ) == 0.0 Он не генерирует вход v1 > 10000 , поэтому тестовый пример не сможет выяснить исключения. Это потому, что s в строке 3 не используется в возвратном значении.
Если мы изменим второй оператор, если на elif , который мы сможем вернуть [s] {. Title-Ref} в строке 3, Klara генерирует входные данные, которые охватывают случай v1 > 10000 .
Это важное различие с другой генерацией автоматического тестового примера, доступной в настоящее время, поскольку, создавая только тестовый пример для возвращаемых значений, мы можем генерировать минимальный тестовый пример, и легче настроить, как Klara охватывает функцию.
Например, скажем, мы сочиняем сложную систему
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 cellНам не сразу ясно, сколько возможных возвращаемых значений. Но мы можем использовать klara для мгновенного генерации входов, ниже - сгенерированный тест
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 Выше приведено 10 общих результатов, которые являются продуктом nnn_right , которые имеют 2 возможности и mc , которые имеют 5 возможностей.
Предположим, что вход 10 тестов слишком много, и мы определили, что аргумент options для Component является избыточным для тестирования, мы можем использовать пользовательский плагин Клары, чтобы выборочно определить, какую часть игнорировать в генерации тестов. Перейдите, чтобы настроить стратегию покрытия для получения дополнительной информации.
После настройки плагин, Klara будет генерировать после тестирования
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 Это всего лишь 2 комбинации nnn_right
Поскольку Клара не может динамически выполнить код, она предоставит расширение, чтобы указать, как вывести конкретный узел AST или пользовательский тип, чтобы сделать Klara «умнее». Он описан в расширении, расширении типа пользователя и настройке стратегии покрытия.
Мы используем поэзию для управления зависимостями. После установки поэзии запустите:
$ poetry shell
$ poetry install
Чтобы запустить тестовый пример, сделайте:
$ poetry run pytest test
Этот проект лицензируется в соответствии с условиями Медленной общей публичной лицензии GNU.