8

How to test a function in Python which creates a directory? Can you give an example make_directory_test() function?

def make_directory(directory):
    if not os.path.exists(directory):
        os.makedirs(directory)
Valeriy
  • 1,365
  • 3
  • 18
  • 45

2 Answers2

9

Just test that directory exists

def test_make_directory(self):
    directory_path = '...' # somepath

    make_directory(directory_path)

    self.assertTrue(os.path.exists(directory_path))

And do ensure that any directories created in unit tests are deleted after each tests in tearDown or setUp, to ensure independency of tests

hspandher
  • 15,934
  • 2
  • 32
  • 45
  • 2
    Missing a ) on assert true. – marsh Sep 23 '15 at 19:47
  • 2
    I think a `self.assertFalse(os.path.exists(directory_path))` before the `make_directory` would improve things. – Shawn Mehan Sep 23 '15 at 19:48
  • 1
    I think that would be unnecessary, as I mentioned all unit tests should be independent. So something like that should always be written in tearDown of any test that uses file system. – hspandher Sep 23 '15 at 19:50
7

A good practice here would be to learn how to use testing frameworks like mock or flexmock

Furthermore, you should use Python's unittest Framework.

Your objective here is not necessarily to make sure that something gets created, but rather that it gets called. So what you would want to do is mock out the external calls to help test the proper flow of your method. So in this case you should mock exists and makedirs. Then ensure that it gets called. That would be an acceptable unit test. You would do something like this:

Let us assume your main code is being housed in a module called my_module.py. So you would want to create a test file, let's call it test.py to keep it simple and do something like this in your unit test:

from mock import patch
import my_module
import unittest


class MyTest(unittest.TestCase):
    @patch('my_module.exists')
    @patch('my_module.makedirs')
    def test_create_dir(self, mock_make_dirs, mock_exists):
        mock_exists.return_value = True

        make_directory('thing_to_create')

        mock_make_dirs.assert_called_with('thing_to_create')

So, what's happening here is that you are telling the mock that you want to mock out the makedirs and exists. You are making exists return with a True as specified with the mock_exists.return_value. Then you are making your actual call, your mocks will take effect in that method call. The last line with mock_make_dirs.assert_called_with will ensure that your method for making the dir will actually be called.

If you want to still test that something does actually get created

What you can do in this case, is maybe venture down the path of using a context manager and create a temporary folder to test in, do your work to test your method and anything else you have to do, and once you are done your work, the context manager will destroy itself.

For information on context managers, check out contextlib

idjaw
  • 25,487
  • 7
  • 64
  • 83
  • shouldnt the patch functions use `@patch('os.path.exists')` etc. ? – Vince Miller Mar 18 '19 at 16:42
  • 1
    Hi @VinceMiller. The rule of thumb with mock patching is you mock with respect to where you are testing and not where the module is defined. The docs actually explain this really well [here](https://docs.python.org/3/library/unittest.mock.html#where-to-patch). – idjaw Mar 18 '19 at 16:57
  • I am still little bit confused, hope you can clarify. I think we have to patch `os.path.exists` instead of `module`, otherwise it's going to be `AttributeError`. It is similar to this [post](https://stackoverflow.com/questions/22201583/patch-over-a-function-imported-inside-another-function). – GabrielChu Jun 19 '19 at 01:33
  • Hi @GabrielChu. I wrote up a very quick example to help illustrate [here](https://github.com/idjaw/testing_example). Use pytest to run the tests. All but one of the tests will fail. Specifically for the `os.path.exists` example, because this is a library within Python, if you mock `os.path.exists`, it *will* still work, but we want to avoid that, because that will actually mock it across all modules. We are only interested in mocking the current system under test, which is where the specific method is we want to test. [cont'd in next message] – idjaw Jun 19 '19 at 03:02
  • @GabrielChu - By performing the mock with the path relative to where we are testing we are only mocking it within that context. Now, this brings to the example I linked. The failing test shows you what happens when you actually try to mock a module that "you" wrote by referencing its "definition" path rather than its usage in the portion of the code you are currently testing. I think the example provided should help clarify what's going on. Feel free to mess around with it and experiment. – idjaw Jun 19 '19 at 03:04
  • Also, @GabrielChu just a comment about the post you linked. I believe part of the reason it was mocked like that is because the example written in that answer the "real" code and test code were in the same file. But, this still should not take away from the fact that you should usually always mock with respect to the system under test. Just as a reminder, here is the documentation on it: [here](https://docs.python.org/3/library/unittest.mock.html#where-to-patch) – idjaw Jun 19 '19 at 03:19