对处理 csv 文件的函数进行单元测试的最佳方法是什么?

Sho*_*tor 8 testing unit-testing pytest python-3.x pandas

我正在尝试使用 Pytest 对处理 csv 文件的函数进行单元测试。虽然我的函数有效,但我觉得在项目目录中创建“示例”csv 文件来测试该函数时有很多代码重复。保存真实数据的实际 csv 文件有数百万条记录。

这些不是我必须在模块中测试的唯一csv 文件,因此了解测试使用不同文件结构的函数的最佳方法将非常有帮助。

现在,我正在创建一个非常短的 csv 文件,该文件通过单行数据以及通过函数处理文件后的预期数据帧输出来模拟实际的文件架构。

也许嘲笑是正确的选择?但我觉得你不应该需要嘲笑这种测试

测试功能

@pytest.mark.parametrize('test_file, expected', [
    (r'Path\To\Project\Output\Folder\mock_sales1.csv',
     pd.DataFrame([['A0A0A0', 1, 4000]], columns=['Postal_Code', 'Store_Num', 'Sales'])),
    (r'Path\To\Project\Output\Folder\mock_sales2.csv',
     pd.DataFrame([['A0A0A0', 1, 4000]], columns=['Postal_Code', 'Store_Num', 'Sales']))
])
def test_sales_dataframe(test_file, expected):
    # This part is repetitive, different tests each need a seperate file written within the test function.
    # Writing sample file to test that files with 7 columns are read correctly.
    mock_mks_sales1 = [['Data0', 'A0A0A0', 1, 'Data3', 'Data4', 'Data5', 4000]]
    with open(r'Path\To\Project\Output\Folder\mock_sales1.csv', 'w') as file:
        writer = csv.writer(file)
        writer.writerows(mock_sales1)
    # Writing sample file to test that files with 8 columns are read correctly.
    mock_mks_sales2 = [['Data0', 'A0A0A0', 1, 'Data3', 'Data4', 'Data5', 'Data6', 4000]]
    with open(r'Path\To\Project\Output\Folder\mock_sales2.csv', 'w') as file:
        writer = csv.writer(file)
        writer.writerows(mock_sales2)

    sales_df = mks_sales_dataframe(test_file)
    testing.assert_frame_equal(expected, sales_df)

    os.remove(r'Path\To\Project\Output\Folder\mock_sales1.csv')
    os.remove(r'Path\To\Project\Output\Folder\mock_sales2.csv')
Run Code Online (Sandbox Code Playgroud)

主功能

def sales_dataframe(file):
    try:
        with open(file, 'r') as f:
            reader = csv.reader(f)
            num_cols = len(next(reader))
            columns = [1, 2, (num_cols - 1)]  # Number of columns is variable, this is used later to accurately specify which columns should be read. This is part I'm testing!

        sales_df = pd.read_csv(file, usecols=columns, names=['Postal_Code', 'Store_Num', 'Sales'])
        return sales_df
    except FileNotFoundError:
        raise FileNotFoundError(file)
Run Code Online (Sandbox Code Playgroud)

测试按预期通过。但是,对于每个不同的测试,我必须在测试函数中创建一个示例 csv 文件,并在测试完成后删除每个文件。正如您可以想象的那样,单个测试函数中有很多重复的代码,感觉相当笨重和冗长,尤其是当测试被参数化时。

Arn*_*del 4

我认为问题在于您的测试输入和预期输出紧密相关,但位于两个不同的位置,一个在参数中,另一个在测试代码中。
如果更改一个参数,除了重复的代码之外,您还需要更改测试的方法主体,这在我看来是不正确的。

我认为您应该拥有参数test(test_data, expected output)并将输入注入临时文件中。
然后调用函数并比较预期输出和实际输出。

@pytest.mark.parametrize('test_data, expected', [
    ([['Data0', 'A0A0A0', 1, 'Data3', 'Data4', 'Data5', 4000]],
      pd.DataFrame([['A0A0A0', 1, 4000]], columns=['Postal_Code', 'Store_Num', 'Sales'])),
    ([['Data0', 'A0A0A0', 1, 'Data3', 'Data4', 'Data5', 'Data6', 4000]],
      pd.DataFrame([['A0A0A0', 1, 4000]], columns=['Postal_Code', 'Store_Num', 'Sales']))
])
def test_sales_dataframe(test_data, expected):

    # Write your test data in a temporary file
    tmp_file = r'Path\To\Project\Output\Folder\tmp.csv';
    with open(tmp_file, 'w') as file:
        writer = csv.writer(file)
        writer.writerows(test_data)

    # Process the data
    sales_df = mks_sales_dataframe(tmp_file)

    # Compare expected and actual output
    testing.assert_frame_equal(expected, sales_df)

    # Clean the temporary file
    os.remove(tmp_file)
Run Code Online (Sandbox Code Playgroud)

您还可以创建 .csv 并将其添加为测试资源,但您的输入和预期输出将位于不同的位置,这不太好。

  • @ShockDoctor 我已经更新了我的答案。就我个人而言,我会删除“@parameterize”,因为它的可读性不太好,并且它会阻止您轻松添加更长的测试用例 (2认同)