Files
CSC110/05-memory-model/06-testing-functions-3.html
Hykilpikonna 6fffdf686a deploy
2021-12-07 22:28:01 -05:00

234 lines
20 KiB
HTML
Raw Permalink 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>5.6 Testing Functions III: Testing Mutation</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">5.6 Testing Functions III: Testing Mutation</h1>
</header>
<section>
<p>The ability to mutate objects means that we have to be careful when writing functions that accept mutable types as parameters. In general, if a functions documentation does not specify that an object will be mutated, then it <strong>must not</strong> be mutated. How can we test that no mutation occured? And, for functions that intend to mutate an object, how can we test that the correct change occured? In this section, we will extend our study of writing tests to answer both of these questions.</p>
<h2 id="identifying-mutable-parameters">Identifying mutable parameters</h2>
<p>Consider the <code>squares</code> function we introduced at the beginning of the chapter:</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> squares(nums: <span class="bu">list</span>[<span class="bu">int</span>]) <span class="op">-&gt;</span> <span class="bu">list</span>[<span class="bu">int</span>]:</span>
<span id="cb1-2"><a href="#cb1-2"></a> <span class="co">&quot;&quot;&quot;Return a list of the squares of the given numbers.&quot;&quot;&quot;</span></span>
<span id="cb1-3"><a href="#cb1-3"></a> squares_so_far <span class="op">=</span> []</span>
<span id="cb1-4"><a href="#cb1-4"></a></span>
<span id="cb1-5"><a href="#cb1-5"></a> <span class="cf">for</span> num <span class="kw">in</span> nums:</span>
<span id="cb1-6"><a href="#cb1-6"></a> <span class="bu">list</span>.append(squares_so_far, num <span class="op">*</span> num)</span>
<span id="cb1-7"><a href="#cb1-7"></a></span>
<span id="cb1-8"><a href="#cb1-8"></a> <span class="cf">return</span> squares_so_far</span></code></pre></div>
<p>There are two lists in <code>squares</code>: the <code>nums</code> parameter, which is an input to the function; and the <code>squares_so_far</code> variable, which is an output of the function. Because <code>squares_so_far</code> is created by the function <code>squares</code>, it is okay that it is mutated (i.e., the call to <code>list.append</code> inside the for loop). However, the <code>nums</code> list is passed as an argument to <code>squares</code>. Because the docstring does not indicate that <code>nums</code> will be mutated, it is expected that the <code>squares</code> function will not mutate the list object referred to by <code>nums</code>.</p>
<p>We can contrast this with how we would document and implement a similar function that <em>does</em> mutate its input:</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="kw">def</span> square_all(nums: <span class="bu">list</span>[<span class="bu">int</span>]) <span class="op">-&gt;</span> <span class="va">None</span>:</span>
<span id="cb2-2"><a href="#cb2-2"></a> <span class="co">&quot;&quot;&quot;Modify nums by squaring each of its elements.&quot;&quot;&quot;</span></span>
<span id="cb2-3"><a href="#cb2-3"></a> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">0</span>, <span class="bu">len</span>(nums)):</span>
<span id="cb2-4"><a href="#cb2-4"></a> nums[i] <span class="op">=</span> nums[i] <span class="op">*</span> nums[i]</span></code></pre></div>
<h2 id="testing-for-no-mutation">Testing for no mutation</h2>
<p>Let us write a test that ensures the <code>squares</code> function does not mutate the list referred to by <code>nums</code>:</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="kw">def</span> test_squares_no_mutation() <span class="op">-&gt;</span> <span class="va">None</span>:</span>
<span id="cb3-2"><a href="#cb3-2"></a> <span class="co">&quot;&quot;&quot;Confirm that squares does not mutate the list it is given.</span></span>
<span id="cb3-3"><a href="#cb3-3"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb3-4"><a href="#cb3-4"></a> lst <span class="op">=</span> [<span class="dv">1</span>, <span class="dv">2</span>, <span class="dv">3</span>]</span>
<span id="cb3-5"><a href="#cb3-5"></a> squares(lst)</span>
<span id="cb3-6"><a href="#cb3-6"></a></span>
<span id="cb3-7"><a href="#cb3-7"></a> <span class="co"># </span><span class="al">TODO</span><span class="co">: complete the test</span></span></code></pre></div>
<p>In order to test that a list is not mutated, we first create a list <code>lst</code>. Second, we call the <code>squares</code> function on <code>lst</code>; note that this function call returns a list of squares, but we do not assign the result to a variable because we dont actually care about the returned value for the purpose of this test.<label for="sn-0" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-0" class="margin-toggle"/><span class="sidenote"> This might seem a bit strange, as all of our tests so far have been about checking the return value of the function being tested. In practice, we would have such unit/property-based tests for <code>squares</code> as well, we just arent showing them here.</span> We can now add an assertion that ensures <code>lst</code> has not been mutated:</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="kw">def</span> test_squares_no_mutation() <span class="op">-&gt;</span> <span class="va">None</span>:</span>
<span id="cb4-2"><a href="#cb4-2"></a> <span class="co">&quot;&quot;&quot;Test that squares does not mutate the list it is given.</span></span>
<span id="cb4-3"><a href="#cb4-3"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb4-4"><a href="#cb4-4"></a> lst <span class="op">=</span> [<span class="dv">1</span>, <span class="dv">2</span>, <span class="dv">3</span>]</span>
<span id="cb4-5"><a href="#cb4-5"></a> squares(lst)</span>
<span id="cb4-6"><a href="#cb4-6"></a></span>
<span id="cb4-7"><a href="#cb4-7"></a> <span class="cf">assert</span> lst <span class="op">==</span> [<span class="dv">1</span>, <span class="dv">2</span>, <span class="dv">3</span>]</span></code></pre></div>
<p>The variable <code>lst</code> originally had value <code>[1, 2, 3]</code>. So our assertion checks that <em>after</em> the call to <code>squares</code>, <code>lst</code> still has value <code>[1, 2, 3]</code>. Another way to accomplish this, without re-typing the list value, is by creating a copy of <code>lst</code> before the call to <code>squares</code>. We can do this using the <code>list.copy</code> method:</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="kw">def</span> test_squares_no_mutation() <span class="op">-&gt;</span> <span class="va">None</span>:</span>
<span id="cb5-2"><a href="#cb5-2"></a> <span class="co">&quot;&quot;&quot;Test that squares does not mutate the list it is given.</span></span>
<span id="cb5-3"><a href="#cb5-3"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb5-4"><a href="#cb5-4"></a> lst <span class="op">=</span> [<span class="dv">1</span>, <span class="dv">2</span>, <span class="dv">3</span>]</span>
<span id="cb5-5"><a href="#cb5-5"></a> lst_copy <span class="op">=</span> <span class="bu">list</span>.copy(lst) <span class="co"># Create a copy of lst (not an alias!)</span></span>
<span id="cb5-6"><a href="#cb5-6"></a> squares(lst)</span>
<span id="cb5-7"><a href="#cb5-7"></a></span>
<span id="cb5-8"><a href="#cb5-8"></a> <span class="cf">assert</span> lst <span class="op">==</span> lst_copy</span></code></pre></div>
<p>Note that the order of statements is very important when testing for mutation. We need to create the list and its copy before the call to <code>squares</code>. And we need to test for mutation (i.e., the assertion) after the call to <code>squares</code>.</p>
<h3 id="generalizing-this-test">Generalizing this test</h3>
<p>You might notice that the above <code>test_squares_no_mutation</code> test function doesnt actually use the specific elements of the list <code>lst</code>. That is, if we replaced <code>lst</code>s value with another list, the test would behave in the exact same way. That makes this test very suitable to be generalized into a <em>property-based test</em>, representing the following property:</p>
<blockquote>
<p>For all lists of integers <code>lst</code>, calling <code>squares(lst)</code> does not mutate <code>lst</code>.</p>
</blockquote>
<p>Here is how we could implement such a property-based test using the technique we learned in <a href="../03-logic/10-testing-fucntions-2.html">3.10 Testing Functions II: <code>hypothesis</code></a>.<label for="sn-1" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-1" class="margin-toggle"/><span class="sidenote"> Weve included the import statements to remind you about the ones from <code>hypothesis</code> you need for property-based tests.</span></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="im">from</span> hypothesis <span class="im">import</span> given</span>
<span id="cb6-2"><a href="#cb6-2"></a><span class="im">from</span> hypothesis.strategies <span class="im">import</span> lists, integers</span>
<span id="cb6-3"><a href="#cb6-3"></a></span>
<span id="cb6-4"><a href="#cb6-4"></a></span>
<span id="cb6-5"><a href="#cb6-5"></a><span class="at">@given</span>(lst<span class="op">=</span>lists(integers()))</span>
<span id="cb6-6"><a href="#cb6-6"></a><span class="kw">def</span> test_squares_no_mutation_general(lst: <span class="bu">list</span>[<span class="bu">int</span>]) <span class="op">-&gt;</span> <span class="va">None</span>:</span>
<span id="cb6-7"><a href="#cb6-7"></a> <span class="co">&quot;&quot;&quot;Test that squares does not mutate the list it is given.</span></span>
<span id="cb6-8"><a href="#cb6-8"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb6-9"><a href="#cb6-9"></a> lst_copy <span class="op">=</span> <span class="bu">list</span>.copy(lst) <span class="co"># Create a copy of lst (not an alias!)</span></span>
<span id="cb6-10"><a href="#cb6-10"></a> squares(lst)</span>
<span id="cb6-11"><a href="#cb6-11"></a></span>
<span id="cb6-12"><a href="#cb6-12"></a> <span class="cf">assert</span> lst <span class="op">==</span> lst_copy</span></code></pre></div>
<h2 id="testing-for-mutation">Testing for mutation</h2>
<p>Now lets consider testing the <code>square_all</code> function. One common error students make when writing tests for mutating functions is to check the return value of the function.</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="kw">def</span> test_square_all() <span class="op">-&gt;</span> <span class="va">None</span>:</span>
<span id="cb7-2"><a href="#cb7-2"></a> <span class="co">&quot;&quot;&quot;Test that square_all mutates the list it is given correctly.</span></span>
<span id="cb7-3"><a href="#cb7-3"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb7-4"><a href="#cb7-4"></a> lst <span class="op">=</span> [<span class="dv">1</span>, <span class="dv">2</span>, <span class="dv">3</span>]</span>
<span id="cb7-5"><a href="#cb7-5"></a> result <span class="op">=</span> square_all(lst)</span>
<span id="cb7-6"><a href="#cb7-6"></a></span>
<span id="cb7-7"><a href="#cb7-7"></a> <span class="cf">assert</span> result <span class="op">==</span> [<span class="dv">1</span>, <span class="dv">4</span>, <span class="dv">9</span>]</span></code></pre></div>
<p>This test fails because <code>square_all</code> returns <code>None</code>, and <code>None == [1, 4, 9]</code> is False. Using <code>result</code> in our assertion is not useful for testing if <code>lst</code> was mutated. Instead, we must test if the value of <code>lst</code> has changed:<label for="sn-2" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-2" class="margin-toggle"/><span class="sidenote"> Like <code>test_squares_no_mutation</code>, this test does not store the return value of the function being tested. But the reason is quite different!</span></p>
<div class="sourceCode" id="cb8"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb8-1"><a href="#cb8-1"></a><span class="kw">def</span> test_square_all_mutation() <span class="op">-&gt;</span> <span class="va">None</span>:</span>
<span id="cb8-2"><a href="#cb8-2"></a> <span class="co">&quot;&quot;&quot;Test that square_all mutates the list it is given correctly.</span></span>
<span id="cb8-3"><a href="#cb8-3"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb8-4"><a href="#cb8-4"></a> lst <span class="op">=</span> [<span class="dv">1</span>, <span class="dv">2</span>, <span class="dv">3</span>]</span>
<span id="cb8-5"><a href="#cb8-5"></a> square_all(lst)</span>
<span id="cb8-6"><a href="#cb8-6"></a></span>
<span id="cb8-7"><a href="#cb8-7"></a> <span class="cf">assert</span> lst <span class="op">==</span> [<span class="dv">1</span>, <span class="dv">4</span>, <span class="dv">9</span>]</span></code></pre></div>
<p>We can again generalize this test into a property-based test by storing a copy of the original list and verifying the relationship between corresponding elements. Well leave it as an exercise for you to read through and understand the following property-based test:</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="at">@given</span>(lst<span class="op">=</span>lists(integers()))</span>
<span id="cb9-2"><a href="#cb9-2"></a><span class="kw">def</span> test_square_all_mutation_general(lst: <span class="bu">list</span>[<span class="bu">int</span>]) <span class="op">-&gt;</span> <span class="va">None</span>:</span>
<span id="cb9-3"><a href="#cb9-3"></a> <span class="co">&quot;&quot;&quot;Test that square_all mutates the list it is given correctly.</span></span>
<span id="cb9-4"><a href="#cb9-4"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb9-5"><a href="#cb9-5"></a> lst_copy <span class="op">=</span> <span class="bu">list</span>.copy(lst)</span>
<span id="cb9-6"><a href="#cb9-6"></a> square_all(lst)</span>
<span id="cb9-7"><a href="#cb9-7"></a></span>
<span id="cb9-8"><a href="#cb9-8"></a> <span class="cf">assert</span> <span class="bu">all</span>({lst[i] <span class="op">==</span> lst_copy[i] <span class="op">**</span> <span class="dv">2</span> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">0</span>, <span class="bu">len</span>(lst))})</span></code></pre></div>
</section>
<footer>
<a href="https://www.teach.cs.toronto.edu/~csc110y/fall/notes/">CSC110 Course Notes Home</a>
</footer>
</body>
</html>