使用外部数据文件的Python单元测试

时间:2015-09-11 16:17:04

标签: python unit-testing

我有一个我在Eclipse中工作的Python项目,我有以下文件结构:

/Project
    /projectname
        module1.py
        module2.py 
        # etc.
    /test
        testModule1.py
        # etc.
        testdata.csv

在我的一个测试中,我创建了一个给出'testdata.csv'作为参数的类的实例。该对象执行open('testdata.csv')并读取内容。

如果我只用unittest运行这个单个测试文件,一切正常,文件就可以找到并正确读取。但是,如果我尝试运行所有单元测试(即通过右键单击test目录而不是单个测试文件运行),则会收到无法找到文件的错误。

有没有办法绕过这个(除了提供绝对路径,我不想这样做)?

4 个答案:

答案 0 :(得分:22)

通常我所做的是定义

THIS_DIR = os.path.dirname(os.path.abspath(__file__))

位于每个测试模块的顶部。然后,您所处的工作目录并不重要 - 文件路径始终与测试模块所在的位置相同。

然后我在我的测试(或测试设置)中使用这样的东西:

my_data_path = os.path.join(THIS_DIR, os.pardir, 'data_folder/data.csv')

或者在您的情况下,因为数据源位于测试目录中:

my_data_path = os.path.join(THIS_DIR, 'testdata.csv')

答案 1 :(得分:12)

访问文件系统的单元测试通常不是一个好主意。这是因为测试应该是自包含的,通过将测试数据放在测试之外,csv文件所属的测试不再是显而易见的,或者即使它仍在使用中。

一个更好的解决方案是修补open并使其返回类似文件的对象。

from unittest import TestCase
from unittest.mock import patch, mock_open

from textwrap import dedent

class OpenTest(TestCase):
    DATA = dedent("""
        a,b,c
        x,y,z
        """).strip()

    @patch("builtins.open", mock_open(read_data=DATA))
    def test_open(self):

        # Due to how the patching is done, any module accessing `open' for the 
        # duration of this test get access to a mock instead (not just the test 
        # module).
        with open("filename", "r") as f:
            result = f.read()

        open.assert_called_once_with("filename", "r")
        self.assertEqual(self.DATA, result)
        self.assertEqual("a,b,c\nx,y,z", result)

答案 2 :(得分:0)

我认为处理这些情况的最好方法是通过控制反转进行编程。

在下面的两节中,我主要显示无反转控制解决方案的外观。第二部分显示了控制反转的解决方案,以及如何在没有模拟框架的情况下测试此代码。

最后,我指出一些个人利与弊,一点都不是正确无误的。随意评论以进行扩充和更正。

无控制权倒置(无依赖项注入)

您有一个使用python中的std open方法的类。

class UsesOpen(object):
  def some_method(self, path):
    with open(path) as f:
      process(f)

# how the class is being used in the open
def main():
  uses_open = UsesOpen()
  uses_open.some_method('/my/path')

这里我在代码中显式使用了open,因此为其编写测试的唯一方法是使用显式测试数据(文件)或使用沙丘建议的模拟框架。 但是还有另一种方法:

我的建议:控制反转(带有依赖注入)

现在,我以不同的方式重写了课堂:

class UsesOpen(object):
  def __init__(self, myopen):
    self.__open = myopen

  def some_method(self, path):
    with self.__open(path) as f:
      process(f)

# how the class is being used in the open
def main():
  uses_open = UsesOpen(open)
  uses_open.some_method('/my/path')

在第二个示例中,我将open的依赖项注入到构造函数中(构造函数依赖项注入)。

控制反转的写作测试

现在,我可以轻松编写测试并在需要时使用open的测试版本:

EXAMPLE_CONTENT = """my file content
as an example
this can be anything"""

TEST_FILES = {
  '/my/long/fake/path/to/a/file.conf': EXAMPLE_CONTENT
}

class MockFile(object):
  def __init__(self, content):
    self.__content = content
  def read(self):
    return self.__content

  def __enter__(self):
    return self
  def __exit__(self, type, value, tb):
    pass

class MockFileOpener(object):
  def __init__(self, test_files):
    self.__test_files = test_files

  def open(self, path, *args, **kwargs):
    return MockFile(self.__test_files[path])

class TestUsesOpen(object):
  def test_some_method(self):
    test_opener = MockFileOpener(TEST_FILES)

    uses_open = UsesOpen(test_opener.open)

    # assert that uses_open.some_method('/my/long/fake/path/to/a/file.conf')
    # does the right thing

Pro / Con

专业依赖注入

  • 无需学习测试的模拟框架
  • 完全控制必须伪造的类和方法
  • 通常,更改和发展代码也更容易
  • 代码质量通常会提高,这是最重要的条件之一 因素能够尽可能轻松地应对变化
  • 使用依赖注入和依赖注入框架 通常是在项目https://en.wikipedia.org/wiki/Dependency_injection
  • 上受尊重的工作方式

Con依赖注入

  • 一般编写的代码要多一些
  • 在测试中不如通过@patch修补类
  • 构造函数可能会因依赖项而超载
  • 您需要以某种方式学习来使用依赖项注入

答案 3 :(得分:-3)

您的测试不应直接打开文件,每个测试都应复制文件并使用其副本。