【问题标题】:How do I write a unit test for OSError?如何为 OSError 编写单元测试?
【发布时间】:2014-05-30 04:56:54
【问题描述】:

我有以下要测试的 python 代码:

def find_or_make_logfolder(self):
    if not path.isdir(self.logfolder):
        try:
            makedirs(self.logfolder)
        except OSError:
            if not path.isdir(self.logfolder):
                raise

我想在我的单元测试中执行以下操作。

def test_find_or_make_logfolder_pre_existing(self):
    with self.assertRaises(OSError):
        makedirs(self.logfolder)
        find_or_make_logfolder()

但是,if not path.isdir(self.logfolder): 正在检查该目录是否已经存在,以便 except OSError 只会在某些边缘情况下抛出,即程序或人在 if 之后几毫秒成功创建目录,并且在try 之前。

我该如何测试,或者我真的需要测试吗?

当覆盖率达到 100% 时,我倾向于喜欢它。

【问题讨论】:

    标签: python unit-testing testing code-coverage python-unittest


    【解决方案1】:

    mock 库是实现 100% 覆盖率的必备工具。

    模拟出make_dirs() 函数并在其上设置side_effect

    side_effect 允许您执行副作用,包括提高 调用模拟时的异常

    from mock import patch  # or from unittest import mock for python-3.x
    
    @patch('my_module.makedirs')
    def test_find_or_make_logfolder_pre_existing(self, makedirs_mock):
        makedirs_mock.side_effect = OSError('Some error was thrown')
        with self.assertRaises(OSError):
            makedirs(self.logfolder)
    

    【讨论】:

      【解决方案2】:

      您可以采用更 Pythonic 的方式来实现这一点。在 Python 中,哲学是

      请求原谅总比请求许可好。

      EAFP here

      考虑到这一点,您的代码可以编写如下:

      def find_or_make_logfolder(self):
          try:
              makedirs(self.logfolder)
          except OSError:
              #self.logfolder was already a directory, continue on.
              pass
      

      现在要 100% 覆盖此代码,您只需要在目录已经存在的地方创建一个测试用例。

      【讨论】:

        【解决方案3】:

        我在这里发帖迟了,但我想分享我的解决方案(基于this answer),还包括我的mocked 单元测试。

        如果路径不存在,我创建了一个函数来创建路径,例如mkdir -p(并称它为mkdir_p,以方便我记忆)。

        my_module.py

        import os
        import errno
        
        def mkdir_p(path):
            try:
                print("Creating directory at '{}'".format(path))
                os.makedirs(path)
            except OSError as e:
                if e.errno == errno.EEXIST and os.path.isdir(path):
                    print("Directory already exists at '{}'".format(path))
                else:
                    raise
        

        如果os.makedirs 无法创建目录,我们检查OSError 错误号。如果它是errno.EEXIST (==17),并且我们看到路径存在,我们不需要做任何事情(尽管打印一些东西可能会有所帮助)。如果错误号是其他东西,例如errno.EPERM (==13),然后我们抛出异常,因为目录不可用。

        我通过在测试函数中模拟 os 并将错误编号分配给 OSError 来测试它。 (这使用文件 tests/context.py 以允许从父目录轻松导入,正如Kenneth Reitz 所建议的那样。虽然与问题没有直接关系,但为了方便起见,我将其包含在此处完整性。)

        tests/context.py

        import sys
        import os
        sys.path.insert(0, os.path.abspath('..'))
        
        import my_module
        

        tests/my_module_tests.py

        import errno
        import unittest
        
        import mock
        
        from .context import my_module
        
        @mock.patch('my_module.os')
        class MkdirPTests(unittest.TestCase):
            def test_with_valid_non_existing_dir(self, mock_os):
                my_module.mkdir_p('not_a_dir')
                mock_os.makedirs.assert_called_once_with('not_a_dir')
        
            def test_with_existing_dir(self, mock_os):
                mock_os.makedirs.side_effect = OSError(errno.EEXIST, 'Directory exists.')
                mock_os.path.isdir.return_value = True
                my_module.mkdir_p('existing_dir')
                mock_os.path.isdir.assert_called_once_with('existing_dir')
        
            def test_with_permissions_error(self, mock_os):
                mock_os.makedirs.side_effect = OSError(errno.EPERM, 'You shall not pass!')
                with self.assertRaises(OSError):
                    my_module.mkdir_p('forbidden_dir')
        

        【讨论】:

        • 我不认为这个例子“分配错误号”,错误号是作为 args 传递的,它没有做任何特别的事情。 Python 3.6.9:In [1]: err = OSError(os.errno.EEXIST)In [2]: err.errno is NoneOut[2]: TrueIn [3]: err.argsOut[3]: (17,)
        【解决方案4】:

        还有很多其他情况会引发 OSError,例如文件系统已满、权限不足、文件已存在等

        在这种情况下,权限很容易被利用 - 只需将self.logfolder 设置为您的进程没有写入权限的不存在目录,例如在 *nix 中假设您在根目录中没有写权限:

        >>> import os
        >>> os.makedirs('/somedir')
        OSError: [Errno 13] Permission denied: '/somedir'
        

        另外,请考虑 Martin Konecny 建议的重构。

        【讨论】:

        • 附注这只是在单元测试中咬我。有人运行了测试套件,但没有引发异常。他们的盒子有很大的问题,这可能假设了非疯狂的权限——但这仍然是一个假设,并且被证明是错误的。我现在明确设置一个临时目录,保证没有权限。
        • 答案中提到了假设。使用 /root 只是一个建议,因为它是非 root 用户不应该能够写入的已知目录。按照alecxe的建议看模拟可能会更好
        • 是的,我基本上滚动了类似的东西,制作了一个临时目录并手动设置了权限。我没有在我的测试中使用 /root,我使用了一个假目录(/should/not/exist 或类似的)。
        猜你喜欢
        • 2018-01-17
        • 2012-01-06
        • 2019-03-17
        • 2016-04-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多