如何向Python单元测试提供stdin、文件和环境变量输入?

34 投票
4 回答
17964 浏览
提问于 2025-04-15 21:29

如何编写测试,遇到以下情况时该怎么做:

  1. 测试用户输入。
  2. 测试从文件中读取的输入。
  3. 测试从环境变量中读取的输入。

如果有人能告诉我该如何处理这些情况,那就太好了;如果还能推荐一些文档、文章或博客让我阅读,那就更棒了。

4 个回答

6

如果可以不使用外部进程,那就尽量避免使用。

不过,有些情况下这样做会比较复杂,这时候你可能真的需要用到进程,比如你想测试一个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_inputsys.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 上有一个模块,用来测试 STDOUTSTDERR 和给定 STDIN、命令行参数以及环境变量时的退出状态。此外,检查一下这个模块下的“tests”目录里的测试。市面上可能有更好的模块,所以我的代码仅供学习参考。

30

如果你必须使用 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 会按照顺序返回列表中的元素。它还能做更多的事情!我建议你去看看文档。

34

你提到的这三种情况,都需要特别注意在设计中使用松耦合。

你真的需要单独测试 Python 的 raw_input 方法吗?open 方法呢?os.environ.get 呢?其实不需要。

你需要设计得让你可以用其他方式来获取输入。这样,在进行单元测试时,你可以用一个替代品来代替实际调用 raw_inputopen

比如,你的正常代码可能是这样的:

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")

记住,你不需要测试一种语言的输入输出功能(除非你是设计这个语言的人,那就是另一回事了)。

撰写回答