简体   繁体   English

单元测试文件修改

[英]Unit Testing File Modifications

A common task in programs I've been working on lately is modifying a text file in some way. 我最近一直在研究的程序中的一个常见任务是以某种方式修改文本文件。 (Hey, I'm on Linux. Everything's a file. And I do large-scale system admin.) (嘿,我在Linux上。一切都是文件。我做大规模的系统管理员。)

But the file the code modifies may not exist on my desktop box. 但是代码修改的文件可能不存在于我的桌面盒上。 And I probably don't want to modify it if it IS on my desktop. 如果它在我的桌面上,我可能不想修改它。

I've read about unit testing in Dive Into Python, and it's pretty clear what I want to do when testing an app that converts decimal to Roman Numerals (the example in DintoP). 我已经阅读了Dive Into Python中的单元测试,并且在测试将十进制转换为罗马数字的应用程序(DintoP中的示例)时,我非常清楚我想要做什么。 The testing is nicely self-contained. 测试非常独立。 You don't need to verify that the program PRINTS the right thing, you just need to verify that the functions are returning the right output to a given input. 您无需验证程序PRINTS是否正确,您只需要验证函数是否将正确的输出返回给定输入。

In my case, however, we need to test that the program is modifying its environment correctly. 但是,在我的情况下,我们需要测试程序是否正确地修改了它的环境。 Here's what I've come up with: 这是我想出的:

1) Create the "original" file in a standard location, perhaps /tmp. 1)在标准位置创建“原始”文件,可能是/ tmp。

2) Run the function that modifies the file, passing it the path to the file in /tmp. 2)运行修改文件的函数,将路径传递给/ tmp中的文件。

3) Verify that the file in /tmp was changed correctly; 3)验证/ tmp中的文件是否正确更改; pass/fail unit test accordingly. 相应的通过/失败单元测试。

This seems kludgy to me. 这对我来说似乎很狡猾。 (Gets even kludgier if you want to verify that backup copies of the file are created properly, etc.) Has anyone come up with a better way? (如果你想验证文件的备份副本是否正确创建等,甚至可以获得kludgier。)有没有人想出更好的方法?

You're talking about testing too much at once. 你说的是一次测试太多了。 If you start trying to attack a testing problem by saying "Let's verify that it modifies its environment correctly", you're doomed to failure. 如果您开始尝试通过说“让我们验证它正确地修改其环境”来攻击测试问题,那么您注定要失败。 Environments have dozens, maybe even millions of potential variations. 环境有数十种甚至数百种可能的变化。

Instead, look at the pieces ("units") of your program. 相反,请查看程序的各个部分(“单位”)。 For example, are you going to have a function that determines where the files are that have to be written? 例如,您是否有一个确定必须写入文件的位置的函数? What are the inputs to that function? 该功能的输入是什么? Perhaps an environment variable, perhaps some values read from a config file? 也许是一个环境变量,也许从配置文件中读取一些值? Test that function, and don't actually do anything that modifies the filesystem. 测试该功能,并不实际做任何修改文件系统的事情。 Don't pass it "realistic" values, pass it values that are easy to verify against. 不要传递“现实”值,传递易于验证的值。 Make a temporary directory, populate it with files in your test's setUp method. 创建一个临时目录,用test的setUp方法中的文件填充它。

Then test the code that writes the files. 然后测试写入文件的代码。 Just make sure it's writing the right contents file contents. 只要确保它正在编写正确的内容文件内容。 Don't even write to a real filesystem! 甚至不写入真正的文件系统! You don't need to make "fake" file objects for this, just use Python's handy StringIO modules; 你不需要为此制作“假的”文件对象,只需使用Python的方便的StringIO模块; they're "real" implementations of the "file" interface, they're just not the ones that your program is actually going to be writing to. 它们是“文件”界面的“真实”实现,它们不是您的程序实际要写入的那些。

Ultimately you will have to test the final, everything-is-actually-hooked-up-for-real top-level function that passes the real environment variable and the real config file and puts everything together. 最终,您将不得不测试最终的,实际上是实际连接的顶级函数,它传递真实的环境变量和真实的配置文件并将所有内容放在一起。 But don't worry about that to get started. 但是不要担心要开始。 For one thing, you will start picking up tricks as you write individual tests for smaller functions and creating test mocks, fakes, and stubs will become second nature to you. 首先,当您为较小的函数编写单独的测试时,您将开始学习技巧,并且创建测试模拟,假货和存根将成为您的第二天性。 For another: even if you can't quite figure out how to test that one function call, you will have a very high level of confidence that everything which it is calling works perfectly. 另一方面:即使你不能完全弄清楚如何测试一个函数调用,你也会非常自信地认为它调用的所有东西都能完美运行。 Also, you'll notice that test-driven development forces you to make your APIs clearer and more flexible. 此外,您会注意到测试驱动的开发会迫使您使API更清晰,更灵活。 For example: it's much easier to test something that calls an open() method on an object that came from somewhere abstract, than to test something that calls os.open on a string that you pass it. 例如:测试在某个抽象的对象上调用open()方法的东西os.open在传递它的字符串上测试os.open东西要os.open The open method is flexible; open方法灵活; it can be faked, it can be implemented differently, but a string is a string and os.open doesn't give you any leeway to catch what methods are called on it. 它可以是伪造的,它可以以不同的方式实现,但字符串是一个字符串, os.open不会给你任何余地来捕捉它上面调用的方法。

You can also build testing tools to make repetitive tasks easy. 您还可以构建测试工具,以便轻松完成重复性任务。 For example, twisted provides facilities for creating temporary files for testing built right into its testing tool . 例如,twisted提供了用于创建临时文件的工具,用于在其测试工具中内置的测试 It's not uncommon for testing tools or larger projects with their own test libraries to have functionality like this. 使用自己的测试库测试工具或更大的项目来获得这样的功能并不罕见。

You have two levels of testing. 你有两个级别的测试。

  1. Filtering and Modifying content. 过滤和修改内容。 These are "low-level" operations that don't really require physical file I/O. 这些是“低级”操作,实际上并不需要物理文件I / O. These are the tests, decision-making, alternatives, etc. The "Logic" of the application. 这些是测试,决策,替代等。应用程序的“逻辑”。

  2. File system operations. 文件系统操作。 Create, copy, rename, delete, backup. 创建,复制,重命名,删除,备份。 Sorry, but those are proper file system operations that -- well -- require a proper file system for testing. 抱歉,这些都是正确的文件系统操作 - 很好 - 需要一个适当的文件系统进行测试。

For this kind of testing, we often use a "Mock" object. 对于这种测试,我们经常使用“模拟”对象。 You can design a "FileSystemOperations" class that embodies the various file system operations. 您可以设计一个“FileSystemOperations”类,它包含各种文件系统操作。 You test this to be sure it does basic read, write, copy, rename, etc. There's no real logic in this. 你测试它以确保它做基本的读,写,复制,重命名等。这里没有真正的逻辑。 Just methods that invoke file system operations. 只是调用文件系统操作的方法。

You can then create a MockFileSystem which dummies out the various operations. 然后,您可以创建一个模拟各种操作的MockFileSystem。 You can use this Mock object to test your other classes. 您可以使用此Mock对象来测试其他类。

In some cases, all of your file system operations are in the os module. 在某些情况下,所有文件系统操作都在os模块中。 If that's the case, you can create a MockOS module with mock version of the operations you actually use. 如果是这种情况,您可以创建一个MockOS模块,其中包含您实际使用的操作的模拟版本。

Put your MockOS module on the PYTHONPATH and you can conceal the real OS module. 将MockOS模块放在PYTHONPATH ,您可以隐藏真实的OS模块。

For production operations you use your well-tested "Logic" classes plus your FileSystemOperations class (or the real OS module.) 对于生产操作,您可以使用经过良好测试的“逻辑”类和FileSystemOperations类(或真正的OS模块)。

For later readers who just want a way to test that code writing to files is working correctly, here is a "fake_open" that patches the open builtin of a module to use StringIO. 对于后来只想要一种方法来测试代码写入文件的读者是否正常工作,这里有一个“fake_open”,用于修补模块的开放内置以使用StringIO。 fake_open returns a dict of opened files which can be examined in a unit test or doctest, all without needing a real file-system. 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

When I touch files in my code, I tend to prefer to mock the actual reading and writing of the file... so then I can give my classes exact contents I want in the test, and then assert that the test is writing back the contents I expect. 当我在我的代码中触摸文件时,我倾向于模拟文件的实际读取和写入...因此,我可以在测试中为我的类提供我想要的确切内容,然后声明测试正在写回我希望的内容。

I've done this in Java, and I imagine it is quite simple in Python... but it may require designing your classes/functions in such a way that it is EASY to mock the use of an actual file. 我在Java中完成了这个,我想它在Python中非常简单......但它可能需要设计你的类/函数,以便模拟实际文件的使用是很容易的。

For this, you can try passing in streams and then just pass in a simple string input/output stream which won't write to a file, or have a function that does the actual "write this string to a file" or "read this string from a file", and then replace that function in your tests. 为此,您可以尝试传入流,然后只传入一个不会写入文件的简单字符串输入/输出流,或者具有实际“将此字符串写入文件”或“读取此内容”的函数来自文件的字符串“,然后在测试中替换该函数。

I think you are on the right track. 我认为你走在正确的轨道上。 Depending on what you need to do chroot may help you set up an environment for your scrpits that 'looks' real, but isn't. 根据您的需要, chroot可以帮助您为您的scrpits设置一个“看起来”真实的环境,但不是。

If that doesn't work then you could write your scripts to take a 'root' path as an argument. 如果这不起作用,那么您可以编写脚本以将“根”路径作为参数。

In a production run the root path is just /. 在生产运行中,根路径只是/。 For testing you create a shadow environment under /tmp/test and then run your scripts with a root path of /tmp/test. 为了测试,在/ tmp / test下创建一个阴影环境,然后使用/ tmp / test的根路径运行脚本。

您可能想要设置测试以便它在chroot jail中运行,因此您拥有测试所需的所有环境,即使路径和文件位置在代码中是硬编码的[这不是一个很好的做法,但有时会得到文件来自其他地方的位置...]然后通过退出代码检查结果。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM