对访问文件的函数进行单元测试

10 投票
5 回答
7590 浏览
提问于 2025-04-15 19:23

我有两个函数,一个是用来构建文件路径的,另一个是用来读取这些文件的。下面是这两个函数的代码:

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

那么,测试这两个函数的最佳方法是什么呢?

5 个回答

2

我不是专家,但我试着说说。首先,我们可以对代码进行一些重构:把它们改成函数形式(去掉所有类的东西),删掉不需要的部分。这样做会让测试变得简单很多。如果你真的想把这些函数放在一个类里,也可以让类去调用这些函数。

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

现在我们可以通过传入一个自定义的函数来测试 pass_file_name,这样就能测试 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 ...

因为我们假设 os.path.isfile 是有效的,所以我们对第一个函数的测试应该是相当不错的。不过,你也可以让测试实际创建一些文件,然后用 exists = os.path.isfile 来调用 pass_file_name

第二个函数就比较难测试了;我听说最好的(单元)测试是不涉及网络、数据库、图形界面或硬盘的。所以可能需要再进行一些重构,这样会更容易。可以考虑模拟打开文件的操作;或者在测试函数中实际写一些长文件,然后再读取它们。

如何在使用Python的Mock框架时模拟在with语句中使用的open?

3

我个人会建立一个测试工具,先准备好需要的文件,然后再测试这两个函数。

对于每个测试案例(也就是你期望文件存在的情况 - 记得也要测试失败的情况!),在合适的文件里写入一些已知的日志;然后调用要测试的函数,检查结果是否正确。

8

这里有两个单元

  • 一个是用来生成文件路径的
  • 另一个是用来读取这些路径的

所以应该有两个单元测试案例(也就是包含测试的类)。第一个测试只负责检查文件路径的生成。第二个测试则是从你在测试目录的一个特定子目录中准备好的文件集合里读取数据,它应该和第一个测试相互独立。

在你的情况下,测试用的日志文件可能会很短。为了让代码更易读和维护,最好把这些文件直接嵌入到测试代码里。不过这样的话,你需要稍微改进一下你的读取函数,让它可以接受文件名或者类似文件的对象:

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

撰写回答