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

204 lines
13 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.2 pytest</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.2 <code>pytest</code></h1>
</header>
<section>
<p><code>pytest</code> is a Python library used to run tests for your code. In this section, well describe how to write tests that are automatically discovered and run by <code>pytest</code>, how to actually run <code>pytest</code> in your code, and some tips and tricks for making the most of <code>pytest</code>.</p>
<h2 id="how-do-you-write-a-pytest-test">How do you write a <code>pytest</code> test?</h2>
<p>A <strong>test</strong> in <code>pytest</code> is a Python function whose name starts with <code>test_</code>. Inside a test function, we use <code>assert</code> statements to verify expected values or behaviours of a function.</p>
<p>For 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="co"># This is the function to test</span></span>
<span id="cb1-2"><a href="#cb1-2"></a><span class="kw">def</span> has_more_trues(booleans: <span class="bu">list</span>) <span class="op">-&gt;</span> <span class="bu">bool</span>:</span>
<span id="cb1-3"><a href="#cb1-3"></a> <span class="co">&quot;&quot;&quot;Return whether booleans contains more True values than False values.</span></span>
<span id="cb1-4"><a href="#cb1-4"></a></span>
<span id="cb1-5"><a href="#cb1-5"></a><span class="co"> &gt;&gt;&gt; has_more_trues([True, False, True])</span></span>
<span id="cb1-6"><a href="#cb1-6"></a><span class="co"> True</span></span>
<span id="cb1-7"><a href="#cb1-7"></a><span class="co"> &gt;&gt;&gt; has_more_trues([True, False, False])</span></span>
<span id="cb1-8"><a href="#cb1-8"></a><span class="co"> False</span></span>
<span id="cb1-9"><a href="#cb1-9"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb1-10"><a href="#cb1-10"></a> <span class="co"># Function body omitted</span></span>
<span id="cb1-11"><a href="#cb1-11"></a></span>
<span id="cb1-12"><a href="#cb1-12"></a></span>
<span id="cb1-13"><a href="#cb1-13"></a><span class="co"># This the test</span></span>
<span id="cb1-14"><a href="#cb1-14"></a><span class="kw">def</span> test_mixture_one_more_true() <span class="op">-&gt;</span> <span class="va">None</span>:</span>
<span id="cb1-15"><a href="#cb1-15"></a> <span class="co">&quot;&quot;&quot;Test has_more_trues on a list with a mixture of True and False,</span></span>
<span id="cb1-16"><a href="#cb1-16"></a><span class="co"> with one more True than False.</span></span>
<span id="cb1-17"><a href="#cb1-17"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb1-18"><a href="#cb1-18"></a> <span class="cf">assert</span> has_more_trues([<span class="va">True</span>, <span class="va">False</span>, <span class="va">True</span>])</span></code></pre></div>
<p>A single test can have multiple <code>assert</code> statements, although it is generally recommended to separate each <code>assert</code> statement into a separate test. A single Python file can have multiple tests; when <code>pytest</code> is run on a file, it (by default) runs all the tests in that file.</p>
<h2 id="running-pytest">Running <code>pytest</code></h2>
<p>The simplest way of running <code>pytest</code> is to add the following <code>if __name__ == '__main__'</code> block to the bottom of a test file:</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> pytest</span>
<span id="cb2-3"><a href="#cb2-3"></a> pytest.main()</span></code></pre></div>
<p>When you run this file, <code>pytest.main</code> will run all test functions in the file. <em>Note</em>: by default, <code>pytest.main</code> actually searches through <em>all</em> Python files in the current directory whose name starts with <code>test_</code> or ends with <code>_test</code>, which can be a bit surprising. So our practice will be to explicitly pass in the name of the current test file to <code>pytest.main</code>, wrapped in a list:</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="co"># If we&#39;re in a file test_my_file.py</span></span>
<span id="cb3-2"><a href="#cb3-2"></a></span>
<span id="cb3-3"><a href="#cb3-3"></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="cb3-4"><a href="#cb3-4"></a> <span class="im">import</span> pytest</span>
<span id="cb3-5"><a href="#cb3-5"></a> pytest.main([<span class="st">&#39;test_my_file.py&#39;</span>])</span></code></pre></div>
<h2 id="testing-for-an-exceptions">Testing for an exceptions</h2>
<p>It is possible to write a <code>pytest</code> test that checks whether a function raises a specific error. To do so, use <code>pytest.raises</code>, which takes an error type as an argument, inside a <code>with</code> statement. Here is an example</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="im">import</span> pytest</span>
<span id="cb4-2"><a href="#cb4-2"></a></span>
<span id="cb4-3"><a href="#cb4-3"></a></span>
<span id="cb4-4"><a href="#cb4-4"></a><span class="kw">def</span> add_one(n):</span>
<span id="cb4-5"><a href="#cb4-5"></a> <span class="cf">return</span> n <span class="op">+</span> <span class="dv">1</span></span>
<span id="cb4-6"><a href="#cb4-6"></a></span>
<span id="cb4-7"><a href="#cb4-7"></a></span>
<span id="cb4-8"><a href="#cb4-8"></a><span class="kw">def</span> test_add_one_type_error():</span>
<span id="cb4-9"><a href="#cb4-9"></a> <span class="co">&quot;&quot;&quot;Test add_one when given a non-numeric argument.&quot;&quot;&quot;</span></span>
<span id="cb4-10"><a href="#cb4-10"></a> <span class="cf">with</span> pytest.raises(<span class="pp">TypeError</span>):</span>
<span id="cb4-11"><a href="#cb4-11"></a> add_one(<span class="st">&#39;hello&#39;</span>)</span></code></pre></div>
<h2 id="options-for-pytest.main">Options for <code>pytest.main</code></h2>
<p><code>pytest.main</code> takes a list of strings as an argument because users can add options (as strings) to modify <code>pytest</code>s default behaviour when running tests. The format for this is <code>pytest.main([&lt;option1&gt;, &lt;option2&gt;, ...])</code>.</p>
<p>Here are some useful options:</p>
<ul>
<li><code>'&lt;filename&gt;'</code>: as we saw above, adding a filename restricts <code>pytest</code> to only running the tests in that Python file.</li>
<li><code>'&lt;filename&gt;::&lt;test_name&gt;'</code>: restrict <code>pytest</code> to run a specific test in the given file (e.g., <code>'test_my_file.py::test_1'</code>)</li>
<li><code>'-x'</code>: stop running tests after the first failure (by default, <code>pytest</code> runs all tests, regardless of the number of failures)</li>
<li><code>'--pdb'</code>: start the Python debugger when a test fails</li>
</ul>
<h2 id="references">References</h2>
<p>For the full documentation for the <code>pytest</code> library, check out <a href="https://docs.pytest.org/en/latest/" class="uri">https://docs.pytest.org/en/latest/</a>.</p>
</section>
<footer>
<a href="https://www.teach.cs.toronto.edu/~csc110y/fall/notes/">CSC110 Course Notes Home</a>
</footer>
</body>
</html>