If you search Google or SO for 'unit test stdin stdout python' you will find very many questions, each and every one of which is answered in one way or another with
Do you really need to unit test Python's builtin
input
/sys.stdin
methods?
My answer is yes, I emphatically do, because I'm essentially implementing my own input
+ poor-man's libreadline
/ libcurses
, and I need to test using stdin and the contents of the terminal.
I happen to use a Unix-derived OS so I have pipes |
and shell redirection <
, so I could write a little shell script to do this alongside some Python helper code, and to test the terminal's actions (ANSI escape sequences, cursor movement, exactly what gets printed, etc) I could read from a known /dev/tty/whatever
, but there are two main reasons I don't want to do this:
Testing code should be as cross-platform as the code it's testing (and not so fragile!)
I quite like
unittest
, thank you, and I don't want to resort to shell scripting and unix hackery (as much as I like unix hackery) just to test my module.
There must be a better way to test things like curses
, not when you're using curses
but when you're developing a curses
.
Since it was requested, here's some examples of what I'm looking to test: (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)