nashr rafeeg
nashr rafeeg

Reputation: 829

Unit testing functions that access files

I have two functions—one that builds the path to a set of files and another that reads the files. Below are the two functions:

def pass_file_name(self):
    self.log_files= []
    file_name = self.path+"\\access_"+self.appliacation+".log"
    if os.path.isfile(file_name):
        self.log_files.append(file_name)
    for i in xrange(7):
         file_name = self.path+"\\access_"+self.appliacation+".log"+"."+str(i+1)
         if os.path.isfile(file_name):
            self.log_files.append(file_name)
    return self.log_files


def read_log_files (self, log_file_names): 
    self.log_entrys = []
    self.log_line = []
    for i in log_file_names:
        self.f = open(i)
        for line in self.f:
            self.log_line = line.split(" ")
            #print self.log_line
            self.log_entrys.append(self.log_line)
    return self.log_entrys

What would be the best way to unit test these two functions?

Upvotes: 11

Views: 7734

Answers (5)

Tambe Tabitha
Tambe Tabitha

Reputation: 175

You might find testfixtures helpful. It allows you to create a temporary directory while testing. Files can be created, removed and written to.

I just came across it as one of my search results and glanced through the docs. Figuring out how to make it work.

Upvotes: 0

James Brooks
James Brooks

Reputation: 4375

I'm no expert but I'll give it a go. First a bit of refactoring: make them functional (remove all class stuff), remove unneeded things. This should make it much easier to test. You can always make the class call these functions if you really want it in a class.

def pass_file_name(base_filename, exists):
    """return a list of filenames that exist
       based upon `base_filename`.
       use `os.path.isfile` for `exists`"""

    log_files = []
    if exists(base_filename):
        log_files.append(base_filename)
    for i in range(1, 8):
         filename = base_filename + "." + str(i)
         if exists(filename):
             log_files.append(filename)
    return log_files

def read_log_files (self, log_files):
    """read and parse each line from log_files
       use `pass_file_name` for `log_files`"""

    log_entrys = []
    for filename in log_files:
        with open(filename) as myfile:
            for line in myfile:
                log_entrys.append(line.split())
    return log_entrys

Now we can easily test pass_file_name by passing in a custom function to exists.

class Test_pass_file_name(unittest.TestCase):
    def test_1(self):
        """assume every file exists
           make sure all logs file are there"""
        exists = lambda _: True
        log_files = pass_file_name("a", exists)
        self.assertEqual(log_files,
                    ["a", "a.1", "a.2", "a.3", 
                     "a.4", "a.5", "a.6", "a.7"])

    def test_2(self):
        """assume no files exists
           make sure nothing returned"""
        exists = lambda _: False
        log_files = pass_file_name("a", exists)
        self.assertEqual(log_files, [])

    # ...more tests here ...

As we assume os.path.isfile works we should have got pretty good testing of the first function. Though you could always have the test actually create some files then call pass_file_name with exists = os.path.isfile.

The second one is harder to test; I have been told that the best (unit)tests don't touch the network, databases, GUI or the hard-drive. So maybe some more refactoring would make it easier. Mocking open could work; or would could actually write some long file in the test function and read them in.

How do I mock an open used in a with statement (using the Mock framework in Python)?

Upvotes: 2

nkrkv
nkrkv

Reputation: 7098

You have two units here:

  • One that generate file paths
  • Second that reads them

Thus there should be two unit-test-cases (i.e. classes with tests). First would test only file paths generation. Second would test reading from predefined set of files you prepared in special subdirectory of tests directory, it should test in isolation from first test case.

In your case, you could probably have very short log files for tests. In this case for better readability and maintenance it is good idea to embed them right in test code. But in this case you'll have to improve your reading function a bit so it can take either file name or file-like object:

from cStringIO import StringIO

# ...
def test_some_log_reading_scenario(self):
    log1 = '\n'.join([
        'log line',
        'another log line'
    ])
    log2 = '\n'.join([
        'another log another line',
        'lala blah blah'
    ])
    # ...
    result = myobj.read_log_files([StringIO(log1), StringIO(log2)])
    # assert result

Upvotes: 8

Chowlett
Chowlett

Reputation: 46667

Personally, I'd build a test harness that set up the required files before testing those two functions.

For each test case (where you expect the file to be present - remember to test failure cases too!), write some known logs into the appropriately named files; then call the functions under test and check the results.

Upvotes: 3

Ignacio Vazquez-Abrams
Ignacio Vazquez-Abrams

Reputation: 798606

Bind the open name in the module to a function that mocks the file opening.

Upvotes: 1

Related Questions