2

假设我们有一个使用终端程序转换将图像转换为另一种格式的函数。这是代码:

def convert_image(src, dest=None, extension='jpg', quality=100):

    # Create default destination - same path just different extension
    if dest is None:
        dest = src.split('.')[0] + '.' + extension

    try:
        subprocess.check_call(['convert', src, '-quality', quality, dest])
    except CalledProcessError:
        raise ImageException('convert', 'Could not convert image')

    return dest

现在我想测试这个函数以验证它是否按预期工作。

最直接的方法可能是只进行单元测试,提供真实图像的路径,并验证是否使用正确的扩展名创建了新图像。但问题是,很难知道创建的图像是否实际上是在质量设置为正确值的情况下创建的,并且在测试函数中实际转换真实图像有点尴尬。

如果我这样调用函数:

convert_image('/tmp/myimage.png')

我现在真正感兴趣的是它从此输入将此数组作为输入发送到 check_call:

['convert', '/tmp/myimage.png', '-quality', 100, '/tmp/myimage.jpg']

如果是这样,那么我知道该函数正在针对这种特殊情况做正确的事情。

因此,如果用于测试目的的函数在我测试时实际上只返回这个数组,并且只在我运行真实程序时转换图像,那将是很方便的。

但是在所有函数中都有 if 语句来告诉函数为测试做不同的事情可能不是很好我的测试代码没有检测到。

我正在寻找有关如何测试此代码或如何重构它以使其更具可测试性的建议/讨论/提示。我对使用 python 转换图像的更好方法不感兴趣,这个问题纯粹是关于代码可测试性。

所以,如果你可以测试这个功能——你会怎么做?

4

2 回答 2

2

我认为您很聪明地希望断言正确的数组已发送到check_call,使其成为正确的单元测试(而不是同时测试转换应用程序)。为了能够做到这一点,您确实希望拦截对 check_call 的调用。

下面详细介绍了一种方法(注意代码未经测试,但应该对技术有所了解)。要做到这一点,首先将调用封装check_call在一个像这样的单独函数中(convert_image现在调用而不是subprocess.check_call):

def check_call(args):
    subprocess.check_call(args)

然后,在您的测试中,您可以替换check_call为一个不同的函数,该函数断言将传递给subprocess.check_call. 下面显示了测试的示例:

import unittest

class ConvertImageTest(unittest.TestCase):

    def test_convert_image(self):

        # Test version of check_call to do the assertion
        def assert_check_call(args):
            expected = ['convert', '/tmp/myimage.png', '-quality', 100, '/tmp/myimage.jpg']
            self.assertEquals(expected, args)

        # Replace check_call with our test version
        my.module.check_call = assert_check_call

        # Perform the test
        convert_image('/tmp/myimage.png')

使用这种方法也很容易模拟抛出的异常并测试您如何响应。

一个潜在的问题 - 你可能不得不在my.module.check_call之后反向分配 - 老实说,不确定是否需要这样做(我想这取决于模块是否在测试之间重新导入,希望这是真的)。

于 2013-07-03T19:43:54.250 回答
0

您在这里要做的是模拟 convert 实用程序。然后您可以在没有实际图像文件的情况下进行测试。您的测试将读取传递给模拟实用程序的内容。

换句话说,您测试的是这里发生的实际“进程间通信”的结果。

或者,正如 Brad 建议的那样,模拟 subprocess.check_call,这似乎是一个更好的主意。

于 2013-07-03T19:40:21.290 回答