调用操作系统/模块级Python函数的Python单元测试代码
我有一个Python模块/脚本,它做了几件事情:
- 在脚本的不同嵌套层级中,我会获取命令行输入,验证这些输入,并应用一些合理的默认值。
- 我还会检查一些目录是否存在。
以上只是两个例子。我想知道测试这些功能的最佳“策略”是什么。我所做的是在我的模块中构建了包装函数,围绕raw_input
和os.path.exists
这两个函数,然后在测试中我重写了这两个函数,让它们从我的数组列表中获取输入,或者模拟一些行为。这个方法有以下几个缺点:
- 这些包装函数只是为了测试而存在,这让代码变得杂乱。
- 我每次都得记得在代码中使用包装函数,而不是直接调用
os.path.exists
或raw_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