【问题标题】:Python unit testing code which calls OS/Module level python functions调用操作系统/模块级python函数的Python单元测试代码
【发布时间】:2013-02-04 02:16:55
【问题描述】:
我有一个 python 模块/脚本可以完成其中的一些操作
- 在脚本内的各个嵌套级别,我采用命令行输入、验证它们、应用合理的默认值
- 我还检查了一些目录是否存在
以上只是两个例子。我试图找出测试这一点的最佳“策略”。我所做的是我在我的模块中围绕raw_input 和os.path.exists 构建了包装函数,然后在我的测试中我覆盖这两个函数以从我的数组列表中获取输入或做一些 mocked行为。这种方法有以下缺点
- 包装函数只是为了测试而存在,这会污染代码
- 我必须记住每次都在代码中使用包装函数,而不仅仅是调用
os.path.exists 或raw_input
有什么绝妙的建议吗?
【问题讨论】:
标签:
python
unit-testing
python-2.7
python-unittest
【解决方案1】:
简短的回答是monkey patch这些系统调用。
How to display the redirected stdin in Python?的回答中有一些很好的例子
这是一个简单的raw_input() 示例,使用lambda 丢弃提示并返回我们想要的内容。
被测系统
$ 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
【解决方案2】:
解决方案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】:
Johnnysweb 可以找到您需要做的事情,但您可以导入并使用 mock,而不是自己动手。 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")