sloth测试:一种自动单元测试生成器

slothtest的Python项目详细描述


sloth test是一个python库,它根据以前的实际情况自动创建单元测试,以防止出现回归错误。

  1. 您将把sloth测试库连接到您的项目,并运行该项目以执行典型的例程。
  2. sloth收集您在项目中使用的类、方法和函数的内部状态,并指出要监视的sloth。它将记录每次运行每种方法的所有可能收入和结果
  3. 在收集足够的数据后,库会将收集的数据转储到文件中
  4. 对于此文件中的每个记录运行,sloth test将自动创建一个特定的单元测试,其中包含类的特定状态、特定记录的序列化收入以及此方法的结果断言。 结果是一组典型的pytest单元测试,这些测试可以作为测试例程的一部分执行。
  5. 对于这个方法的每一次修改,您都可以运行这些创建的测试用例来检查这个方法是否没有新的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测试库自动为我们执行此操作。

  1. 安装Sloth测试:

pip install slothtest -U

  1. 第一步-我们需要从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

  1. 我们需要一个懒惰的观察者,在哪里开始它的观察过程,在哪里应该停下来观察。它可以是应用程序的入口和出口点,也可以是我们的应用程序内部的逻辑起点和停止轨迹。对于我们的小型应用程序,它是一个run方法,因此我们的代码看起来像:
if __name__ == '__main__':
    slothwatcher.start()
    run()
    slothwatcher.stop()

。仅此而已!

  1. 现在,让我们像往常一样运行我们的应用程序,让懒人看我们的进程运行。运行之后,在我们的示例中的一个文件夹中,会出现一个新的zip文件,其中包含一个数字文件名(它是一个时间戳)和一个在这个zip文件中运行的转储文件 zip转储是在sloth停止后创建的,或者它记录了观察到的所有方法的一定数量的运行。我们可以通过slothconfig类设置一定数量的运行
from slothtest import SlothConfig
SlothConfig.DUMP_ITER_COUNT = 200

  1. 现在,我们有一个转储文件。现在,为了进一步开发,我们需要得到一个典型的pytest单元测试。我们可以使用sloth转换器从转储文件创建它:

python -m slothtest.sloth_xml_converter -p o:\work\slothexample -d o:\work\slothexample 1549134821.zip

其中-p是一个目录的键,在该目录中,我们将放置一个指向项目的路径,-d是一个目录的键,在该目录中,将创建结果pytest文件 5个。转换的结果是两个文件:

  1. 测试_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)
…

  1. 现在我们可以像往常一样使用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 =========================================

仅此而已。容易的!

如果方法和类是可序列化的,并且有足够的空间进行数据转储,则可以根据需要在任意多的情况下推断自动生成单元测试的方法

欢迎加入QQ群-->: 979659372 Python中文网_新手群

推荐PyPI第三方库


热门话题
在ElasticSearch中将SearchHit转换为Java对象   第三方库类的java重写XmlAdapter   java如何使用动画类获得平滑的动画效果?   Java PDFBox如果文本内容超过PDF的第一页,如何添加新页面?   Java二叉搜索树u根到最近叶的距离   java什么是diff Scanner和BufferedReader   java如何设计不生成并行数组的程序   java多次声明变量会降低执行速度吗?   java如何使用JXLAPI读取下拉列表的值   多线程为什么自定义阻塞队列在Java中不是线程安全的   java在一个变量中每输入1000个单位,就从另一个变量中减去1?   java Mapstruct通用映射器   Java中的类能否确定它是否已被修改?   java如何在MogoOperations聚合函数中定义输出类型?