我最近一直在研究的程序中的一个常见任务是以某种方式修改文本文件。 (嘿,我在Linux上,一切都是文件,而且我是大规模的系统pipe理员。)
但是代码修改的文件可能不存在于我的桌面上。 我可能不想修改它,如果它在我的桌面上。
我已经阅读了Dive Into Python中的unit testing,当testing将十进制转换为罗马数字(DintoP中的示例)的应用程序时,我非常清楚自己想要做什么。 testing是很好的独立。 您不需要validation程序是否打印正确的东西,您只需要validation这些函数是否将正确的输出返回给定的input。
但是,就我而言,我们需要testing程序是否正确地修改了它的环境。 以下是我想到的:
1)在标准位置创build“原始”文件,可能是/ tmp。
2)运行修改文件的函数,将path传递给/ tmp中的文件。
3)validation/ tmp中的文件是否正确更改; 通过/失败单元相应的testing。
这对我来说似乎很笨拙。 (如果你想validation文件的备份副本是否正确创build,甚至可以获得kludgier等)有没有人想出了一个更好的方法?
你在谈论太多的测试。 如果您开始试图通过说“让我们验证它正确地修改它的环境”来攻击测试问题,那么注定会失败。 环境有数十甚至数百万种潜在的变化。
相反,看看你的程序(“单位”)。 例如,你将有一个函数来确定哪些文件必须被写入? 什么是该功能的输入? 也许一个环境变量,也许从配置文件读取一些值? 测试这个函数,并且实际上并没有做任何修改文件系统的事情。 不要把它传递给“现实”的价值观,通过它很容易验证的价值观。 创建一个临时目录,在你的测试的setUp
方法中填充文件。
然后测试写入文件的代码。 只要确保它正在写入正确的内容文件内容。 甚至不写入一个真正的文件系统! 您不需要为此创建“假”文件对象,只需使用Python的方便的StringIO
模块; 他们是“文件”接口的“真正的”实现,它们只是不是你的程序实际上要写入的那些。
最终,你将不得不测试最终的,一切都是真正的挂钩的真正的顶级功能,通过真实的环境变量和真正的配置文件,把所有的东西放在一起。 但不要担心,开始。 首先,当你为较小的函数编写单独的测试时,你会开始拿起技巧,创建测试嘲笑,假货和存根将成为你的第二天性。 换另一种说法:即使你不能完全弄清楚如何测试这个函数调用,你也将拥有非常高的信心,它所调用的所有东西都是完美的。 另外,您会注意到,测试驱动开发强制您使API更加清晰和灵活。 例如:在抽象的某个对象上测试一些调用open()
方法的东西要比测试通过它的字符串上的os.open
调用os.open
。 open
方法是灵活的; 它可以是伪造的,它可以以不同的方式实现,但是一个字符串是一个字符串,而os.open
并没有给你任何方法来捕获什么方法被调用。
您也可以构建测试工具,使重复性任务变得简单。 例如,twisted为创建用于测试的临时文件创建测试工具提供了便利。 使用自己的测试库来测试工具或者更大的项目来获得这样的功能并不罕见。
你有两个级别的测试。
过滤和修改内容。 这些是“低级”操作,并不需要物理文件I / O。 这些是测试,决策,选择等。应用程序的“逻辑”。
文件系统操作。 创建,复制,重命名,删除,备份。 对不起,但那些是正确的文件系统操作 – 很好 – 需要适当的文件系统进行测试。
对于这种测试,我们经常使用“模拟”对象。 您可以设计一个体现各种文件系统操作的“FileSystemOperations”类。 你测试这个是为了确保它可以进行基本的读,写,复制,重命名等。这里没有真正的逻辑。 只是调用文件系统操作的方法。
然后,您可以创建一个虚拟文件系统,虚拟出各种操作。 你可以使用这个模拟对象来测试你的其他类。
在某些情况下,所有的文件系统操作都在os模块中。 如果是这样的话,你可以用你实际使用的模拟版本创建一个MockOS模块。
把你的MockOS模块放在PYTHONPATH
,你可以隐藏真正的OS模块。
对于生产操作,您可以使用经过充分测试的“逻辑”类和您的FileSystemOperations类(或真正的OS模块)。
对于稍后需要测试代码写入文件的方法的读者来说,这里是一个“fake_open”,用于修补使用StringIO的模块的开放内置。 fake_open返回可以在单元测试或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
当我在代码中触摸文件时,我倾向于嘲笑文件的实际读写……所以我可以在测试中给我的类确切的内容,然后声明测试正在写回我期望的内容。
我已经在Java中完成了这个工作,我想这在Python中是非常简单的…但是它可能需要设计你的类/函数,这样很容易就可以模拟使用实际的文件。
为此,您可以尝试传入流,然后传递一个简单的字符串输入/输出流,不会写入文件,或者有一个实际的“将此字符串写入文件”或“读取字符串从一个文件“,然后在测试中替换该函数。
我认为你是在正确的轨道上。 根据你需要做什么chroot可以帮助你为你的scrpits建立一个“看起来”真实的环境,但不是。
如果这不起作用,那么你可以编写你的脚本来采取“根”路径作为参数。
在生产运行中,根路径就是/。 要进行测试,请在/ tmp / test下创建一个影子环境,然后使用根路径/ tmp / test运行脚本。
您可能想要设置测试,以便它在chroot jail中运行,所以即使路径和文件位置在代码中硬编码,您仍然拥有测试所需的所有环境[这不是一个好的做法,但有时会得到该文件从其他地方的地点…],然后通过退出代码检查结果。