调用操作系统/模块级Python函数的Python单元测试代码

6 投票
3 回答
2789 浏览
提问于 2025-04-17 16:21

我有一个Python模块/脚本,它做了几件事情:

  1. 在脚本的不同嵌套层级中,我会获取命令行输入,验证这些输入,并应用一些合理的默认值。
  2. 我还会检查一些目录是否存在。

以上只是两个例子。我想知道测试这些功能的最佳“策略”是什么。我所做的是在我的模块中构建了包装函数,围绕raw_inputos.path.exists这两个函数,然后在测试中我重写了这两个函数,让它们从我的数组列表中获取输入,或者模拟一些行为。这个方法有以下几个缺点:

  1. 这些包装函数只是为了测试而存在,这让代码变得杂乱。
  2. 我每次都得记得在代码中使用包装函数,而不是直接调用os.path.existsraw_input

有没有什么聪明的建议呢?

3 个回答

3

Johnnysweb说得很对,你需要做的事情。不过与其自己动手写,不如直接引入并使用mock库。这个库专门为单元测试设计,让你想做的事情变得非常简单。它在Python 3.3中是内置的。

举个例子,如果你想运行一个单元测试,替换掉os.path.isfile这个函数,并且让它总是返回True:

try:
    from unittest.mock import patch
except ImportError:
    from mock import patch

class SomeTest(TestCase):

    def test_blah():
        with patch("os.path.isfile", lambda x: True):
            self.assertTrue(some_function("input"))

这样可以省去很多重复的代码,而且代码也很容易理解。

如果你需要做一些稍微复杂的事情,比如替换supbroccess.check_output,你可以创建一个简单的辅助函数:

def _my_monkeypatch_function(li):
     x,y = li[0], li[1]
     if x == "Reavers":
        return "Gorram"
     if x == "Inora":
        return "Shiny!"
     if x == y:
        return "The Ballad of Jayne"

def test_monkey():
     with patch("subprocess.check_output", _my_monkeypatch_function):
            assertEquals(subprocess.check_output(["Mudder","Mudder"]),
                                                 "The Ballad of Jayne")
3

解决方案1:我会这样做,因为这样有效:

def setUp(self):
    self._os_path_exists = os.path.exists
    os.path.exists = self.myTestExists # mock

def tearDown(self):
    os.path.exists = self._os_path_exists

虽然这样做不太优雅。

解决方案2:你说过重构代码不是一个选项,对吧?这样做会让代码更难理解,而且不太直观。

3

简单来说,就是要对这些系统调用进行一种叫做猴子补丁的操作。

在这个如何在Python中显示重定向的标准输入?的回答里,有一些很好的例子。

这里有一个简单的例子,展示了如何使用一个lambda函数来处理raw_input(),这个函数会忽略提示信息,直接返回我们想要的内容。

测试系统

$ cat ./name_getter.py
#!/usr/bin/env python

class NameGetter(object):

    def get_name(self):
        self.name = raw_input('What is your name? ')

    def greet(self):
        print 'Hello, ', self.name, '!'

    def run(self):
        self.get_name()
        self.greet()

if __name__ == '__main__':
    ng = NameGetter()
    ng.run()

$ echo Derek | ./name_getter.py 
What is your name? Hello,  Derek !

测试案例:

$ cat ./t_name_getter.py
#!/usr/bin/env python

import unittest
import name_getter

class TestNameGetter(unittest.TestCase):

    def test_get_alice(self):
        name_getter.raw_input = lambda _: 'Alice'
        ng = name_getter.NameGetter()
        ng.get_name()
        self.assertEquals(ng.name, 'Alice')

    def test_get_bob(self):
        name_getter.raw_input = lambda _: 'Bob'
        ng = name_getter.NameGetter()
        ng.get_name()
        self.assertEquals(ng.name, 'Bob')

if __name__ == '__main__':
    unittest.main()

$ ./t_name_getter.py -v
test_get_alice (__main__.TestNameGetter) ... ok
test_get_bob (__main__.TestNameGetter) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

撰写回答