summaryrefslogtreecommitdiff
path: root/docs/features/transcript.rst
diff options
context:
space:
mode:
authorkotfu <kotfu@kotfu.net>2019-07-02 19:02:36 -0600
committerkotfu <kotfu@kotfu.net>2019-07-02 19:02:36 -0600
commit92ae130c38520b249eb7351cfb0da1ad67d3d3cf (patch)
treea403641a1a9412b19e26b52fae83635d812f9409 /docs/features/transcript.rst
parent80950bfa4216ed20df5d63f1ebe63bac5b3746b4 (diff)
downloadcmd2-git-92ae130c38520b249eb7351cfb0da1ad67d3d3cf.tar.gz
Major overhaul of documentation structure for #709
Diffstat (limited to 'docs/features/transcript.rst')
-rw-r--r--docs/features/transcript.rst193
1 files changed, 193 insertions, 0 deletions
diff --git a/docs/features/transcript.rst b/docs/features/transcript.rst
new file mode 100644
index 00000000..089ab704
--- /dev/null
+++ b/docs/features/transcript.rst
@@ -0,0 +1,193 @@
+========================
+Transcript based testing
+========================
+
+A transcript is both the input and output of a successful session of a
+``cmd2``-based app which is saved to a text file. With no extra work on your
+part, your app can play back these transcripts as a unit test. Transcripts can
+contain regular expressions, which provide the flexibility to match responses
+from commands that produce dynamic or variable output.
+
+.. highlight:: none
+
+Creating a transcript
+=====================
+
+Automatically from history
+--------------------------
+A transcript can automatically generated based upon commands previously executed in the *history* using ``history -t``::
+
+ (Cmd) help
+ ...
+ (Cmd) help history
+ ...
+ (Cmd) history 1:2 -t transcript.txt
+ 2 commands and outputs saved to transcript file 'transcript.txt'
+
+This is by far the easiest way to generate a transcript.
+
+.. warning::
+
+ Make sure you use the **poutput()** method in your ``cmd2`` application for generating command output. This method
+ of the ``cmd2.Cmd`` class ensure that output is properly redirected when redirecting to a file, piping to a shell
+ command, and when generating a transcript.
+
+Automatically from a script file
+--------------------------------
+A transcript can also be automatically generated from a script file using ``run_script -t``::
+
+ (Cmd) run_script scripts/script.txt -t transcript.txt
+ 2 commands and their outputs saved to transcript file 'transcript.txt'
+ (Cmd)
+
+This is a particularly attractive option for automatically regenerating transcripts for regression testing as your ``cmd2``
+application changes.
+
+Manually
+--------
+Here's a transcript created from ``python examples/example.py``::
+
+ (Cmd) say -r 3 Goodnight, Gracie
+ Goodnight, Gracie
+ Goodnight, Gracie
+ Goodnight, Gracie
+ (Cmd) mumble maybe we could go to lunch
+ like maybe we ... could go to hmmm lunch
+ (Cmd) mumble maybe we could go to lunch
+ well maybe we could like go to er lunch right?
+
+This transcript has three commands: they are on the lines that begin with the
+prompt. The first command looks like this::
+
+ (Cmd) say -r 3 Goodnight, Gracie
+
+Following each command is the output generated by that command.
+
+The transcript ignores all lines in the file until it reaches the first line
+that begins with the prompt. You can take advantage of this by using the first
+lines of the transcript as comments::
+
+ # Lines at the beginning of the transcript that do not
+ ; start with the prompt i.e. '(Cmd) ' are ignored.
+ /* You can use them for comments. */
+
+ All six of these lines before the first prompt are treated as comments.
+
+ (Cmd) say -r 3 Goodnight, Gracie
+ Goodnight, Gracie
+ Goodnight, Gracie
+ Goodnight, Gracie
+ (Cmd) mumble maybe we could go to lunch
+ like maybe we ... could go to hmmm lunch
+ (Cmd) mumble maybe we could go to lunch
+ maybe we could like go to er lunch right?
+
+In this example I've used several different commenting styles, and even bare
+text. It doesn't matter what you put on those beginning lines. Everything before::
+
+ (Cmd) say -r 3 Goodnight, Gracie
+
+will be ignored.
+
+
+Regular Expressions
+===================
+
+If we used the above transcript as-is, it would likely fail. As you can see,
+the ``mumble`` command doesn't always return the same thing: it inserts random
+words into the input.
+
+Regular expressions can be included in the response portion of a transcript,
+and are surrounded by slashes::
+
+ (Cmd) mumble maybe we could go to lunch
+ /.*\bmaybe\b.*\bcould\b.*\blunch\b.*/
+ (Cmd) mumble maybe we could go to lunch
+ /.*\bmaybe\b.*\bcould\b.*\blunch\b.*/
+
+Without creating a tutorial on regular expressions, this one matches anything
+that has the words ``maybe``, ``could``, and ``lunch`` in that order. It doesn't
+ensure that ``we`` or ``go`` or ``to`` appear in the output, but it does work if
+mumble happens to add words to the beginning or the end of the output.
+
+Since the output could be multiple lines long, ``cmd2`` uses multiline regular
+expression matching, and also uses the ``DOTALL`` flag. These two flags subtly
+change the behavior of commonly used special characters like ``.``, ``^`` and
+``$``, so you may want to double check the `Python regular expression
+documentation <https://docs.python.org/3/library/re.html>`_.
+
+If your output has slashes in it, you will need to escape those slashes so the
+stuff between them is not interpred as a regular expression. In this transcript::
+
+ (Cmd) say cd /usr/local/lib/python3.6/site-packages
+ /usr/local/lib/python3.6/site-packages
+
+the output contains slashes. The text between the first slash and the second
+slash, will be interpreted as a regular expression, and those two slashes will
+not be included in the comparison. When replayed, this transcript would
+therefore fail. To fix it, we could either write a regular expression to match
+the path instead of specifying it verbatim, or we can escape the slashes::
+
+ (Cmd) say cd /usr/local/lib/python3.6/site-packages
+ \/usr\/local\/lib\/python3.6\/site-packages
+
+.. warning::
+
+ Be aware of trailing spaces and newlines. Your commands might output
+ trailing spaces which are impossible to see. Instead of leaving them
+ invisible, you can add a regular expression to match them, so that you can
+ see where they are when you look at the transcript::
+
+ (Cmd) set prompt
+ prompt: (Cmd)/ /
+
+ Some terminal emulators strip trailing space when you copy text from them.
+ This could make the actual data generated by your app different than the
+ text you pasted into the transcript, and it might not be readily obvious why
+ the transcript is not passing. Consider using :ref:`output_redirection` to
+ the clipboard or to a file to ensure you accurately capture the output of
+ your command.
+
+ If you aren't using regular expressions, make sure the newlines at the end
+ of your transcript exactly match the output of your commands. A common cause
+ of a failing transcript is an extra or missing newline.
+
+ If you are using regular expressions, be aware that depending on how you
+ write your regex, the newlines after the regex may or may not matter.
+ ``\Z`` matches *after* the newline at the end of the string, whereas
+ ``$`` matches the end of the string *or* just before a newline.
+
+
+Running a transcript
+====================
+
+Once you have created a transcript, it's easy to have your application play it
+back and check the output. From within the ``examples/`` directory::
+
+ $ python example.py --test transcript_regex.txt
+ .
+ ----------------------------------------------------------------------
+ Ran 1 test in 0.013s
+
+ OK
+
+The output will look familiar if you use ``unittest``, because that's exactly
+what happens. Each command in the transcript is run, and we ``assert`` the
+output matches the expected result from the transcript.
+
+.. note::
+
+ If you have set ``allow_cli_args`` to False in order to disable parsing of
+ command line arguments at invocation, then the use of ``-t`` or ``--test``
+ to run transcript testing is automatically disabled. In this case, you can
+ alternatively provide a value for the optional ``transcript_files`` when
+ constructing the instance of your ``cmd2.Cmd`` derived class in order to
+ cause a transcript test to run::
+
+ from cmd2 import Cmd
+ class App(Cmd):
+ # customized attributes and methods here
+
+ if __name__ == '__main__':
+ app = App(transcript_files=['exampleSession.txt'])
+ app.cmdloop()