为了账号安全,请及时绑定邮箱和手机立即绑定

Pytest:使用使用另一个夹具作为输入的夹具参数化单元测试

Pytest:使用使用另一个夹具作为输入的夹具参数化单元测试

料青山看我应如是 2023-04-11 15:03:10
我是参数化和固定装置的新手,并且还在学习。我发现了一些使用间接参数化的帖子,但根据我的代码中的内容,我很难实现。我将不胜感激关于如何实现这一目标的任何想法。我的 conftest.py 中有几个固定装置,它们为我的测试文件中的函数“get_fus_output()”提供输入文件。该函数处理输入并生成两个数据帧以在我的测试中进行比较。此外,我将根据共同值 ('Fus_id') 转租这两个 DF 以单独测试它们。所以这个函数的输出将是 [(Truth_df1, test_df1),(Truth_df2, test_df2)...] 只是为了参数化每个测试和真值 df 的测试。不幸的是,我无法在我的测试函数“test_annotation_match”中使用它,因为这个函数需要一个夹具。我无法将夹具作为输入提供给另一个夹具进行参数化。是的,它在 pytest 中不受支持,但无法找出间接参数化的解决方法。#fixtures from conftest.py@pytest.fixture(scope="session")def test_input_df(fixture_path):    fus_bkpt_file = os.path.join(fixture_path, 'test_bkpt.tsv')    test_input_df= pd.read_csv(fus_bkpt_file, sep='\t')    return test_input_df@pytest.fixturedef test_truth_df(fixture_path):    test_fus_out_file = os.path.join(fixture_path, 'test_expected_output.tsv')    test_truth_df = pd.read_csv(test_fus_out_file, sep='\t')    return test_truth_df@pytest.fixturedef res_path():    return utils.get_res_path()#test script@pytest.fixturedef get_fus_output(test_input_df, test_truth_df, res_path):    param_list = []    # get output from script    script_out = ex_annot.run(test_input_df, res_path)    for index, row in test_input_df.iterrows():        fus_id = row['Fus_id']         param_list.append((get_frame(test_truth_df, fus_id), get_frame(script_out, fus_id)))        # param_list eg : [(Truth_df1, test_df1),(Truth_df2, test_df2)...]    print(param_list)    return param_list@pytest.mark.parametrize("get_fus_output", [test_input_df, test_truth_df, res_path], indirect=True)def test_annotation_match(get_fus_output):    test, expected = get_fusion_output    assert_frame_equal(test, expected, check_dtype=False, check_like=True)
查看完整描述

1 回答

?
DIEA

TA贡献1820条经验 获得超2个赞

我不是 100% 确定我理解你在这里试图做什么,但我认为你对参数化和固定装置作用的理解是不正确的。看起来您正在尝试使用固定装置为您的测试创建参数列表,这并不是真正正确的方法(而且您这样做的方式肯定行不通,正如您所看到的) .


为了全面解释如何解决这个问题,首先,让我介绍一下参数化和固定装置的使用背景。


参数化

我不认为这里有任何新内容,只是为了确保我们在同一页面上:


通常,在 Pytest 中,一个test_*函数就是一个测试用例:


def test_square():

    assert square(3) == 9

如果你想做相同的测试但使用不同的数据,你可以编写单独的测试:


def test_square_pos():

    assert square(3) == 9


def test_square_frac():

    assert square(0.5) == 0.25


def test_square_zero():

    assert square(0) == 0


def test_square_neg():

    assert square(-3) == 9

这不是很好,因为它违反了DRY原则。参数化是解决这个问题的方法。您可以通过提供测试参数列表将一个测试用例变成多个:


@pytest.mark.parametrize('test_input,expected',

                         [(3, 9), (0.5, 0.25), (0, 0), (-3, 9)])

def test_square(test_input, expected):

    assert square(test_input) == expected

固定装置

Fixture 也与DRY代码有关,但方式不同。


假设您正在编写一个 Web 应用程序。您可能有多个测试需要连接到数据库。您可以向每个测试添加相同的代码以打开和设置测试数据库,但这肯定是在重复您自己。比方说,如果您切换数据库,则需要更新大量测试代码。


夹具是允许您进行一些可用于多个测试的设置(以及可能的拆卸)的功能:


@pytest.fixture

def db_connection():

    # Open a temporary database in memory

    db = sqlite3.connect(':memory:')

    # Create a table of test orders to use

    db.execute('CREATE TABLE orders (id, customer, item)')

    db.executemany('INSERT INTO orders (id, customer, item) VALUES (?, ?, ?)',

                   [(1, 'Max', 'Pens'),

                    (2, 'Rachel', 'Binders'),

                    (3, 'Max', 'White out'),

                    (4, 'Alice', 'Highlighters')])

    return db      


def test_get_orders_by_name(db_connection):

    orders = get_orders_by_name(db_connection, 'Max')

    assert orders = [(1, 'Max', 'Pens'),

                     (3, 'Max', 'White out')]


def test_get_orders_by_name_nonexistent(db_connection):

    orders = get_orders_by_name(db_connection, 'John')

    assert orders = []

修正你的代码

好的,在了解了背景知识后,让我们深入研究您的代码。


第一个问题是你的@pytest.mark.parametrize装饰器:


@pytest.mark.parametrize("get_fus_output", [test_input_df, test_truth_df, res_path], indirect=True)

这不是使用indirect. 就像测试可以参数化一样,夹具也可以参数化。从文档中看不是很清楚(在我看来),但这indirect只是参数化固定装置的另一种方法。这与在另一个 fixture 中使用 fixture完全不同,这正是您想要的。


事实上,要get_fus_output使用test_input_df、test_truth_df和fixtures,您根本res_path不需要这条线。通常,如果没有以其他方式使用(例如,由装饰器),@pytest.mark.parametrize测试函数或夹具的任何参数都会自动假定为夹具。@pytest.mark.parametrize


所以,你现有的@pytest.mark.parametrize并没有按照你的期望去做。那么你如何参数化你的测试呢?这会遇到更大的问题:您正在尝试使用get_fus_output夹具为 . 创建参数test_annotation_match。这不是你可以用夹具做的事情。


Pytest运行时,首先收集所有的测试用例,然后一个一个运行。测试参数必须在收集阶段准备就绪,但 fixture 直到测试阶段才会运行。夹具内的代码无法帮助进行参数化。您仍然可以通过编程方式生成参数,但固定装置不是这样做的方法。


你需要做几件事:


首先,将get_fus_output夹具转换为常规函数。这意味着删除@pytest.fixture装饰器,但您还必须更新它以不使用test_input_df test_truth_df, 和res_path固定装置。(如果没有其他东西需要它们作为固定装置,您可以将它们全部转换为常规函数,在这种情况下,您可能希望将它们放在它们自己的模块之外,或者只是将它们移动到同一个测试脚本中。conftest.py)


然后,@pytest.mark.parametrize需要使用该函数来获取参数列表:


@pytest.mark.parametrize("expected,test", get_fus_output())

def test_annotation_match(expected, test):

    assert_frame_equal(test, expected, check_dtype=False, check_like=True)


查看完整回答
反对 回复 2023-04-11
  • 1 回答
  • 0 关注
  • 198 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信