[Tutor] how to unittest cli input

Ben Finney ben+python at benfinney.id.au
Sun Oct 11 03:14:52 CEST 2015


Alex Kleider <akleider at sonic.net> writes:

> """
> I'm trying to follow a test driven development paradigm (using
> unittest) but can't figure out how to test functions that collect
> info from the command line such as the following.
> """
> # collect.py
> def collect_data():
>     ret = {}
>     ret['first'] = input("Enter your first name: ")
>     ret['last'] = input("Enter your last name: ")
>     ret['phone'] = input("Your mobile phone #: ")
>     return ret

To collect information from the command line, a program interrogates
‘sys.argv’.

The example you show, rather, gets *interactive* input from the user.
That's not command-line, so it's important to get the terminology right.

You can write unit tests for this kind of function by making a “test
double” for the ‘input’ function: an instrumented replacement (a
“double”) that behaves exactly as you determine in the test case.

One common way is to make a collection of data scenarios, and write test
cases that use each scenario to set up the environment, call the
function, and make one assertion about the result.

    import builtins
    import unittest
    import unittest.mock

    import testscenarios

    from .. import collect

    class collect_data_TestCase(
            testscenarios.WithScenarios, unittest.TestCase):
        """ Test cases for the `collect_data` function. """

        scenarios = [
                ('simple', {
                    'input_lines': [
                        "Lorem",
                        "Ipsum",
                        "+61 412 345 678",
                        ],
                    'expected_result': {
                        'first': "Lorem",
                        'last': "Ipsum",
                        'phone': "+61 412 345 678",
                        },
                    }),
                ('empty', {
                    'input_lines': [""] * 3,
                    'expected_result': {
                        'first': "", 'last': "", 'phone': ""},
                    }),
                ]

        def setUp(self):
            """ Set up test fixtures for the test case. """

            # Perform setup from all superclasses. This ensures each
            # test case will get attributes assigned from the scenario.
            super().setUp()

            # Makes a ‘patcher’ to patch the ‘builtins.input’
            # function, replacing it with an instrumented test double
            # (a MagicMock) that will return the next ‘input_lines’
            # value each time it is called.
            func_patcher = unittest.mock.patch.object(
                    builtins, "input",
                    side_effect=self.input_lines)

            # Start the patcher (i.e. replace the real function) for
            # this test case.
            func_patcher.start()

            # Register the patcher's ‘stop’ (i.e. restore the original
            # function) as a clean-up action for this test case.
            self.addCleanup(func_patcher.stop)

        def test_returns_expected_result(self):
            """ Should return the result expected for the intputs. """
            result = collect.collect_data()
            self.assertEqual(self.expected_result, result)

The ‘testscenarios’ third-party library is recommended for data-driven
tests <URL:https://pypi.python.org/pypi/testscenarios/>. It will, at
run-time, create a separate test case for each test case function × each
scenario defined for that function's class.

All the produced test cases will run separately, identified by the test
case method and the scenario name; and each one will fail or pass
separately. This makes it very easy to test a matrix of assertions
versus input/output expectations.

The ‘unittest.mock’ library is part of the Python 3 standard library,
and is good for creating powerful test doubles
<URL:http://xunitpatterns.com/Test%20Double.html>.

-- 
 \     “Airports are ugly. Some are very ugly. Some attain a degree of |
  `\        ugliness that can only be the result of a special effort.” |
_o__)       —Douglas Adams, _The Long Dark Tea-Time of the Soul_, 1988 |
Ben Finney



More information about the Tutor mailing list