Python unit test that uses an external data file

前端 未结 4 1380
执念已碎
执念已碎 2021-02-06 22:29

I have a Python project that I\'m working on in Eclipse and I have the following file structure:

/Project
    /projectname
        module1.py
        module2.py          


        
4条回答
  •  暗喜
    暗喜 (楼主)
    2021-02-06 23:30

    In my opinion the best way to handle these cases is to program via inversion of control.

    In the two sections below I primarily show how a no-inversion-of-control solution would look like. The second section shows a solution with inversion of control and how this code can be tested without a mocking-framework.

    In the end I state some personal pros and cons that do not at all have the intend to be correct and or complete. Feel free to comment for augmentation and correction.

    No inversion of control (no dependency injection)

    You have a class that uses the std open method from python.

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

    Here I have used open explicitly in my code, so the only way to write tests for it would be to use explicit test-data (files) or use a mocking-framework like Dunes suggests. But there is still another way:

    My suggestion: Inversion of control (with dependency injection)

    Now I rewrote the class differently:

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

    In this second example I injected the dependency for open into the constructor (Constructor Dependency Injection).

    Writing tests for inversion of control

    Now I can easily write tests and use my test version of open when I need it:

    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

    Pro Dependency Injection

    • no need to learn mocking framework for tests
    • complete control over the classes and methods that have to be faked
    • also changing and evolving your code is easier in general
    • code quality normally improves, as one of the most important factors is being able to respond to changes as easy as possible
    • using dependency injection and a dependency injection framework is generally a respected way to work on a project https://en.wikipedia.org/wiki/Dependency_injection

    Con Dependency Injection

    • a little bit more code to write in general
    • in tests not as short as patching a class via @patch
    • constructors can get overloaded with dependencies
    • you need to somehow learn to use dependency-injection

提交回复
热议问题