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