sloth测试:一种自动单元测试生成器
slothtest的Python项目详细描述
sloth test是一个python库,它根据以前的实际情况自动创建单元测试,以防止出现回归错误。
- 您将把sloth测试库连接到您的项目,并运行该项目以执行典型的例程。
- sloth收集您在项目中使用的类、方法和函数的内部状态,并指出要监视的sloth。它将记录每次运行每种方法的所有可能收入和结果
- 在收集足够的数据后,库会将收集的数据转储到文件中
- 对于此文件中的每个记录运行,sloth test将自动创建一个特定的单元测试,其中包含类的特定状态、特定记录的序列化收入以及此方法的结果断言。 结果是一组典型的pytest单元测试,这些测试可以作为测试例程的一部分执行。
- 对于这个方法的每一次修改,您都可以运行这些创建的测试用例来检查这个方法是否没有新的bug,并实现它应该具有的业务逻辑。
假设我们有一个关键而复杂的方法,它是etl过程的一部分(pd_表是pandas表):
def do_useful_stuff(pd_table=None, a=0, b=0):
for i, row in pd_table.iterrows():
pd_table['value'][i] = row['value'] * a + b
return pd_table
让我们展示一些运行示例,作为etl过程的一部分,我们将通过其他方法实现这些示例:
def run():
tables = {
'table1': pd.DataFrame([{'column': 1, 'value': 1},
{'column': 2, 'value': 2},
{'column': 3, 'value': 4}]),
'table2': pd.DataFrame([{'column': 1, 'value': 1},
{'column': 2, 'value': 2},
{'column': 3, 'value': 4}]),
'table3': pd.DataFrame([{'value': 1},
{'value': 2},
{'value': 4}]),
'table4': pd.DataFrame([{'value': 1000},
{'value': 10}]),
}
for t_name, pd_table in tables.items():
print("Table {name}: \n {table} \n".
format(name=t_name, table=str(do_useful_stuff(pd_table=pd_table, a=2, b=3))))
if __name__ == '__main__':
run()
结果是:
Table table1:
column value
0 1 5
1 2 7
2 3 11
Table table2:
column value
0 1 5
1 2 7
2 3 11
Table table3:
value
0 5
1 7
2 11
Table table4:
value
0 2003
1 23
好的。接下来,我们需要确保这个方法将实现它应该实现的业务逻辑。要做到这一点,我们需要为这种方法手工编写一组pytests,用于不同的收入和结果(对于不同的表变量,可能有100多个测试)。或者使用Sloth测试库自动为我们执行此操作。
- 安装Sloth测试:
pip install slothtest -U
- 第一步-我们需要从slothtest库导入一个@watchme()装饰器。这个decorator应该用在目标方法需要注意的地方。让我们把它添加到函数中:
from slothtest import watchme
@watchme()
def do_useful_stuff(pd_table=None, a=0, b=0):
for i, row in pd_table.iterrows():
pd_table['value'][i] = row['value'] * a + b
- 我们需要一个懒惰的观察者,在哪里开始它的观察过程,在哪里应该停下来观察。它可以是应用程序的入口和出口点,也可以是我们的应用程序内部的逻辑起点和停止轨迹。对于我们的小型应用程序,它是一个run方法,因此我们的代码看起来像:
if __name__ == '__main__':
slothwatcher.start()
run()
slothwatcher.stop()
。仅此而已!
- 现在,让我们像往常一样运行我们的应用程序,让懒人看我们的进程运行。运行之后,在我们的示例中的一个文件夹中,会出现一个新的zip文件,其中包含一个数字文件名(它是一个时间戳)和一个在这个zip文件中运行的转储文件 zip转储是在sloth停止后创建的,或者它记录了观察到的所有方法的一定数量的运行。我们可以通过slothconfig类设置一定数量的运行
from slothtest import SlothConfig
SlothConfig.DUMP_ITER_COUNT = 200
- 现在,我们有一个转储文件。现在,为了进一步开发,我们需要得到一个典型的pytest单元测试。我们可以使用sloth转换器从转储文件创建它:
python -m slothtest.sloth_xml_converter -p o:\work\slothexample -d o:\work\slothexample 1549134821.zip
其中-p是一个目录的键,在该目录中,我们将放置一个指向项目的路径,-d是一个目录的键,在该目录中,将创建结果pytest文件 5个。转换的结果是两个文件:
- 测试_sloth_1549134821.py和2)测试_parval_1549134821.py 第一个是我们关注的函数每次运行的基本pytest集合:
import sloth_test_parval_1549134821 as sl
def test_do_useful_stuff_1():
from themethod import do_useful_stuff
try:
run_result = do_useful_stuff(pd_table=sl.val_do_useful_stuff_1_pd_table, a=sl.val_do_useful_stuff_1_a, b=sl.val_do_useful_stuff_1_b, )
except Exception as e:
run_result = e
test_result = sl.res_do_useful_stuff_1_ret_0
assert(type(run_result) == type(test_result))
assert(run_result.equals(test_result))
def test_do_useful_stuff_2():
from themethod import do_useful_stuff
try:
run_result = do_useful_stuff(pd_table=sl.val_do_useful_stuff_2_pd_table, a=sl.val_do_useful_stuff_2_a, b=sl.val_do_useful_stuff_2_b, )
except Exception as e:
run_result = e
test_result = sl.res_do_useful_stuff_2_ret_0
assert(type(run_result) == type(test_result))
assert(run_result.equals(test_result))
…
第二个是方法每次运行的序列化(如果是原始类型,则为原始值)收入和结果值(4种情况):
import codecs
import io
import joblib
# ===== 1: do_useful_stuff@themethod
var_stream = io.BytesIO()
var_stream_str = codecs.decode('gANdWIu…'.encode(),"base64")
var_stream.write(var_stream_str)
var_stream.seek(0)
val_do_useful_stuff_1_pd_table = joblib.load(var_stream)
val_do_useful_stuff_1_a = 2
val_do_useful_stuff_1_b = 3
res_stream = io.BytesIO()
res_stream_str = codecs.decode('gANdWIu…\n'.encode(),"base64")
res_stream.write(res_stream_str)
res_stream.seek(0)
res_do_useful_stuff_1_ret_0 = joblib.load(res_stream)
…
- 现在我们可以像往常一样使用pytest运行测试例程:
python -m pytest test_sloth_1549134821.py
================================================= test session starts =================================================
platform win32 -- Python 3.7.0, pytest-4.1.1, py-1.7.0, pluggy-0.8.1
rootdir: o:\work\slothexample, inifile:
plugins: remotedata-0.3.1, openfiles-0.3.2, doctestplus-0.2.0, arraydiff-0.3
collected 4 items
test_sloth_1549134821.py .... [100%]
======================================== 4 passed, 2 warnings in 0.34 seconds =========================================
仅此而已。容易的!
如果方法和类是可序列化的,并且有足够的空间进行数据转储,则可以根据需要在任意多的情况下推断自动生成单元测试的方法