Creating automated tests for an interactive shell based on the cmd module in Python
I am creating an interactive shell using Python 3 and the cmd module. I've already written simple unit tests using py.test to test individual functions like do_ * functions. I would like to create more comprehensive tests that actually interact with the shell itself, simulating user input. For example, how can I test the following simulated session:
bash$ console-app.py
md:> show options
Available Options:
------------------
HOST The IP address or hostname of the machine to interact with
PORT The TCP port number of the server on the HOST
md:> set HOST localhost
HOST => 'localhost'
md:> set PORT 2222
PORT => '2222'
md:>
source to share
You can mock
input
either the input stream passed to the cmd to enter user input, but I find it easier and more flexible by checking it with the onecmd()
Cmd
API method and trusting how Cmd
to read the input. So you don't care how Cmd
to do the dirty work and check directly with the user command: I use Cmd
both with console and socket, and it doesn't bother me where the stream is coming from.
Also, I use onecmd()
to test even methods do_*
(and sometimes help_*
) and make my test less code-related.
Follow a simple use case. create()
and _last_write()
are helper methods for instantiation MyCLI
and take the last output lines accordingly.
from mymodule import MyCLI
from unittest.mock import create_autospec
class TestMyCLI(unittest.TestCase):
def setUp(self):
self.mock_stdin = create_autospec(sys.stdin)
self.mock_stdout = create_autospec(sys.stdout)
def create(self, server=None):
return MyCLI(stdin=self.mock_stdin, stdout=self.mock_stdout)
def _last_write(self, nr=None):
""":return: last `n` output lines"""
if nr is None:
return self.mock_stdout.write.call_args[0][0]
return "".join(map(lambda c: c[0][0], self.mock_stdout.write.call_args_list[-nr:]))
def test_active(self):
"""Tesing `active` command"""
cli = self.create()
self.assertFalse(cli.onecmd("active"))
self.assertTrue(self.mock_stdout.flush.called)
self.assertEqual("Autogain active=False\n", self._last_write())
self.mock_stdout.reset_mock()
self.assertFalse(cli.onecmd("active TRue"))
self.assertTrue(self.mock_stdout.flush.called)
self.assertEqual("Autogain active=True\n", self._last_write())
self.assertFalse(cli.onecmd("active 0"))
self.assertTrue(self.mock_stdout.flush.called)
self.assertEqual("Autogain active=False\n", self._last_write())
def test_exit(self):
"""exit command"""
cli = self.create()
self.assertTrue(cli.onecmd("exit"))
self.assertEqual("Goodbay\n", self._last_write())
Make sure to onecmd()
return True
if your cli should exit False
otherwise.
source to share