【问题标题】:Unit testing keypresses and terminal output in Python?在 Python 中对按键和终端输出进行单元测试?
【发布时间】:2016-06-02 22:34:21
【问题描述】:

如果您在 Google 或 SO 中搜索“unit test stdin stdout python”,您会发现很多问题,每一个问题都以一种或另一种方式回答

你真的需要对 Python 的内置 input / sys.stdin 方法进行单元测试吗?

我的回答是是的,我强调这样做,因为我基本上是在实现我自己的input + 穷人的libreadline / libcurses,我需要使用标准输入和终端的内容。

我碰巧使用 Unix 派生的操作系统,所以我有管道 | 和 shell 重定向 <,所以我可以编写一个小 shell 脚本和一些 Python 帮助代码一起执行此操作,并测试终端的操作( ANSI 转义序列、光标移动、打印的确切内容等)我可以从已知的/dev/tty/whatever 中读取信息,但我不想这样做的主要原因有两个:

  1. 测试代码应该和它正在测试的代码一样跨平台(而且不那么脆弱!)

  2. 我非常喜欢unittest,谢谢,我不想仅仅为了测试我的模块而求助于 shell 脚本和 unix hackery(就像我喜欢 unix hackery 一样)。

必须有更好的方法来测试curses 之类的东西,而不是在您使用 curses 时,而是在您开发 curses 时.


既然有人要求,以下是我要测试的一些示例:(full code on github)

def _Getch():
    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)
    try:
        tty.setraw(sys.stdin.fileno())
        ch = sys.stdin.read(1)
    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
        return ch

class xyctl:
    def _terminal_size():
        import fcntl, struct
        h, w, hp, wp = struct.unpack('HHHH',
            fcntl.ioctl(0, termios.TIOCGWINSZ,
            struct.pack('HHHH', 0, 0, 0, 0)))
        return w, h, w * h

    def _matrix_calc(adj_x, adj_y):
        cur_x, cur_y = xyctl.getter()
        new_x, new_y = (
            (cur_x + adj_x),
            (cur_y + adj_y)
        )

        if (new_x * new_y) < (xyctl._terminal_size()[2]):
            return new_x, new_y
        else:
            _writer(CHAR_BEL)

    def getter():
        _writer(CHAR_ESC + "[6n")
        pos = until("R", raw=True)
        _writer(CHAR_CRR + CHAR_SPC * (len(pos) + 1) + CHAR_CRR)
        pos = pos[2:].split(";")
        pos[0], pos[1] = int(pos[1]), int(pos[0])
        return pos

    def setter(new_x, new_y):
        _writer(CHAR_ESC + "[{};{}H".format(new_x, new_y))

    def adjust(adj_x, adj_y):
        new_x, new_y = xyctl._matrix_calc(adj_x, adj_y)
        xyctl.setter(new_x, new_y)

【问题讨论】:

  • @downvoter/s please consider adding a comment if you think this post can be improved
  • 你能给出一个你想测试的代码片段的具体例子吗?它可能通过模拟或存根inputsys.stdin 来完成,这样您就不必测试内置函数,而是测试您的代码对它们的作用。
  • @das-g 我已经编辑了我的问题

标签: python unit-testing


【解决方案1】:

您可以使用subprocess 模块从python 中调用您的脚本。然后您可以通过communicate发送测试输入

import subprocess as sp
import sys

interpreter_path = sys.executable
p = sp.Popen([interpreter_path, script_to_test])
(stdout, stderr) = p.communicate(input = testinput)

然后可以测试stdoutstderr 的正确值

【讨论】:

    【解决方案2】:

    迟到了,但是:我正是在这种情况下,想要测试 CLI 的实际面向用户的部分,以便能够 (i) 确保由一组给定的用户按键产生一致的行为,以及 (ii) 检查打印到控制台的实际内容。

    我将几个类放在一起以(在我看来)一种非常好的方式实现这一点,最初实现 here 并且在撰写本文时,现在在 PyPI 的 v1.0 预发布版中发布为 @ 987654322@。其中大部分并不是特别新颖,只是一个上下文管理器来模拟stdout/stderr/stdin 到临时流,正如this onethis onethis one 等答案中已经描述的那样。

    与我在其他地方看到的任何东西的最大区别在于 stdin 被模拟为 custom StringIO subclass,它 (a) 自动将从流中读取的所有内容发送到模拟的 stdout,从而呼应“键入输入”到“控制台”; (b) 实现 .append() 辅助方法以允许在 mock-stdin 的末尾添加更多内容,而无需更改搜索位置。

    示例用法:

    >>> from stdio_mgr import stdio_mgr
    >>> with stdio_mgr() as (in_, out_, err_):
    ...     print('foobar')               # 'print' appends '\n'
    ...     in_.append('bazquux\n')       # Don't forget trailing '\n'!
    ...     inp_result = input('?')       # 'bazquux\n' is teed to 'out_' here
    ...     out_result = out_.getvalue()  # Pull the whole 'out_' stream contents
    >>> inp_result
    'bazquux'
    
    >>> out_result
    'foobar\n?bazquux\n'
    

    请注意,即使附加到in_ 的字符串是换行符终止的(否则执行会挂起),根据standard behavior input 在字符串存储到inp_result 之前剥离了尾随'\n'。可以看出,作为input提示符的问号存储在out_中。

    但是,由于TeeStdin 将从“stdin”读取的内容发送到 之前,它被传递给inputout_ 收到?bazquux\n,而不是?bazquux。虽然这种换行处理可能会让人有些困惑,但我很确定没有什么可做的——从包装代码的角度来看,任何旋转都可能会破坏模拟的透明度。

    【讨论】:

      猜你喜欢
      • 2014-01-23
      • 1970-01-01
      • 1970-01-01
      • 2010-12-12
      • 2017-04-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多