diff options
-rw-r--r-- | docs/api/index.rst | 2 | ||||
-rw-r--r-- | docs/api/plugin_external_test.rst | 9 | ||||
-rw-r--r-- | docs/features/builtin_commands.rst | 2 | ||||
-rw-r--r-- | docs/features/scripting.rst | 2 | ||||
-rw-r--r-- | docs/index.rst | 9 | ||||
-rw-r--r-- | docs/plugins/external_test.rst | 18 | ||||
-rw-r--r-- | docs/testing.rst | 46 | ||||
-rw-r--r-- | noxfile.py | 1 | ||||
-rwxr-xr-x | tests/test_parsing.py | 15 |
9 files changed, 95 insertions, 9 deletions
diff --git a/docs/api/index.rst b/docs/api/index.rst index 17a25907..1a49adfa 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -32,6 +32,7 @@ This documentation is for ``cmd2`` version |version|. py_bridge table_creator utils + plugin_external_test **Modules** @@ -56,3 +57,4 @@ This documentation is for ``cmd2`` version |version|. embedded python environment to the host app - :ref:`api/table_creator:cmd2.table_creator` - table creation module - :ref:`api/utils:cmd2.utils` - various utility classes and functions +- :ref:`api/plugin_external_test:cmd2_ext_test` - External test plugin diff --git a/docs/api/plugin_external_test.rst b/docs/api/plugin_external_test.rst new file mode 100644 index 00000000..58450b11 --- /dev/null +++ b/docs/api/plugin_external_test.rst @@ -0,0 +1,9 @@ +cmd2_ext_test +============= + +External Test Plugin + + +.. autoclass:: cmd2_ext_test.ExternalTestMixin + :members: + diff --git a/docs/features/builtin_commands.rst b/docs/features/builtin_commands.rst index e08b5c24..d5112458 100644 --- a/docs/features/builtin_commands.rst +++ b/docs/features/builtin_commands.rst @@ -70,6 +70,8 @@ quit This command exits the ``cmd2`` application. +.. _feature-builtin-commands-run-pyscript: + run_pyscript ~~~~~~~~~~~~ diff --git a/docs/features/scripting.rst b/docs/features/scripting.rst index 141eaa62..f92942be 100644 --- a/docs/features/scripting.rst +++ b/docs/features/scripting.rst @@ -61,6 +61,8 @@ session. (Cmd) command # this is not a comment +.. _scripting-python-scripts: + Python Scripts -------------- diff --git a/docs/index.rst b/docs/index.rst index 9153c47f..30584f42 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -76,6 +76,15 @@ Plugins plugins/index +Testing +======= + +.. toctree:: + :maxdepth: 2 + + testing + + API Reference ============= diff --git a/docs/plugins/external_test.rst b/docs/plugins/external_test.rst index 74407b97..ac0026c6 100644 --- a/docs/plugins/external_test.rst +++ b/docs/plugins/external_test.rst @@ -5,11 +5,13 @@ Overview ~~~~~~~~ .. _cmd2_external_test_plugin: - https://github.com/python-cmd2/cmd2-ext-test/ + https://github.com/python-cmd2/cmd2/tree/cmdset_settables/plugins/ext_test -The cmd2_external_test_plugin_ supports testing of a cmd2 application by exposing access cmd2 commands with the same -context as from within a cmd2 pyscript. This allows for verification of an application's support for pyscripts and -enables the cmd2 application to be tested as part of a larger system integration test. +The `External Test Plugin <cmd2_external_test_plugin_>`_ supports testing of a cmd2 application by exposing access cmd2 +commands with the same context as from within a cmd2 :ref:`Python Scripts <scripting-python-scripts>`. This interface +captures ``stdout``, ``stderr``, as well as any application-specific data returned by the command. This also allows +for verification of an application's support for :ref:`Python Scripts <scripting-python-scripts>` and enables the cmd2 +application to be tested as part of a larger system integration test. Example cmd2 Application @@ -59,11 +61,11 @@ In your test, define a fixture for your cmd2 application Writing Tests ~~~~~~~~~~~~~ -Now write your tests that validate your application using the `app_cmd` function to access -the cmd2 application's commands. This allows invocation of the application's commands in the +Now write your tests that validate your application using the :meth:`~cmd2_ext_test.ExternalTestMixin.app_cmd()` +function to access the cmd2 application's commands. This allows invocation of the application's commands in the same format as a user would type. The results from calling a command matches what is returned -from running an python script with cmd2's pyscript command, which provides stdout, stderr, and -the command's result data. +from running an python script with cmd2's :ref:`feature-builtin-commands-run-pyscript` command, which provides +``stdout``, ``stderr``, and the command's result data. .. code-block:: python diff --git a/docs/testing.rst b/docs/testing.rst new file mode 100644 index 00000000..811e1137 --- /dev/null +++ b/docs/testing.rst @@ -0,0 +1,46 @@ +Testing +======= + +.. toctree:: + :maxdepth: 1 + +Overview +~~~~~~~~ + +This covers special considerations when writing unit tests for a cmd2 application. + + +Testing Commands +~~~~~~~~~~~~~~~~ + +The :doc:`External Test Plugin <plugins/external_test>` provides a mixin class with an :meth:`` function that +allows external calls to application commands. The :meth:`~cmd2_ext_test.ExternalTestMixin.app_cmd()` function captures +and returns stdout, stderr, and the command-specific result data. + + +Mocking +~~~~~~~ + +.. _python_mock_autospeccing: + https://docs.python.org/3/library/unittest.mock.html#autospeccing +.. _python_mock_patch: + https://docs.python.org/3/library/unittest.mock.html#patch + +If you need to mock anything in your cmd2 application, and most specifically in sub-classes of :class:`~cmd2.Cmd` or +:class:`~cmd2.command_definition.CommandSet`, you must use `Autospeccing <python_mock_autospeccing_>`_, +`spec=True <python_mock_patch_>`_, or whatever equivalant is provided in the mocking library you're using. + +In order to automatically load functions as commands cmd2 performs a number of reflection calls to look up attributes +of classes defined in your cmd2 application. Many mocking libraries will automatically create mock objects to match any +attribute being requested, regardless of whether they're present in the object being mocked. This behavior can +incorrectly instruct cmd2 to treat a function or attribute as something it needs to recognize and process. To prevent +this, you should always mock with `Autospeccing <python_mock_autospeccing_>`_ or `spec=True <python_mock_patch_>`_ +enabled. + +Example of spec=True +==================== +.. code-block:: python + + def test_mocked_methods(): + with mock.patch.object(MockMethodApp, 'foo', spec=True): + cli = MockMethodApp() @@ -6,6 +6,7 @@ def docs(session): session.install('sphinx', 'sphinx-rtd-theme', '.', + 'plugins/ext_test', ) session.chdir('docs') tmpdir = session.create_tmp() diff --git a/tests/test_parsing.py b/tests/test_parsing.py index c2c242fe..62982ab5 100755 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -836,7 +836,15 @@ def test_statement_is_immutable(): statement.raw = 'baz' -def test_is_valid_command_invalid(parser): +def test_is_valid_command_invalid(mocker, parser): + # Non-string command + valid, errmsg = parser.is_valid_command(5) + assert not valid and 'must be a string' in errmsg + + mock = mocker.MagicMock() + valid, errmsg = parser.is_valid_command(mock) + assert not valid and 'must be a string' in errmsg + # Empty command valid, errmsg = parser.is_valid_command('') assert not valid and 'cannot be an empty string' in errmsg @@ -871,6 +879,11 @@ def test_is_valid_command_valid(parser): assert valid assert not errmsg + # Subcommands can start with shortcut + valid, errmsg = parser.is_valid_command('!subcmd', is_subcommand=True) + assert valid + assert not errmsg + def test_macro_normal_arg_pattern(): # This pattern matches digits surrounded by exactly 1 brace on a side and 1 or more braces on the opposite side |