Files
CSC110/B-python-libraries/01-doctest.html
T
Hykilpikonna 6fffdf686a deploy
2021-12-07 22:28:01 -05:00

250 lines
19 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<title>B.1 doctest</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
</style>
<link rel="stylesheet" href="../tufte.css" />
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<div style="display:none">
\(
\newcommand{\NOT}{\neg}
\newcommand{\AND}{\wedge}
\newcommand{\OR}{\vee}
\newcommand{\XOR}{\oplus}
\newcommand{\IMP}{\Rightarrow}
\newcommand{\IFF}{\Leftrightarrow}
\newcommand{\TRUE}{\text{True}\xspace}
\newcommand{\FALSE}{\text{False}\xspace}
\newcommand{\IN}{\,{\in}\,}
\newcommand{\NOTIN}{\,{\notin}\,}
\newcommand{\TO}{\rightarrow}
\newcommand{\DIV}{\mid}
\newcommand{\NDIV}{\nmid}
\newcommand{\MOD}[1]{\pmod{#1}}
\newcommand{\MODS}[1]{\ (\text{mod}\ #1)}
\newcommand{\N}{\mathbb N}
\newcommand{\Z}{\mathbb Z}
\newcommand{\Q}{\mathbb Q}
\newcommand{\R}{\mathbb R}
\newcommand{\C}{\mathbb C}
\newcommand{\cA}{\mathcal A}
\newcommand{\cB}{\mathcal B}
\newcommand{\cC}{\mathcal C}
\newcommand{\cD}{\mathcal D}
\newcommand{\cE}{\mathcal E}
\newcommand{\cF}{\mathcal F}
\newcommand{\cG}{\mathcal G}
\newcommand{\cH}{\mathcal H}
\newcommand{\cI}{\mathcal I}
\newcommand{\cJ}{\mathcal J}
\newcommand{\cL}{\mathcal L}
\newcommand{\cK}{\mathcal K}
\newcommand{\cN}{\mathcal N}
\newcommand{\cO}{\mathcal O}
\newcommand{\cP}{\mathcal P}
\newcommand{\cQ}{\mathcal Q}
\newcommand{\cS}{\mathcal S}
\newcommand{\cT}{\mathcal T}
\newcommand{\cV}{\mathcal V}
\newcommand{\cW}{\mathcal W}
\newcommand{\cZ}{\mathcal Z}
\newcommand{\emp}{\emptyset}
\newcommand{\bs}{\backslash}
\newcommand{\floor}[1]{\left \lfloor #1 \right \rfloor}
\newcommand{\ceil}[1]{\left \lceil #1 \right \rceil}
\newcommand{\abs}[1]{\left | #1 \right |}
\newcommand{\xspace}{}
\newcommand{\proofheader}[1]{\underline{\textbf{#1}}}
\)
</div>
<header id="title-block-header">
<h1 class="title">B.1 <code>doctest</code></h1>
</header>
<section>
<p><em>Adapted from <a href="https://docs.python.org/3.8/library/doctest.html" class="uri">https://docs.python.org/3.8/library/doctest.html</a>.</em></p>
<p>The <code>doctest</code> module searches for pieces of text that look like interactive Python sessions, and then executes those sessions to verify that they work exactly as shown.</p>
<p>Heres a simple standalone example:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1"></a><span class="kw">def</span> is_even(value: <span class="bu">int</span>) <span class="op">-&gt;</span> <span class="bu">bool</span>:</span>
<span id="cb1-2"><a href="#cb1-2"></a> <span class="co">&quot;&quot;&quot; Return whether value is divisible by 2.</span></span>
<span id="cb1-3"><a href="#cb1-3"></a></span>
<span id="cb1-4"><a href="#cb1-4"></a><span class="co"> &gt;&gt;&gt; is_even(2)</span></span>
<span id="cb1-5"><a href="#cb1-5"></a><span class="co"> True</span></span>
<span id="cb1-6"><a href="#cb1-6"></a><span class="co"> &gt;&gt;&gt; is_even(17)</span></span>
<span id="cb1-7"><a href="#cb1-7"></a><span class="co"> False</span></span>
<span id="cb1-8"><a href="#cb1-8"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb1-9"><a href="#cb1-9"></a> <span class="cf">return</span> value <span class="op">%</span> <span class="dv">2</span> <span class="op">==</span> <span class="dv">0</span></span></code></pre></div>
<p>The simplest way to start using <code>doctest</code> is to end each module with:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb2-1"><a href="#cb2-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&#39;__main__&#39;</span>:</span>
<span id="cb2-2"><a href="#cb2-2"></a> <span class="im">import</span> doctest</span>
<span id="cb2-3"><a href="#cb2-3"></a> doctest.testmod()</span></code></pre></div>
<p><code>doctest</code> then examines docstrings in the module.</p>
<p>Running the module as a script causes the examples in the docstrings to get executed and verified.</p>
<p>This wont display anything unless an example fails, in which case the failing example(s) and the cause(s) of the failure(s) are printed, and the final line of output is <code>***Test Failed*** N failures.</code>, where <code>N</code> is the number of examples that failed.</p>
<p>You can force verbose mode by passing <code>verbose=True</code> to <code>testmod()</code>. In this case, a detailed report of all examples tried is printed to standard output, along with assorted summaries at the end.</p>
<h2 id="how-it-works">How it works</h2>
<p>This section examines in detail how doctest works: which docstrings it looks at, how it finds interactive examples, and how it handles exceptions. This is the information that you need to know to write doctest examples; for information about actually running doctest on these examples, see the following sections.</p>
<h2 id="which-docstrings-are-examined">Which docstrings are examined?</h2>
<p>The module docstring, and all function, class and method docstrings are searched. Objects imported into the module are not searched.</p>
<h2 id="how-are-docstring-examples-recognized">How are docstring examples recognized?</h2>
<p>The module docstring, and all function, class and method docstrings are searched. Objects imported into the module are not searched.</p>
<p>In most cases a copy-and-paste of an interactive console session works fine, but doctest isnt trying to do an exact emulation of any specific Python shell.</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb3-1"><a href="#cb3-1"></a><span class="op">&gt;&gt;&gt;</span> <span class="co"># comments are ignored</span></span>
<span id="cb3-2"><a href="#cb3-2"></a><span class="op">&gt;&gt;&gt;</span> x <span class="op">=</span> <span class="dv">12</span></span>
<span id="cb3-3"><a href="#cb3-3"></a><span class="op">&gt;&gt;&gt;</span> x</span>
<span id="cb3-4"><a href="#cb3-4"></a><span class="dv">12</span></span>
<span id="cb3-5"><a href="#cb3-5"></a><span class="op">&gt;&gt;&gt;</span> <span class="cf">if</span> x <span class="op">==</span> <span class="dv">13</span>:</span>
<span id="cb3-6"><a href="#cb3-6"></a>... <span class="bu">print</span>(<span class="st">&quot;yes&quot;</span>)</span>
<span id="cb3-7"><a href="#cb3-7"></a>... <span class="cf">else</span>:</span>
<span id="cb3-8"><a href="#cb3-8"></a>... <span class="bu">print</span>(<span class="st">&quot;no&quot;</span>)</span>
<span id="cb3-9"><a href="#cb3-9"></a>... <span class="bu">print</span>(<span class="st">&quot;NO&quot;</span>)</span>
<span id="cb3-10"><a href="#cb3-10"></a>... <span class="bu">print</span>(<span class="st">&quot;NO!!!&quot;</span>)</span>
<span id="cb3-11"><a href="#cb3-11"></a>...</span>
<span id="cb3-12"><a href="#cb3-12"></a>no</span>
<span id="cb3-13"><a href="#cb3-13"></a>NO</span>
<span id="cb3-14"><a href="#cb3-14"></a>NO<span class="op">!!!</span></span>
<span id="cb3-15"><a href="#cb3-15"></a><span class="op">&gt;&gt;&gt;</span></span></code></pre></div>
<p>Any expected output must immediately follow the final <code>'&gt;&gt;&gt; '</code> or <code>'... '</code> line containing the code, and the expected output (if any) extends to the next <code>'&gt;&gt;&gt; '</code> or all-whitespace line.</p>
<p>Notes:</p>
<ul>
<li><p>Expected output cannot contain an all-whitespace line, since such a line is taken to signal the end of expected output. If expected output does contain a blank line, put <code>&lt;BLANKLINE&gt;</code> in your doctest example each place a blank line is expected.</p></li>
<li><p>This is an incorrect example because the prompt characters (i.e., <code>&gt;&gt;&gt;</code>) are missing:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb4-1"><a href="#cb4-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb4-2"><a href="#cb4-2"></a><span class="co">is_even(2)</span></span>
<span id="cb4-3"><a href="#cb4-3"></a><span class="co">True</span></span>
<span id="cb4-4"><a href="#cb4-4"></a><span class="co">&quot;&quot;&quot;</span></span></code></pre></div></li>
<li><p>This is an incorrect example because there is no space between the <code>&gt;&gt;&gt;</code> and the function call:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb5-1"><a href="#cb5-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb5-2"><a href="#cb5-2"></a><span class="co">&gt;&gt;&gt;is_even(2)</span></span>
<span id="cb5-3"><a href="#cb5-3"></a><span class="co">True</span></span>
<span id="cb5-4"><a href="#cb5-4"></a><span class="co">&quot;&quot;&quot;</span></span></code></pre></div></li>
<li><p>This is an incorrect example because the result of the function call (<code>True</code>) is not included:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb6-1"><a href="#cb6-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb6-2"><a href="#cb6-2"></a><span class="co">&gt;&gt;&gt; is_even(2)</span></span>
<span id="cb6-3"><a href="#cb6-3"></a><span class="co">&quot;&quot;&quot;</span></span></code></pre></div></li>
<li><p>This is an incorrect example because the result of the function call (<code>True</code>) is indented:</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb7-1"><a href="#cb7-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb7-2"><a href="#cb7-2"></a><span class="co">&gt;&gt;&gt; is_even(2)</span></span>
<span id="cb7-3"><a href="#cb7-3"></a><span class="co"> True</span></span>
<span id="cb7-4"><a href="#cb7-4"></a><span class="co">&quot;&quot;&quot;</span></span></code></pre></div></li>
</ul>
<h3 id="what-about-exceptions">What about exceptions?</h3>
<p>The expected output for an exception must start with a traceback header, which may be either of the following two lines, indented the same as the first line of the example:</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb8-1"><a href="#cb8-1"></a>Traceback (most recent call last):</span>
<span id="cb8-2"><a href="#cb8-2"></a>Traceback (innermost last):</span></code></pre></div>
<p>The traceback header is followed by an optional traceback stack, whose contents are ignored by doctest. The traceback stack is typically omitted, or copied verbatim from an interactive session.</p>
<p>The traceback stack is followed by the most interesting part: the line(s) containing the exception type and detail. This is usually the last line of a traceback, but can extend across multiple lines if the exception has a multi-line detail:</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb9-1"><a href="#cb9-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb9-2"><a href="#cb9-2"></a><span class="co">&gt;&gt;&gt; 1 + &#39;hi&#39;</span></span>
<span id="cb9-3"><a href="#cb9-3"></a><span class="co">Traceback (most recent call last):</span></span>
<span id="cb9-4"><a href="#cb9-4"></a><span class="co"> File &quot;&lt;stdin&gt;&quot;, line 1, in &lt;module&gt;</span></span>
<span id="cb9-5"><a href="#cb9-5"></a><span class="co">TypeError: unsupported operand type(s) for +: &#39;int&#39; and &#39;str&#39;</span></span>
<span id="cb9-6"><a href="#cb9-6"></a><span class="co">&quot;&quot;&quot;</span></span></code></pre></div>
<p>Best practice is to omit the traceback stack, unless it adds significant documentation value to the example. So the last example is probably better as:</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb10-1"><a href="#cb10-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb10-2"><a href="#cb10-2"></a><span class="co">&gt;&gt;&gt; 1 + &#39;hi&#39;</span></span>
<span id="cb10-3"><a href="#cb10-3"></a><span class="co">Traceback (most recent call last):</span></span>
<span id="cb10-4"><a href="#cb10-4"></a><span class="co">TypeError: unsupported operand type(s) for +: &#39;int&#39; and &#39;str&#39;</span></span>
<span id="cb10-5"><a href="#cb10-5"></a><span class="co">&quot;&quot;&quot;</span></span></code></pre></div>
<h2 id="warnings">Warnings</h2>
<p><code>doctest</code> is serious about requiring exact matches in expected output. If even a single character doesnt match, the test fails. This will probably surprise you a few times, as you learn exactly what Python does and doesnt guarantee about output. For example, when printing a set, Python doesnt guarantee that the element is printed in any particular order, so a test like</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb11-1"><a href="#cb11-1"></a><span class="op">&gt;&gt;&gt;</span> foo()</span>
<span id="cb11-2"><a href="#cb11-2"></a>{<span class="st">&#39;Hermione&#39;</span>, <span class="st">&#39;Harry&#39;</span>}</span></code></pre></div>
<p>is vulnerable! One workaround is to do</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb12-1"><a href="#cb12-1"></a><span class="op">&gt;&gt;&gt;</span> foo() <span class="op">==</span> {<span class="st">&#39;Hermione&#39;</span>, <span class="st">&#39;Harry&#39;</span>}</span>
<span id="cb12-2"><a href="#cb12-2"></a><span class="va">True</span></span></code></pre></div>
<p>instead. Another is to do</p>
<div class="sourceCode" id="cb13"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb13-1"><a href="#cb13-1"></a><span class="op">&gt;&gt;&gt;</span> d <span class="op">=</span> <span class="bu">sorted</span>(foo())</span>
<span id="cb13-2"><a href="#cb13-2"></a><span class="op">&gt;&gt;&gt;</span> d</span>
<span id="cb13-3"><a href="#cb13-3"></a>[<span class="st">&#39;Harry&#39;</span>, <span class="st">&#39;Hermione&#39;</span>]</span></code></pre></div>
<h2 id="soapbox">Soapbox</h2>
<p>As mentioned in the introduction, <code>doctest</code> has grown to have three primary uses:</p>
<ol type="1">
<li>Checking examples in docstrings.</li>
<li>Regression testing.</li>
<li>Executable documentation / literate testing.</li>
</ol>
<p>These uses have different requirements, and it is important to distinguish them. In particular, filling your docstrings with obscure test cases makes for bad documentation.</p>
<p>When writing a docstring, choose docstring examples with care. Theres an art to this that needs to be learned—it may not be natural at first. Examples should add genuine value to the documentation. A good example can often be worth many words. If done with care, the examples will be invaluable for your users, and will pay back the time it takes to collect them many times over as the years go by and things change. Were still amazed at how often one of our <code>doctest</code> examples stops working after a “harmless” change.</p>
</section>
<footer>
<a href="https://www.teach.cs.toronto.edu/~csc110y/fall/notes/">CSC110 Course Notes Home</a>
</footer>
</body>
</html>