我最近一直在努力的程序中的一个常见任务是以某种方式修改文本文件。 (嘿,我在Linux上。一切都是文件。我做大规模的系统管理。)
但是我的桌面盒上可能不存在代码修改的文件。如果它在我的桌面上,我可能不想修改它。
我已经阅读了Dive Into Python中的单元测试,在测试将十进制转换为罗马数字的应用程序(DintoP中的示例)时,我非常清楚我想要做什么。测试非常独立。您无需验证程序PRINTS是否正确,您只需要验证函数是否将正确的输出返回给定输入。
但是,在我的情况下,我们需要测试程序是否正确地修改了它的环境。以下是我的想法:
1)在标准位置创建“原始”文件,可能是/ tmp。
2)运行修改文件的函数,将其传递给/ tmp中文件的路径。
3)验证/ tmp中的文件是否正确更改;相应的通过/失败单元测试。
这对我来说似乎很糟糕。 (如果你想验证文件的备份副本是否正确创建等,那么甚至可以获得kludgier。)有没有人想出更好的方法?
答案 0 :(得分:14)
你说的是一次测试太多了。如果您开始尝试通过说“让我们验证它正确地修改其环境”来攻击测试问题,那么您注定要失败。环境有数十种甚至数百种可能的变化。
相反,请查看程序的各个部分(“单位”)。例如,您是否有一个确定必须写入文件的位置的函数?该功能的输入是什么?也许是一个环境变量,也许从配置文件中读取一些值?测试该功能,并不实际做任何修改文件系统的事情。不要传递“现实”值,传递易于验证的值。创建一个临时目录,用测试的setUp
方法中的文件填充它。
然后测试写入文件的代码。只要确保它正在编写正确的内容文件内容。甚至不写入真正的文件系统!你不需要为此制作“假的”文件对象,只需使用Python的方便的StringIO
模块;它们是“文件”界面的“真实”实现,它们不是您的程序实际要写入的那些。
最终,您将不得不测试最终的,实际上是实际连接的顶级函数,它传递真实的环境变量和真实的配置文件并将所有内容放在一起。但是不要担心要开始。首先,当您为较小的函数编写单独的测试时,您将开始学习技巧,并且创建测试模拟,假货和存根将成为您的第二天性。另一方面:即使你不能完全弄清楚如何测试一个函数调用,你也会非常自信地认为它调用的所有东西都能完美运行。此外,您会注意到测试驱动的开发会迫使您使API更清晰,更灵活。例如:测试在某个抽象的对象上调用open()
方法的东西要比在传递它的字符串上测试调用os.open
的东西要容易得多。 open
方法很灵活;它可以是伪造的,它可以以不同的方式实现,但字符串是一个字符串,os.open
没有给你任何余地来捕捉它上面调用的方法。
您还可以构建测试工具,以便轻松完成重复性任务。例如,twisted提供了用于创建用于测试built right into its testing tool的临时文件的工具。使用自己的测试库测试工具或大型项目以获得这样的功能并不罕见。
答案 1 :(得分:7)
您有两个级别的测试。
过滤和修改内容。这些是“低级”操作,实际上并不需要物理文件I / O.这些是测试,决策,替代等。应用程序的“逻辑”。
文件系统操作。创建,复制,重命名,删除,备份。抱歉,这些都是正确的文件系统操作 - 很好 - 需要一个适当的文件系统进行测试。
对于这种测试,我们经常使用“模拟”对象。您可以设计一个“FileSystemOperations”类,它包含各种文件系统操作。你测试它以确保它做基本的读,写,复制,重命名等。这里没有真正的逻辑。只是调用文件系统操作的方法。
然后,您可以创建一个模拟各种操作的MockFileSystem。您可以使用此Mock对象来测试其他类。
在某些情况下,所有文件系统操作都在os模块中。如果是这种情况,您可以创建一个MockOS模块,其中包含您实际使用的操作的模拟版本。
将MockOS模块放在PYTHONPATH
上,您可以隐藏真实的OS模块。
对于生产操作,您可以使用经过良好测试的“逻辑”类以及FileSystemOperations类(或真正的OS模块)。
答案 2 :(得分:3)
对于后来想要测试代码写入文件的方法正常工作的读者,这里有一个“fake_open”修补模块的开放内置以使用StringIO。 fake_open返回打开文件的dict,可以在单元测试或doctest中检查,所有这些都不需要真正的文件系统。
def fake_open(module):
"""Patch module's `open` builtin so that it returns StringIOs instead of
creating real files, which is useful for testing. Returns a dict that maps
opened file names to StringIO objects."""
from contextlib import closing
from StringIO import StringIO
streams = {}
def fakeopen(filename,mode):
stream = StringIO()
stream.close = lambda: None
streams[filename] = stream
return closing(stream)
module.open = fakeopen
return streams
答案 3 :(得分:2)
当我在我的代码中触摸文件时,我倾向于模拟文件的实际读取和写入...因此,我可以在测试中为我的类提供我想要的确切内容,然后断言测试是写回我期望的内容。
我在Java中完成了这个,我想它在Python中非常简单......但它可能需要设计你的类/函数,以便模拟实际文件的使用是很容易的。 / p>
为此,您可以尝试传入流,然后传入一个简单的字符串输入/输出流,该流不会写入文件,或者具有实际“将此字符串写入文件”的函数或“从文件中读取此字符串”,然后在测试中替换该函数。
答案 4 :(得分:1)
我认为你走在正确的轨道上。根据您的需要chroot可能会帮助您为scrpits设置一个“看起来”真实但不是真实的环境。
如果这不起作用,那么您可以编写脚本以将“根”路径作为参数。
在生产运行中,根路径只是/。为了测试,在/ tmp / test下创建一个阴影环境,然后使用/ tmp / test的根路径运行脚本。
答案 5 :(得分:1)
您可能希望设置测试以便它在chroot jail中运行,因此您拥有测试所需的所有环境,即使路径和文件位置在代码中是硬编码的[这不是一个很好的做法,但有时一个从其他地方获取文件位置...]然后通过退出代码检查结果。