我是新来的参数化装置和固定装置,并且仍在学习中。我发现了一些使用间接参数化的文章,但是我很难根据代码中的内容来实现。将不胜感激关于如何实现此目标的任何想法。
我的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.fixture
def 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.fixture
def res_path():
return utils.get_res_path()
#test script
@pytest.fixture
def 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)
#OUTPUT
================================================================================ ERRORS ================================================================================
_______________________________________________________ ERROR collecting test_annotations.py
_______________________________________________________
test_annotations.py:51: in <module>
@pytest.mark.parametrize("get_fus_output", [test_input_df, test_truth_df, res_path], indirect=True)
E NameError: name 'test_input_df' is not defined
======================================================================= short test summary info ========================================================================
ERROR test_annotations.py - NameError: name 'test_input_df' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
=========================================================================== 1 error in 1.46s ===========================================================================
答案 0 :(得分: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
固定装置也与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
的正确情况。就像可以对测试进行参数化一样,fixtures can be parameterized也是如此。从文档中来看不是很清楚(我认为),但是indirect
只是参数化灯具的另一种方法。与您想要的using a fixture in another fixture完全不同。
实际上,对于get_fus_output
使用test_input_df
,test_truth_df
和res_path
固定装置,您根本不需要@pytest.mark.parametrize
行。通常,如果测试功能或灯具的其他参数未使用(例如,@pytest.mark.parametrize
装饰器没有使用),则会自动假定为灯具。
因此,您现有的@pytest.mark.parametrize
并没有达到您的期望。那么,如何设置测试参数?这是一个更大的问题:您正在尝试使用get_fus_output
固定装置为test_annotation_match
创建参数。 这不是您可以使用固定装置完成的事情。
Pytest运行时, first 首先收集所有测试用例,然后然后逐个运行它们。在收集阶段必须准备好测试参数,但是夹具要等到测试阶段才能运行。固定装置中的代码无法帮助进行参数化。您仍然可以通过编程方式生成参数,但是使用夹具无法实现。
您需要做一些事情:
首先,将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)