|
| 1 | +======================== |
| 2 | +Transcript based testing |
| 3 | +======================== |
| 4 | + |
| 5 | +A transcript is both the input and output of a successful session of a |
| 6 | +``cmd2``-based app which is saved to a text file. The transcript can be played |
| 7 | +back into the app as a unit test. You can embed regular expressions into the |
| 8 | +transcript to accomodate commands that produce dynamic or variable output. |
| 9 | + |
| 10 | + |
| 11 | +Creating a transcript |
| 12 | +===================== |
| 13 | + |
| 14 | +Here's a transcript created from ``python examples/example.py``: |
| 15 | + |
| 16 | +.. code-block:: none |
| 17 | +
|
| 18 | + (Cmd) say -r 3 Goodnight, Gracie |
| 19 | + Goodnight, Gracie |
| 20 | + Goodnight, Gracie |
| 21 | + Goodnight, Gracie |
| 22 | + (Cmd) mumble maybe we could go to lunch |
| 23 | + like maybe we ... could go to hmmm lunch |
| 24 | + (Cmd) mumble maybe we could go to lunch |
| 25 | + well maybe we could like go to er lunch right? |
| 26 | +
|
| 27 | +This transcript has three commands: you can see them on the lines that begin |
| 28 | +with the prompt, which in this case is ``(Cmd) ``. Following each command is |
| 29 | +the output generated by that command. |
| 30 | +
|
| 31 | +Any lines in the transcript before the first line that begins with the prompt |
| 32 | +are ignored. You can take advantage of this by using the first lines of the |
| 33 | +transcript as comments. |
| 34 | +
|
| 35 | +.. code-block:: none |
| 36 | +
|
| 37 | + # Lines at the beginning of the transcript that do not |
| 38 | + ; start with the prompt i.e. '(Cmd) ' are ignored. |
| 39 | + /* You can use them for comments. */ |
| 40 | + |
| 41 | + All six of these lines before the first prompt are treated as comments. |
| 42 | + |
| 43 | + (Cmd) say -r 3 Goodnight, Gracie |
| 44 | + Goodnight, Gracie |
| 45 | + Goodnight, Gracie |
| 46 | + Goodnight, Gracie |
| 47 | + (Cmd) mumble maybe we could go to lunch |
| 48 | + like maybe we ... could go to hmmm lunch |
| 49 | + (Cmd) mumble maybe we could go to lunch |
| 50 | + maybe we could like go to er lunch right? |
| 51 | +
|
| 52 | +In this example I've used several different commenting styles, and even bare |
| 53 | +text. It doesn't matter what you put on those beginning lines. Everything before |
| 54 | +the first line that starts with ``(Cmd) `` will be ignored. |
| 55 | +
|
| 56 | +If we used this transcript as-is, it would likely fail. As you can see, the |
| 57 | +``mumble`` command doesn't always return the same thing. The ``mumble`` command |
| 58 | +inserts random words into the input. Transcripts can include regular |
| 59 | +expressions as a way to check for output that can change. |
| 60 | + |
| 61 | +Regular expressions can be included in the response portion of a transcript, |
| 62 | +and are surrounded by slashes. |
| 63 | + |
| 64 | +.. code-block:: none |
| 65 | +
|
| 66 | + (Cmd) mumble maybe we could go to lunch |
| 67 | + /.*\bmaybe\b.*\bcould\b.*\blunch\b.*/ |
| 68 | + (Cmd) mumble maybe we could go to lunch |
| 69 | + /.*\bmaybe\b.*\bcould\b.*\blunch\b.*/ |
| 70 | +
|
| 71 | +Without creating a tutorial on regular expressions, this one matches anything |
| 72 | +that has the words `maybe`, `could`, and `lunch` in that order. It doesn't |
| 73 | +ensure that `we` or `go` or `to` appear in the output, but it does work if |
| 74 | +mumble happens to add words to the beginning or the end of the output. |
| 75 | + |
| 76 | +Since the output could be multiple lines long, ``cmd2`` uses multiline regular |
| 77 | +expression matching, and also uses the ``DOTALL`` flag, which subtly changes the behavior of commonly |
| 78 | +used special characters like `.`, `^` and `$`, so you may want to double check the |
| 79 | +`Python regular expression documentation |
| 80 | +<https://docs.python.org/3/library/re.html>`_. |
| 81 | + |
| 82 | +If your output has slashes in it, you will need to escape those slashes so the |
| 83 | +stuff between them is not interpred as a regular expression. In this transcript:: |
| 84 | + |
| 85 | + (Cmd) say cd /usr/local/lib/python3.6/site-packages |
| 86 | + /usr/local/lib/python3.6/site-packages |
| 87 | + |
| 88 | +the output contains slashes. The text between the first slash and the second |
| 89 | +slash, (``usr``) will be interpreted as a regular expression, and those two |
| 90 | +slashes will not be included in the comparison. When replayed, this transcript |
| 91 | +would therefore fail. To fix it, we could either write a regular expression to |
| 92 | +match the path instead of specifying it verbatim, or we can escape the slashes:: |
| 93 | + |
| 94 | + (Cmd) say cd /usr/local/lib/python3.6/site-packages |
| 95 | + \/usr\/local\/lib\/python3.6\/site-packages |
| 96 | + |
| 97 | + |
| 98 | +Running a transcript |
| 99 | +==================== |
| 100 | + |
| 101 | +Once you have created a transcript, it's easy to have your application play it |
| 102 | +back and check the output. From within the ``examples/`` directory: |
| 103 | + |
| 104 | +.. code-block:: none |
| 105 | +
|
| 106 | + $ python example.py --test transcript_regex.txt |
| 107 | + . |
| 108 | + ---------------------------------------------------------------------- |
| 109 | + Ran 1 test in 0.013s |
| 110 | +
|
| 111 | + OK |
| 112 | +
|
| 113 | +The output will look familiar if you use ``unittest``, because that's exactly |
| 114 | +what happens. Each command in the transcript is run, and the output is |
| 115 | +``asserted`` to match expected result from the transcript. |
| 116 | + |
| 117 | +.. note:: |
| 118 | + |
| 119 | + If you have set ``allow_cli_args`` to False in order to disable parsing of |
| 120 | + command line arguments at invocation, then the use of ``-t`` or ``--test`` |
| 121 | + to run transcript testing is automatically disabled. In this case, you can |
| 122 | + alternatively provide a value for the optional ``transcript_files`` when |
| 123 | + constructing the instance of your ``cmd2.Cmd`` derived class in order to |
| 124 | + cause a transcript test to run:: |
| 125 | + |
| 126 | + from cmd2 import Cmd |
| 127 | + class App(Cmd): |
| 128 | + # customized attributes and methods here |
| 129 | + |
| 130 | + if __name__ == '__main__': |
| 131 | + app = App(transcript_files=['exampleSession.txt']) |
| 132 | + app.cmdloop() |
0 commit comments