Gop*_*h S 4 python unit-testing pytest python-3.x pandas
我是参数化和装置的新手,仍在学习。我发现了一些使用间接参数化的帖子,但根据我的代码中的内容,我很难实现。如果有任何关于我如何实现这一目标的想法,我将不胜感激。
我的 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()
Run Code Online (Sandbox Code Playgroud)
#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)
Run Code Online (Sandbox Code Playgroud)
#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 ===========================================================================
Run Code Online (Sandbox Code Playgroud)
Nic*_*ick 10
我不是 100% 确定我理解你在这里想要做什么,但我认为你对参数化和夹具作用的理解是不正确的。看来您正在尝试使用固定装置为您的测试创建参数列表,这实际上并不是正确的方法(正如您所看到的,您这样做的方式肯定行不通) 。
为了充分解释如何解决这个问题,首先,让我介绍一下如何使用参数化和夹具的背景知识。
我认为这里的任何内容都不应该是新的,只是为了确保我们在同一页面上:
通常,在 Pytest 中,一个test_*函数就是一个测试用例:
def test_square():
assert square(3) == 9
Run Code Online (Sandbox Code Playgroud)
如果您想使用不同的数据进行相同的测试,您可以编写单独的测试:
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
Run Code Online (Sandbox Code Playgroud)
这不太好,因为它违反了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
Run Code Online (Sandbox Code Playgroud)
Fixtures 也是关于DRY代码的,但是方式不同。
假设您正在编写一个网络应用程序。您可能有多个测试需要连接到数据库。您可以向每个测试添加相同的代码以打开并设置测试数据库,但这绝对是重复自己。例如,如果您切换数据库,则需要更新大量测试代码。
夹具是允许您进行一些可用于多个测试的设置(以及可能的拆卸)的功能:
@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 = []
Run Code Online (Sandbox Code Playgroud)
好的,了解了这些背景,让我们深入研究您的代码。
第一个问题是你的@pytest.mark.parametrize装饰器:
@pytest.mark.parametrize("get_fus_output", [test_input_df, test_truth_df, res_path], indirect=True)
Run Code Online (Sandbox Code Playgroud)
这不是使用的正确情况indirect。就像测试可以参数化一样,夹具也可以参数化。从文档中来看,这并不是很清楚(在我看来),但这indirect只是参数化装置的另一种方法。这与在另一个灯具中使用一个灯具完全不同,这正是您想要的。
事实上,要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 运行时,首先收集所有测试用例,然后逐一运行它们。测试参数必须在收集阶段准备好,但夹具直到测试阶段才会运行。夹具内部的代码无法帮助参数化。您仍然可以通过编程方式生成参数,但夹具不是这样做的方法。
您需要做一些事情:
首先,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)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3944 次 |
| 最近记录: |