如何向Python单元测试提供stdin、文件和环境变量输入?
如何编写测试,遇到以下情况时该怎么做:
- 测试用户输入。
- 测试从文件中读取的输入。
- 测试从环境变量中读取的输入。
如果有人能告诉我该如何处理这些情况,那就太好了;如果还能推荐一些文档、文章或博客让我阅读,那就更棒了。
4 个回答
如果可以不使用外部进程,那就尽量避免使用。
不过,有些情况下这样做会比较复杂,这时候你可能真的需要用到进程,比如你想测试一个C语言程序的命令行接口。
用户输入
可以使用 subprocess.Popen
,像这样:
process = subprocess.Popen(
command,
shell = False,
stdin = subprocess.PIPE,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
universal_newlines = True
)
stdout, stderr = process.communicate("the user input\nline 2")
exit_status = process.wait()
从用户那里获取输入和通过管道获取输入没有区别,使用 raw_input
或 sys.stdin.read()
这些方法都是一样的。
文件
在你的测试
setUp
方法中,创建一个临时目录,并在里面创建你想要读取的文件:tdir = tempfile.mkdtemp( prefix = 'filetest_', ) fpath = os.path.join(tdir,'filename') fp = open(fpath, 'w') fp.write("contents") fp.close()
在测试中进行文件读取。
测试结束后,记得删除临时目录。
shutil.rmtree(tdir)
从文件中读取数据其实比较复杂,大多数程序可以从文件或者标准输入(STDIN)读取数据(例如使用
fileinput
)。所以,如果你想测试的是某些内容被输入时会发生什么,而你的程序可以接受标准输入,那就直接用Popen
来测试程序吧。
环境变量
- 可以用
os.environ["THE_VAR"] = "the_val"
来设置环境变量。 - 用
del os.environ["THE_VAR"]
来删除它们。 os.environ = {'a':'b'}
这种方式是行不通的。- 然后调用
subprocess.Popen
。环境变量会从调用的进程中继承过来。
模板代码
我在 我的GitHub 上有一个模块,用来测试 STDOUT
、STDERR
和给定 STDIN
、命令行参数以及环境变量时的退出状态。此外,检查一下这个模块下的“tests”目录里的测试。市面上可能有更好的模块,所以我的代码仅供学习参考。
如果你必须使用 raw_input(或者其他特定的输入来源),我非常推荐使用 mock 库。根据 Mark Rushakoff 在他示例中使用的代码:
def say_hello():
name = raw_input("What is your name? ")
return "Hello " + name
你的测试代码可以使用 mock:
import mock
def test_say_hello():
with mock.patch('__builtin__.raw_input', return_value='dbw'):
assert say_hello() == 'Hello dbw'
with mock.patch('__builtin__.raw_input', side_effect=['dbw', 'uki']):
assert say_hello() == 'Hello dbw'
assert say_hello() == 'Hello uki'
这些断言会通过。注意,side_effect 会按照顺序返回列表中的元素。它还能做更多的事情!我建议你去看看文档。
你提到的这三种情况,都需要特别注意在设计中使用松耦合。
你真的需要单独测试 Python 的 raw_input
方法吗?open
方法呢?os.environ.get
呢?其实不需要。
你需要设计得让你可以用其他方式来获取输入。这样,在进行单元测试时,你可以用一个替代品来代替实际调用 raw_input
或 open
。
比如,你的正常代码可能是这样的:
import os
def say_hello(input_func):
name = input_func()
return "Hello " + name
def prompt_for_name():
return raw_input("What is your name? ")
print say_hello(prompt_for_name)
# Normally would pass in methods, but lambdas can be used for brevity
print say_hello(lambda: open("a.txt").readline())
print say_hello(lambda: os.environ.get("USER"))
这个会话看起来像:
What is your name? somebody Hello somebody Hello [some text] Hello mark
然后你的测试会是这样的:
def test_say_hello():
output = say_hello(lambda: "test")
assert(output == "Hello test")
记住,你不需要测试一种语言的输入输出功能(除非你是设计这个语言的人,那就是另一回事了)。