How do I write a unit test for OSError?

大憨熊 提交于 2019-12-24 14:25:15

问题


I have the following python code which I want to test:

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

I want to do something like the following in my unittest.

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

However, if not path.isdir(self.logfolder): is checking if the directory already exists or not so that the except OSError will only be thrown in some edge case where a program or person succeeds in making the directory a few milliseconds after the if and before the try.

How do I test this, or do I really need to test this?

I tend to like it when coverage says 100%.


回答1:


You could go a more Pythonic route to achieve this. In Python the philosophy is

It's better to ask for forgiveness than to ask for permission.

See EAFP here

With that in mind, your code could be written as follows:

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

Now to get 100% of this code covered, you will just need to create a test case where the directory already exists.




回答2:


mock library is a must-have tool for achieving a 100% coverage.

Mock out make_dirs() function and set a side_effect on it:

side_effect allows you to perform side effects, including raising an exception when a mock is called

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)



回答3:


There are plenty of other situations that raise OSError, e.g. filesystem full, insufficient permissions, file already exists etc.

In this case permissions is an easy one to exploit - simply arrange for self.logfolder to be set to a nonexistent directory for which your process does not have write permission, e.g. in *nix assuming that you do not have write permission in the root directory:

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

Also, consider Martin Konecny's suggested refactor.




回答4:


I'm late posting here, but I wanted to share my solution (based on this answer), and also include my mocked unit tests.

I made a function to create a path if it doesn't exist, like mkdir -p (and called it mkdir_p to facilitate my remembering it).

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

If os.makedirs is unable to create the directory, we check the OSError error number. If it is errno.EEXIST (==17), and we see that the path exists, we don't need to do anything (although printing something could be helpful). If the error number is something else, e.g. errno.EPERM (==13), then we raise the exception, because the directory is not available.

I'm testing it by mocking os and assigning error numbers to OSError in the test functions. (This uses a file tests/context.py to allow easy importing from the parent directory, as suggested by Kenneth Reitz. Although not directly related to the question, I'm including it here for the sake of completeness.)

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')


来源:https://stackoverflow.com/questions/23947312/how-do-i-write-a-unit-test-for-oserror

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!