Home > Mobile >  Testing function taking multiple files
Testing function taking multiple files

Time:11-12

I have a function that takes 3 files and returns a tuple with the data from the files. For example:

First file:

SVF2018-05-24_12:02:58.917

NHR2018-05-24_12:02:49.914

Second file:

SVF2018-05-24_1:04:11.332

NHR2018-05-24_1:04:02.979

Third file:

SVF_Sebastian Vettel_FERRARI

NHR_Nico Hulkenberg_RENAULT

And I get such result:

result = (
        [('SVF', '2018-05-24_12:02:58.917'), ('NHR', '2018-05-24_12:02:49.914')],
        [('SVF', '2018-05-24_1:04:11.332'), ('NHR', '2018-05-24_1:04:02.979')],
        [['SVF', 'Sebastian Vettel', 'FERRARI'], ['NHR', 'Nico Hulkenberg', 'RENAULT']]
) 

Function itself looks like this:

def read_files(start_log, end_log, abbr) -> tuple:
    """
    Takes two .log files - start and end, and a file containing abbreviation explanations.
    Processes the data from the files and returns a tuple of lists containing lines for each file.
    """
    for argument in [start_log, end_log, abbr]:
        if not os.path.isfile(argument):
            raise FileNotFoundError('Attribute must be a file.')

        with open(argument, 'r') as f:
            if argument == abbr:
                abbr_lines = [line.strip().split('_') for line in f]
            elif argument == start_log:
                start_lines = [(line[:3], line.strip()[3:]) for line in f]
                start_lines.pop()
            else:
                end_lines = [(line[:3], line.strip()[3:]) for line in f]
    return start_lines, end_lines, abbr_lines

And I need to write a test for it.

I had no problem with catching an error:

class ReadFilesTestCase(unittest.TestCase):
    def test_file_not_found_error(self):
        with self.assertRaises(FileNotFoundError):
            read_files('a.txt', 'b.txt', 'c.txt')

But I really struggle with mocking several files as arguments to the function.

I've been trying to do it this way:

class ReadFilesTestCase(unittest.TestCase):
    def setUp(self):
        self.file_1 = mock.patch("builtins.open", mock.mock_open(read_data=self.file_1_data))
        self.file_2 = mock.patch("builtins.open", mock.mock_open(read_data=self.file_1_data))
        self.file_3 = mock.patch("builtins.open", mock.mock_open(read_data=self.file_1_data))

    def test_read_files:
        self.assertEqual(read_files(self.file_1, self.file_2, self.file_3), self.result)

But I've got a FileNotFoundError. I also tried @mock.patch.multiple - didn't work as well.

I wonder if it's possible to mock files so I just write it something like this:

self.assertEqual(read_files(fake_file_1, fake_file_2, fake_file_3), self.result)

What technology should I use? I appreciate any advice.

CodePudding user response:

The problem here is that with mock_open you can only fake open for all file names, not for specific ones. I see two possibilities here: roll your own mocking of open instead of using using open_mock using side_effect (a bit ugly, and tightly coupled to the implementation), or use something like pyfakefs to mock the file system (less ugly, but heavy-weight).

The first approach could look something like this:

class ReadFilesTestCase(unittest.TestCase):

    @mock.patch("os.path.isfile", return_value=True)
    def test_read_files(self, mocked_isfile):
        # as we have to mock the for look in the open file,
        # we need to provide the lines as arrays (this will probably go in setUp)
        file1_lines = ["SVF2018-05-24_12:02:58.917",
                       "NHR2018-05-24_12:02:49.914"]
        file2_lines = ["SVF2018-05-24_1:04:11.332",
                       "NHR2018-05-24_1:04:02.979"]
        file3_lines = ["SVF_Sebastian Vettel_FERRARI",
                       "NHR_Nico Hulkenberg_RENAULT"]
        open_mock = MagicMock(name='open', spec=open)
        # we have to first mock __enter__ to account for context manager
        # then we use side_effect to define the result subsequent calls to open()
        open_mock.return_value.__enter__.side_effect = [
            file1_lines, file2_lines, file3_lines
        ]
        with mock.patch('builtins.open', open_mock):
            # the file names don't matter in this case
            assert read_files("f1", "f2", "f3") == expected_result

For the second approach, you need pyfakefs:

from pyfakefs import fake_filesystem_unittest

class ReadFilesTestCase(fake_filesystem_unittest.TestCase):

    def setUp(self):
        self.setUpPyfakefs()

    def test_read_files(self, mocked_isfile):
        # we create the needed files in the fake filesystem
        self.fs.create_file("a.txt", 
            contents="SVF2018-05-24_12:02:58.917\nNHR2018-05-24_12:02:49.914")
        self.fs.create_file("b.txt", 
            contents="SVF2018-05-24_1:04:11.332\nNHR2018-05-24_1:04:02.979")
        self.fs.create_file("c.txt",
            contents="SVF_Sebastian Vettel_FERRARI\nNHR_Nico Hulkenberg_RENAULT")
        
        # now the files exist in the fake filesystem (e.g. in memory), and you can just use them    
        assert read_files("a.txt", "b.txt", "c.txt") == expected_result

Disclaimer: I'm a contributor to pyfakefs.

  • Related