1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777
|
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta http-equiv="X-UA-Compatible" content="IE=EDGE" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Test fixtures</title>
<script>// Pandoc 2.9 adds attributes on both header and div. We remove the former (to
// be compatible with the behavior of Pandoc < 2.8).
document.addEventListener('DOMContentLoaded', function(e) {
var hs = document.querySelectorAll("div.section[class*='level'] > :first-child");
var i, h, a;
for (i = 0; i < hs.length; i++) {
h = hs[i];
if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6
a = h.attributes;
while (a.length > 0) h.removeAttribute(a[0].name);
}
});
</script>
<style type="text/css">
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;}
</style>
<style type="text/css">
code {
white-space: pre;
}
.sourceCode {
overflow: visible;
}
</style>
<style type="text/css" data-origin="pandoc">
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
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 { display: inline-block; 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; }
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; }
code span.at { color: #7d9029; }
code span.bn { color: #40a070; }
code span.bu { color: #008000; }
code span.cf { color: #007020; font-weight: bold; }
code span.ch { color: #4070a0; }
code span.cn { color: #880000; }
code span.co { color: #60a0b0; font-style: italic; }
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; }
code span.do { color: #ba2121; font-style: italic; }
code span.dt { color: #902000; }
code span.dv { color: #40a070; }
code span.er { color: #ff0000; font-weight: bold; }
code span.ex { }
code span.fl { color: #40a070; }
code span.fu { color: #06287e; }
code span.im { color: #008000; font-weight: bold; }
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; }
code span.kw { color: #007020; font-weight: bold; }
code span.op { color: #666666; }
code span.ot { color: #007020; }
code span.pp { color: #bc7a00; }
code span.sc { color: #4070a0; }
code span.ss { color: #bb6688; }
code span.st { color: #4070a0; }
code span.va { color: #19177c; }
code span.vs { color: #4070a0; }
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; }
</style>
<script>
// apply pandoc div.sourceCode style to pre.sourceCode instead
(function() {
var sheets = document.styleSheets;
for (var i = 0; i < sheets.length; i++) {
if (sheets[i].ownerNode.dataset["origin"] !== "pandoc") continue;
try { var rules = sheets[i].cssRules; } catch (e) { continue; }
var j = 0;
while (j < rules.length) {
var rule = rules[j];
// check if there is a div.sourceCode rule
if (rule.type !== rule.STYLE_RULE || rule.selectorText !== "div.sourceCode") {
j++;
continue;
}
var style = rule.style.cssText;
// check if color or background-color is set
if (rule.style.color === '' && rule.style.backgroundColor === '') {
j++;
continue;
}
// replace div.sourceCode by a pre.sourceCode rule
sheets[i].deleteRule(j);
sheets[i].insertRule('pre.sourceCode{' + style + '}', j);
}
}
})();
</script>
<style type="text/css">body {
background-color: #fff;
margin: 1em auto;
max-width: 700px;
overflow: visible;
padding-left: 2em;
padding-right: 2em;
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.35;
}
#TOC {
clear: both;
margin: 0 0 10px 10px;
padding: 4px;
width: 400px;
border: 1px solid #CCCCCC;
border-radius: 5px;
background-color: #f6f6f6;
font-size: 13px;
line-height: 1.3;
}
#TOC .toctitle {
font-weight: bold;
font-size: 15px;
margin-left: 5px;
}
#TOC ul {
padding-left: 40px;
margin-left: -1.5em;
margin-top: 5px;
margin-bottom: 5px;
}
#TOC ul ul {
margin-left: -2em;
}
#TOC li {
line-height: 16px;
}
table {
margin: 1em auto;
border-width: 1px;
border-color: #DDDDDD;
border-style: outset;
border-collapse: collapse;
}
table th {
border-width: 2px;
padding: 5px;
border-style: inset;
}
table td {
border-width: 1px;
border-style: inset;
line-height: 18px;
padding: 5px 5px;
}
table, table th, table td {
border-left-style: none;
border-right-style: none;
}
table thead, table tr.even {
background-color: #f7f7f7;
}
p {
margin: 0.5em 0;
}
blockquote {
background-color: #f6f6f6;
padding: 0.25em 0.75em;
}
hr {
border-style: solid;
border: none;
border-top: 1px solid #777;
margin: 28px 0;
}
dl {
margin-left: 0;
}
dl dd {
margin-bottom: 13px;
margin-left: 13px;
}
dl dt {
font-weight: bold;
}
ul {
margin-top: 0;
}
ul li {
list-style: circle outside;
}
ul ul {
margin-bottom: 0;
}
pre, code {
background-color: #f7f7f7;
border-radius: 3px;
color: #333;
white-space: pre-wrap;
}
pre {
border-radius: 3px;
margin: 5px 0px 10px 0px;
padding: 10px;
}
pre:not([class]) {
background-color: #f7f7f7;
}
code {
font-family: Consolas, Monaco, 'Courier New', monospace;
font-size: 85%;
}
p > code, li > code {
padding: 2px 0px;
}
div.figure {
text-align: center;
}
img {
background-color: #FFFFFF;
padding: 2px;
border: 1px solid #DDDDDD;
border-radius: 3px;
border: 1px solid #CCCCCC;
margin: 0 5px;
}
h1 {
margin-top: 0;
font-size: 35px;
line-height: 40px;
}
h2 {
border-bottom: 4px solid #f7f7f7;
padding-top: 10px;
padding-bottom: 2px;
font-size: 145%;
}
h3 {
border-bottom: 2px solid #f7f7f7;
padding-top: 10px;
font-size: 120%;
}
h4 {
border-bottom: 1px solid #f7f7f7;
margin-left: 8px;
font-size: 105%;
}
h5, h6 {
border-bottom: 1px solid #ccc;
font-size: 105%;
}
a {
color: #0033dd;
text-decoration: none;
}
a:hover {
color: #6666ff; }
a:visited {
color: #800080; }
a:visited:hover {
color: #BB00BB; }
a[href^="http:"] {
text-decoration: underline; }
a[href^="https:"] {
text-decoration: underline; }
code > span.kw { color: #555; font-weight: bold; }
code > span.dt { color: #902000; }
code > span.dv { color: #40a070; }
code > span.bn { color: #d14; }
code > span.fl { color: #d14; }
code > span.ch { color: #d14; }
code > span.st { color: #d14; }
code > span.co { color: #888888; font-style: italic; }
code > span.ot { color: #007020; }
code > span.al { color: #ff0000; font-weight: bold; }
code > span.fu { color: #900; font-weight: bold; }
code > span.er { color: #a61717; background-color: #e3d2d2; }
</style>
</head>
<body>
<h1 class="title toc-ignore">Test fixtures</h1>
<div id="test-hygiene" class="section level2">
<h2>Test hygiene</h2>
<blockquote>
<p>Take nothing but memories, leave nothing but footprints.</p>
<p>― Chief Si’ahl</p>
</blockquote>
<p>Ideally, a test should leave the world exactly as it found it. But
you often need to make some changes in order to exercise every part of
your code:</p>
<ul>
<li>Create a file or directory</li>
<li>Create a resource on an external system</li>
<li>Set an R option</li>
<li>Set an environment variable</li>
<li>Change working directory</li>
<li>Change an aspect of the tested package’s state</li>
</ul>
<p>How can you clean up these changes to get back to a clean slate?
Scrupulous attention to cleanup is more than just courtesy or being
fastidious. It is also self-serving. The state of the world after test
<code>i</code> is the starting state for test <code>i + 1</code>. Tests
that change state willy-nilly eventually end up interfering with each
other in ways that can be very difficult to debug.</p>
<p>Most tests are written with an implicit assumption about the starting
state, usually whatever <em>tabula rasa</em> means for the target domain
of your package. If you accumulate enough sloppy tests, you will
eventually find yourself asking the programming equivalent of questions
like “Who forgot to turn off the oven?” and “Who didn’t clean up after
the dog?”.</p>
<p>It’s also important that your setup and cleanup is easy to use when
working interactively. When a test fails, you want to be able to quickly
recreate the exact environment in which the test is run so you can
interactively experiment to figure out what went wrong.</p>
<p>This article introduces a powerful technique that allows you to solve
both problems: <strong>test fixtures</strong>. We’ll begin with an
introduction to the tools that make fixtures possible, then talk about
exactly what a test fixture is, and show a few examples.</p>
<p>Much of this vignette is derived from <a href="https://www.tidyverse.org/blog/2020/04/self-cleaning-test-fixtures/" class="uri">https://www.tidyverse.org/blog/2020/04/self-cleaning-test-fixtures/</a>;
if this is your first encounter with <code>on.exit()</code> or
<code>withr::defer()</code>, I’d recommend starting with that blog as it
gives a gentler introduction. This vignette moves a little faster since
it’s designed as more of a reference doc.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb1-1"><a href="#cb1-1" tabindex="-1"></a><span class="fu">library</span>(testthat)</span></code></pre></div>
</div>
<div id="foundations" class="section level2">
<h2>Foundations</h2>
<p>Before we can talk about test fixtures, we need to lay some
foundations to help you understand how they work. We’ll motivate the
discussion with a <code>sloppy()</code> function that prints a number
with a specific number of significant digits by adjusting an R
option:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb2-1"><a href="#cb2-1" tabindex="-1"></a>sloppy <span class="ot"><-</span> <span class="cf">function</span>(x, sig_digits) {</span>
<span id="cb2-2"><a href="#cb2-2" tabindex="-1"></a> <span class="fu">options</span>(<span class="at">digits =</span> sig_digits)</span>
<span id="cb2-3"><a href="#cb2-3" tabindex="-1"></a> <span class="fu">print</span>(x)</span>
<span id="cb2-4"><a href="#cb2-4" tabindex="-1"></a>}</span>
<span id="cb2-5"><a href="#cb2-5" tabindex="-1"></a></span>
<span id="cb2-6"><a href="#cb2-6" tabindex="-1"></a>pi</span>
<span id="cb2-7"><a href="#cb2-7" tabindex="-1"></a><span class="co">#> [1] 3.141593</span></span>
<span id="cb2-8"><a href="#cb2-8" tabindex="-1"></a><span class="fu">sloppy</span>(pi, <span class="dv">2</span>)</span>
<span id="cb2-9"><a href="#cb2-9" tabindex="-1"></a><span class="co">#> [1] 3.1</span></span>
<span id="cb2-10"><a href="#cb2-10" tabindex="-1"></a>pi</span>
<span id="cb2-11"><a href="#cb2-11" tabindex="-1"></a><span class="co">#> [1] 3.1</span></span></code></pre></div>
<p>Notice how <code>pi</code> prints differently before and after the
call to <code>sloppy()</code>. Calling <code>sloppy()</code> has a side
effect: it changes the <code>digits</code> option globally, not just
within its own scope of operations. This is what we want to avoid<a href="#fn1" class="footnote-ref" id="fnref1"><sup>1</sup></a>.</p>
<div id="on.exit" class="section level3">
<h3><code>on.exit()</code></h3>
<p>The first function you need to know about is base R’s
<code>on.exit()</code>. <code>on.exit()</code> calls the code to
supplied to its first argument when the current function exits,
regardless of whether it returns a value or errors. You can use
<code>on.exit()</code> to clean up after yourself by ensuring that every
mess-making function call is paired with an <code>on.exit()</code> call
that cleans up.</p>
<p>We can use this idea to turn <code>sloppy()</code> into
<code>neat()</code>:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb3-1"><a href="#cb3-1" tabindex="-1"></a>neat <span class="ot"><-</span> <span class="cf">function</span>(x, sig_digits) {</span>
<span id="cb3-2"><a href="#cb3-2" tabindex="-1"></a> op <span class="ot"><-</span> <span class="fu">options</span>(<span class="at">digits =</span> sig_digits)</span>
<span id="cb3-3"><a href="#cb3-3" tabindex="-1"></a> <span class="fu">on.exit</span>(<span class="fu">options</span>(op), <span class="at">add =</span> <span class="cn">TRUE</span>, <span class="at">after =</span> <span class="cn">FALSE</span>)</span>
<span id="cb3-4"><a href="#cb3-4" tabindex="-1"></a> <span class="fu">print</span>(x)</span>
<span id="cb3-5"><a href="#cb3-5" tabindex="-1"></a>}</span>
<span id="cb3-6"><a href="#cb3-6" tabindex="-1"></a></span>
<span id="cb3-7"><a href="#cb3-7" tabindex="-1"></a>pi</span>
<span id="cb3-8"><a href="#cb3-8" tabindex="-1"></a><span class="co">#> [1] 3.141593</span></span>
<span id="cb3-9"><a href="#cb3-9" tabindex="-1"></a><span class="fu">neat</span>(pi, <span class="dv">2</span>)</span>
<span id="cb3-10"><a href="#cb3-10" tabindex="-1"></a><span class="co">#> [1] 3.1</span></span>
<span id="cb3-11"><a href="#cb3-11" tabindex="-1"></a>pi</span>
<span id="cb3-12"><a href="#cb3-12" tabindex="-1"></a><span class="co">#> [1] 3.141593</span></span></code></pre></div>
<p>Here we make use of a useful pattern <code>options()</code>
implements: when you call <code>options(digits = sig_digits)</code> it
both sets the <code>digits</code> option <em>and</em> (invisibly)
returns the previous value of digits. We can then use that value to
restore the previous options.</p>
<p><code>on.exit()</code> also works in tests:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb4-1"><a href="#cb4-1" tabindex="-1"></a><span class="fu">test_that</span>(<span class="st">"can print one digit of pi"</span>, {</span>
<span id="cb4-2"><a href="#cb4-2" tabindex="-1"></a> op <span class="ot"><-</span> <span class="fu">options</span>(<span class="at">digits =</span> <span class="dv">1</span>)</span>
<span id="cb4-3"><a href="#cb4-3" tabindex="-1"></a> <span class="fu">on.exit</span>(<span class="fu">options</span>(op), <span class="at">add =</span> <span class="cn">TRUE</span>, <span class="at">after =</span> <span class="cn">FALSE</span>)</span>
<span id="cb4-4"><a href="#cb4-4" tabindex="-1"></a> </span>
<span id="cb4-5"><a href="#cb4-5" tabindex="-1"></a> <span class="fu">expect_output</span>(<span class="fu">print</span>(pi), <span class="st">"3"</span>)</span>
<span id="cb4-6"><a href="#cb4-6" tabindex="-1"></a>})</span>
<span id="cb4-7"><a href="#cb4-7" tabindex="-1"></a><span class="co">#> Test passed 🌈</span></span>
<span id="cb4-8"><a href="#cb4-8" tabindex="-1"></a>pi</span>
<span id="cb4-9"><a href="#cb4-9" tabindex="-1"></a><span class="co">#> [1] 3.141593</span></span></code></pre></div>
<p>There are three main drawbacks to <code>on.exit()</code>:</p>
<ul>
<li><p>You should always call it with <code>add = TRUE</code> and
<code>after = FALSE</code>. These ensure that the call is
<strong>added</strong> to the list of deferred tasks (instead of
replaces) and is added to the <strong>front</strong> of the stack (not
the back, so that cleanup occurs in reverse order to setup). These
arguments only matter if you’re using multiple <code>on.exit()</code>
calls, but it’s a good habit to always use them to avoid potential
problems down the road.</p></li>
<li><p>It doesn’t work outside a function or test. If you run the
following code in the global environment, you won’t get an error, but
the cleanup code will never be run:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb5-1"><a href="#cb5-1" tabindex="-1"></a>op <span class="ot"><-</span> <span class="fu">options</span>(<span class="at">digits =</span> <span class="dv">1</span>)</span>
<span id="cb5-2"><a href="#cb5-2" tabindex="-1"></a><span class="fu">on.exit</span>(<span class="fu">options</span>(op), <span class="at">add =</span> <span class="cn">TRUE</span>, <span class="at">after =</span> <span class="cn">FALSE</span>)</span></code></pre></div>
<p>This is annoying when you are running tests interactively.</p></li>
<li><p>You can’t program with it; <code>on.exit()</code> always works
inside the <em>current</em> function so you can’t wrap up repeated
<code>on.exit()</code> code in a helper function.</p></li>
</ul>
<p>To resolve these drawbacks, we use <code>withr::defer()</code>.</p>
</div>
<div id="withrdefer" class="section level3">
<h3><code>withr::defer()</code></h3>
<p><code>withr::defer()</code> resolves the main drawbacks of
<code>on.exit()</code>. First, it has the behaviour we want by default;
no extra arguments needed:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb6-1"><a href="#cb6-1" tabindex="-1"></a>neat <span class="ot"><-</span> <span class="cf">function</span>(x, sig_digits) {</span>
<span id="cb6-2"><a href="#cb6-2" tabindex="-1"></a> op <span class="ot"><-</span> <span class="fu">options</span>(<span class="at">digits =</span> sig_digits)</span>
<span id="cb6-3"><a href="#cb6-3" tabindex="-1"></a> withr<span class="sc">::</span><span class="fu">defer</span>(<span class="fu">options</span>(op))</span>
<span id="cb6-4"><a href="#cb6-4" tabindex="-1"></a> <span class="fu">print</span>(x)</span>
<span id="cb6-5"><a href="#cb6-5" tabindex="-1"></a>}</span></code></pre></div>
<p>Second, it works when called in the global environment. Since the
global environment isn’t perishable, like a test environment is, you
have to call <code>deferred_run()</code> explicitly to execute the
deferred events. You can also clear them, without running, with
<code>deferred_clear()</code>.</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb7-1"><a href="#cb7-1" tabindex="-1"></a>withr<span class="sc">::</span><span class="fu">defer</span>(<span class="fu">print</span>(<span class="st">"hi"</span>))</span>
<span id="cb7-2"><a href="#cb7-2" tabindex="-1"></a><span class="co">#> Setting deferred event(s) on global environment.</span></span>
<span id="cb7-3"><a href="#cb7-3" tabindex="-1"></a><span class="co">#> * Execute (and clear) with `deferred_run()`.</span></span>
<span id="cb7-4"><a href="#cb7-4" tabindex="-1"></a><span class="co">#> * Clear (without executing) with `deferred_clear()`.</span></span>
<span id="cb7-5"><a href="#cb7-5" tabindex="-1"></a></span>
<span id="cb7-6"><a href="#cb7-6" tabindex="-1"></a>withr<span class="sc">::</span><span class="fu">deferred_run</span>()</span>
<span id="cb7-7"><a href="#cb7-7" tabindex="-1"></a><span class="co">#> [1] "hi"</span></span></code></pre></div>
<p>Finally, <code>withr::defer()</code> lets you pick which function to
bind the clean up behaviour too. This makes it possible to create helper
functions.</p>
</div>
<div id="local-helpers" class="section level3">
<h3>“Local” helpers</h3>
<p>Imagine we have many functions where we want to temporarily set the
digits option. Wouldn’t it be nice if we could write a helper function
to automate? Unfortunately we can’t write a helper with
<code>on.exit()</code>:</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb8-1"><a href="#cb8-1" tabindex="-1"></a>local_digits <span class="ot"><-</span> <span class="cf">function</span>(sig_digits) {</span>
<span id="cb8-2"><a href="#cb8-2" tabindex="-1"></a> op <span class="ot"><-</span> <span class="fu">options</span>(<span class="at">digits =</span> sig_digits)</span>
<span id="cb8-3"><a href="#cb8-3" tabindex="-1"></a> <span class="fu">on.exit</span>(<span class="fu">options</span>(op), <span class="at">add =</span> <span class="cn">TRUE</span>, <span class="at">after =</span> <span class="cn">FALSE</span>)</span>
<span id="cb8-4"><a href="#cb8-4" tabindex="-1"></a>}</span>
<span id="cb8-5"><a href="#cb8-5" tabindex="-1"></a>neater <span class="ot"><-</span> <span class="cf">function</span>(x, sig_digits) {</span>
<span id="cb8-6"><a href="#cb8-6" tabindex="-1"></a> <span class="fu">local_digits</span>(<span class="dv">1</span>)</span>
<span id="cb8-7"><a href="#cb8-7" tabindex="-1"></a> <span class="fu">print</span>(x)</span>
<span id="cb8-8"><a href="#cb8-8" tabindex="-1"></a>}</span>
<span id="cb8-9"><a href="#cb8-9" tabindex="-1"></a><span class="fu">neater</span>(pi)</span>
<span id="cb8-10"><a href="#cb8-10" tabindex="-1"></a><span class="co">#> [1] 3.141593</span></span></code></pre></div>
<p>This code doesn’t work because the cleanup happens too soon, when
<code>local_digits()</code> exits, not when <code>neat()</code>
finishes.</p>
<p>Fortunately, <code>withr::defer()</code> allows us to solve this
problem by providing an <code>envir</code> argument that allows you to
control when cleanup occurs. The exact details of how this works are
rather complicated, but fortunately there’s a common pattern you can use
without understanding all the details. Your helper function should
always have an <code>env</code> argument that defaults to
<code>parent.frame()</code>, which you pass to the second argument of
<code>defer()</code>:</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb9-1"><a href="#cb9-1" tabindex="-1"></a>local_digits <span class="ot"><-</span> <span class="cf">function</span>(sig_digits, <span class="at">env =</span> <span class="fu">parent.frame</span>()) {</span>
<span id="cb9-2"><a href="#cb9-2" tabindex="-1"></a> op <span class="ot"><-</span> <span class="fu">options</span>(<span class="at">digits =</span> sig_digits)</span>
<span id="cb9-3"><a href="#cb9-3" tabindex="-1"></a> withr<span class="sc">::</span><span class="fu">defer</span>(<span class="fu">options</span>(op), env)</span>
<span id="cb9-4"><a href="#cb9-4" tabindex="-1"></a>}</span>
<span id="cb9-5"><a href="#cb9-5" tabindex="-1"></a></span>
<span id="cb9-6"><a href="#cb9-6" tabindex="-1"></a><span class="fu">neater</span>(pi)</span>
<span id="cb9-7"><a href="#cb9-7" tabindex="-1"></a><span class="co">#> [1] 3</span></span></code></pre></div>
<p>Just like <code>on.exit()</code> and <code>defer()</code>, our helper
also works within tests:</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb10-1"><a href="#cb10-1" tabindex="-1"></a><span class="fu">test_that</span>(<span class="st">"withr lets us write custom helpers for local state manipulation"</span>, {</span>
<span id="cb10-2"><a href="#cb10-2" tabindex="-1"></a> <span class="fu">local_digits</span>(<span class="dv">1</span>)</span>
<span id="cb10-3"><a href="#cb10-3" tabindex="-1"></a> <span class="fu">expect_output</span>(<span class="fu">print</span>(<span class="fu">exp</span>(<span class="dv">1</span>)), <span class="st">"3"</span>)</span>
<span id="cb10-4"><a href="#cb10-4" tabindex="-1"></a> </span>
<span id="cb10-5"><a href="#cb10-5" tabindex="-1"></a> <span class="fu">local_digits</span>(<span class="dv">3</span>)</span>
<span id="cb10-6"><a href="#cb10-6" tabindex="-1"></a> <span class="fu">expect_output</span>(<span class="fu">print</span>(<span class="fu">exp</span>(<span class="dv">1</span>)), <span class="st">"2.72"</span>)</span>
<span id="cb10-7"><a href="#cb10-7" tabindex="-1"></a>})</span>
<span id="cb10-8"><a href="#cb10-8" tabindex="-1"></a><span class="co">#> Test passed 🎊</span></span>
<span id="cb10-9"><a href="#cb10-9" tabindex="-1"></a></span>
<span id="cb10-10"><a href="#cb10-10" tabindex="-1"></a><span class="fu">print</span>(<span class="fu">exp</span>(<span class="dv">1</span>))</span>
<span id="cb10-11"><a href="#cb10-11" tabindex="-1"></a><span class="co">#> [1] 2.718282</span></span></code></pre></div>
<p>We always call these helper functions <code>local_</code>; “local”
here refers to the fact that the state change persists only locally, for
the lifetime of the associated function or test.</p>
</div>
<div id="pre-existing-helpers" class="section level3">
<h3>Pre-existing helpers</h3>
<p>But before you write your own helper function, make sure to check out
the wide range of local functions already provided by withr:</p>
<table>
<thead>
<tr class="header">
<th>Do / undo this</th>
<th>withr function</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Create a file</td>
<td><code>local_file()</code></td>
</tr>
<tr class="even">
<td>Set an R option</td>
<td><code>local_options()</code></td>
</tr>
<tr class="odd">
<td>Set an environment variable</td>
<td><code>local_envvar()</code></td>
</tr>
<tr class="even">
<td>Change working directory</td>
<td><code>local_dir()</code></td>
</tr>
</tbody>
</table>
<p>We can use <code>withr::local_options()</code> to write yet another
version of <code>neater()</code>:</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb11-1"><a href="#cb11-1" tabindex="-1"></a>neatest <span class="ot"><-</span> <span class="cf">function</span>(x, sig_digits) {</span>
<span id="cb11-2"><a href="#cb11-2" tabindex="-1"></a> withr<span class="sc">::</span><span class="fu">local_options</span>(<span class="fu">list</span>(<span class="at">digits =</span> sig_digits))</span>
<span id="cb11-3"><a href="#cb11-3" tabindex="-1"></a> <span class="fu">print</span>(x)</span>
<span id="cb11-4"><a href="#cb11-4" tabindex="-1"></a>}</span>
<span id="cb11-5"><a href="#cb11-5" tabindex="-1"></a><span class="fu">neatest</span>(pi, <span class="dv">3</span>)</span>
<span id="cb11-6"><a href="#cb11-6" tabindex="-1"></a><span class="co">#> [1] 3.14</span></span></code></pre></div>
<p>Each <code>local_*()</code> function has a companion
<code>with_()</code> function, which is a nod to <code>with()</code>,
and the inspiration for withr’s name. We won’t use the
<code>with_*()</code> functions here, but you can learn more about them
at <a href="https://withr.r-lib.org">withr.r-lib.org</a>.</p>
</div>
</div>
<div id="test-fixtures" class="section level2">
<h2>Test fixtures</h2>
<p>Testing is often demonstrated with cute little tests and functions
where all the inputs and expected results can be inlined. But in real
packages, things aren’t always so simple and functions often depend on
other global state. For example, take this variant on
<code>message()</code> that only shows a message if the
<code>verbose</code> option is <code>TRUE</code>. How would you test
that setting the option does indeed silence the message?</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb12-1"><a href="#cb12-1" tabindex="-1"></a>message2 <span class="ot"><-</span> <span class="cf">function</span>(...) {</span>
<span id="cb12-2"><a href="#cb12-2" tabindex="-1"></a> <span class="cf">if</span> (<span class="sc">!</span><span class="fu">isTRUE</span>(<span class="fu">getOption</span>(<span class="st">"verbose"</span>))) {</span>
<span id="cb12-3"><a href="#cb12-3" tabindex="-1"></a> <span class="fu">return</span>()</span>
<span id="cb12-4"><a href="#cb12-4" tabindex="-1"></a> }</span>
<span id="cb12-5"><a href="#cb12-5" tabindex="-1"></a> <span class="fu">message</span>(...)</span>
<span id="cb12-6"><a href="#cb12-6" tabindex="-1"></a>}</span></code></pre></div>
<p>In some cases, it’s possible to make the global state an explicit
argument to the function. For example, we could refactor
<code>message2()</code> to make the verbosity an explicit argument:</p>
<div class="sourceCode" id="cb13"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb13-1"><a href="#cb13-1" tabindex="-1"></a>message3 <span class="ot"><-</span> <span class="cf">function</span>(..., <span class="at">verbose =</span> <span class="fu">getOption</span>(<span class="st">"verbose"</span>)) {</span>
<span id="cb13-2"><a href="#cb13-2" tabindex="-1"></a> <span class="cf">if</span> (<span class="sc">!</span><span class="fu">isTRUE</span>(verbose)) {</span>
<span id="cb13-3"><a href="#cb13-3" tabindex="-1"></a> <span class="fu">return</span>()</span>
<span id="cb13-4"><a href="#cb13-4" tabindex="-1"></a> }</span>
<span id="cb13-5"><a href="#cb13-5" tabindex="-1"></a> <span class="fu">message</span>(...)</span>
<span id="cb13-6"><a href="#cb13-6" tabindex="-1"></a>}</span></code></pre></div>
<p>Making external state explicit is often worthwhile, because it makes
it more clear exactly what inputs determine the outputs of your
function. But it’s simply not possible in many cases. That’s where test
fixtures come in: they allow you to temporarily change global state in
order to test your function. Test fixture is a pre-existing term in the
software engineering world (and beyond):</p>
<blockquote>
<p>A test fixture is something used to consistently test some item,
device, or piece of software.</p>
<p>— <a href="https://en.wikipedia.org/wiki/Test_fixture">Wikipedia</a></p>
</blockquote>
<p>A <strong>test fixture</strong> is just a <code>local_</code>
function that you use to change state in such a way that you can reach
inside and test parts of your code that would otherwise be challenging.
For example, here’s how you could use
<code>withr::local_options()</code> as a test fixture to test
<code>message2()</code>:</p>
<div class="sourceCode" id="cb14"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb14-1"><a href="#cb14-1" tabindex="-1"></a><span class="fu">test_that</span>(<span class="st">"message2() output depends on verbose option"</span>, {</span>
<span id="cb14-2"><a href="#cb14-2" tabindex="-1"></a> withr<span class="sc">::</span><span class="fu">local_options</span>(<span class="at">verbose =</span> <span class="cn">TRUE</span>)</span>
<span id="cb14-3"><a href="#cb14-3" tabindex="-1"></a> <span class="fu">expect_message</span>(<span class="fu">message2</span>(<span class="st">"Hi!"</span>))</span>
<span id="cb14-4"><a href="#cb14-4" tabindex="-1"></a> </span>
<span id="cb14-5"><a href="#cb14-5" tabindex="-1"></a> withr<span class="sc">::</span><span class="fu">local_options</span>(<span class="at">verbose =</span> <span class="cn">FALSE</span>)</span>
<span id="cb14-6"><a href="#cb14-6" tabindex="-1"></a> <span class="fu">expect_message</span>(<span class="fu">message2</span>(<span class="st">"Hi!"</span>), <span class="cn">NA</span>)</span>
<span id="cb14-7"><a href="#cb14-7" tabindex="-1"></a>})</span>
<span id="cb14-8"><a href="#cb14-8" tabindex="-1"></a><span class="co">#> Test passed 🥇</span></span></code></pre></div>
<div id="case-study-usethis" class="section level3">
<h3>Case study: usethis</h3>
<p>One place that we use test fixtures extensively is in the usethis
package (<a href="https://usethis.r-lib.org">usethis.r-lib.org</a>),
which provides functions for looking after the files and folders in R
projects, especially packages. Many of these functions only make sense
in the context of a package, which means to test them, we also have to
be working inside an R package. We need a way to quickly spin up a
minimal package in a temporary directory, then test some functions
against it, then destroy it.</p>
<p>To solve this problem we create a test fixture, which we place in
<code>R/test-helpers.R</code> so that’s it’s available for both testing
and interactive experimentation:</p>
<div class="sourceCode" id="cb15"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb15-1"><a href="#cb15-1" tabindex="-1"></a>local_create_package <span class="ot"><-</span> <span class="cf">function</span>(<span class="at">dir =</span> <span class="fu">file_temp</span>(), <span class="at">env =</span> <span class="fu">parent.frame</span>()) {</span>
<span id="cb15-2"><a href="#cb15-2" tabindex="-1"></a> old_project <span class="ot"><-</span> <span class="fu">proj_get_</span>()</span>
<span id="cb15-3"><a href="#cb15-3" tabindex="-1"></a> </span>
<span id="cb15-4"><a href="#cb15-4" tabindex="-1"></a> <span class="co"># create new folder and package</span></span>
<span id="cb15-5"><a href="#cb15-5" tabindex="-1"></a> <span class="fu">create_package</span>(dir, <span class="at">open =</span> <span class="cn">FALSE</span>) <span class="co"># A</span></span>
<span id="cb15-6"><a href="#cb15-6" tabindex="-1"></a> withr<span class="sc">::</span><span class="fu">defer</span>(fs<span class="sc">::</span><span class="fu">dir_delete</span>(dir), <span class="at">envir =</span> env) <span class="co"># -A</span></span>
<span id="cb15-7"><a href="#cb15-7" tabindex="-1"></a> </span>
<span id="cb15-8"><a href="#cb15-8" tabindex="-1"></a> <span class="co"># change working directory</span></span>
<span id="cb15-9"><a href="#cb15-9" tabindex="-1"></a> <span class="fu">setwd</span>(dir) <span class="co"># B</span></span>
<span id="cb15-10"><a href="#cb15-10" tabindex="-1"></a> withr<span class="sc">::</span><span class="fu">defer</span>(<span class="fu">setwd</span>(old_project), <span class="at">envir =</span> env) <span class="co"># -B</span></span>
<span id="cb15-11"><a href="#cb15-11" tabindex="-1"></a> </span>
<span id="cb15-12"><a href="#cb15-12" tabindex="-1"></a> <span class="co"># switch to new usethis project</span></span>
<span id="cb15-13"><a href="#cb15-13" tabindex="-1"></a> <span class="fu">proj_set</span>(dir) <span class="co"># C</span></span>
<span id="cb15-14"><a href="#cb15-14" tabindex="-1"></a> withr<span class="sc">::</span><span class="fu">defer</span>(<span class="fu">proj_set</span>(old_project, <span class="at">force =</span> <span class="cn">TRUE</span>), <span class="at">envir =</span> env) <span class="co"># -C</span></span>
<span id="cb15-15"><a href="#cb15-15" tabindex="-1"></a> </span>
<span id="cb15-16"><a href="#cb15-16" tabindex="-1"></a> dir</span>
<span id="cb15-17"><a href="#cb15-17" tabindex="-1"></a>}</span></code></pre></div>
<p>Note that the cleanup automatically unfolds in the opposite order
from the setup. Setup is <code>A</code>, then <code>B</code>, then
<code>C</code>; cleanup is <code>-C</code>, then <code>-B</code>, then
<code>-A</code>. This is important because we must create directory
<code>dir</code> before we can make it the working directory; and we
must restore the original working directory before we can delete
<code>dir</code>; we can’t delete <code>dir</code> while it’s still the
working directory!</p>
<p><code>local_create_package()</code> is used in over 170 tests. Here’s
one example that checks that <code>usethis::use_roxygen_md()</code> does
the setup necessary to use roxygen2 in a package, with markdown support
turned on. All 3 expectations consult the DESCRIPTION file, directly or
indirectly. So it’s very convenient that
<code>local_create_package()</code> creates a minimal package, with a
valid <code>DESCRIPTION</code> file, for us to test against. And when
the test is done — poof! — the package is gone.</p>
<div class="sourceCode" id="cb16"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb16-1"><a href="#cb16-1" tabindex="-1"></a><span class="fu">test_that</span>(<span class="st">"use_roxygen_md() adds DESCRIPTION fields"</span>, {</span>
<span id="cb16-2"><a href="#cb16-2" tabindex="-1"></a> pkg <span class="ot"><-</span> <span class="fu">local_create_package</span>()</span>
<span id="cb16-3"><a href="#cb16-3" tabindex="-1"></a> <span class="fu">use_roxygen_md</span>()</span>
<span id="cb16-4"><a href="#cb16-4" tabindex="-1"></a> </span>
<span id="cb16-5"><a href="#cb16-5" tabindex="-1"></a> <span class="fu">expect_true</span>(<span class="fu">uses_roxygen_md</span>())</span>
<span id="cb16-6"><a href="#cb16-6" tabindex="-1"></a> <span class="fu">expect_equal</span>(desc<span class="sc">::</span><span class="fu">desc_get</span>(<span class="st">"Roxygen"</span>, pkg)[[<span class="dv">1</span>]], <span class="st">"list(markdown = TRUE)"</span>)<span class="er">)</span></span>
<span id="cb16-7"><a href="#cb16-7" tabindex="-1"></a> <span class="fu">expect_true</span>(desc<span class="sc">::</span><span class="fu">desc_has_fields</span>(<span class="st">"RoxygenNote"</span>, pkg))</span>
<span id="cb16-8"><a href="#cb16-8" tabindex="-1"></a>})</span></code></pre></div>
</div>
</div>
<div id="scope" class="section level2">
<h2>Scope</h2>
<p>So far we have applied our test fixture to individual tests, but it’s
also possible to apply them to a file or package.</p>
<div id="file" class="section level3">
<h3>File</h3>
<p>If you move the <code>local_()</code> call outside of a
<code>test_that()</code> block, it will affect all tests that come after
it. This means that by calling the test fixture at the top of the file
you can change the behaviour for all tests. This has both advantages and
disadvantages:</p>
<ul>
<li><p>If you would otherwise have called the fixture in every test,
you’ve saved yourself a bunch of work and duplicate code.</p></li>
<li><p>On the downside, if your test fails and you want to recreate the
failure in an interactive environment so you can debug, you need to
remember to run all the setup code at the top of the file
first.</p></li>
</ul>
<p>Generally, I think it’s better to copy and paste test fixtures across
many tests — sure, it adds some duplication to your code, but it makes
debugging test failures so much easier.</p>
</div>
<div id="package" class="section level3">
<h3>Package</h3>
<p>To run code before any test is run, you can create a file called
<code>tests/testthat/setup.R</code>. If the code in this file needs
clean up, you can use the special <code>teardown_env()</code>:</p>
<div class="sourceCode" id="cb17"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb17-1"><a href="#cb17-1" tabindex="-1"></a><span class="co"># Run before any test</span></span>
<span id="cb17-2"><a href="#cb17-2" tabindex="-1"></a><span class="fu">write.csv</span>(<span class="st">"mtcars.csv"</span>, mtcars)</span>
<span id="cb17-3"><a href="#cb17-3" tabindex="-1"></a></span>
<span id="cb17-4"><a href="#cb17-4" tabindex="-1"></a><span class="co"># Run after all tests</span></span>
<span id="cb17-5"><a href="#cb17-5" tabindex="-1"></a>withr<span class="sc">::</span><span class="fu">defer</span>(<span class="fu">unlink</span>(<span class="st">"mtcars.csv"</span>), <span class="fu">teardown_env</span>())</span></code></pre></div>
<p>Setup code is typically best used to create external resources that
are needed by many tests. It’s best kept to a minimum because you will
have to manually run it before interactively debugging tests.</p>
</div>
</div>
<div id="other-challenges" class="section level2">
<h2>Other challenges</h2>
<p>A collection of miscellaneous problems that I don’t know where else
to describe:</p>
<ul>
<li><p>There are a few base functions that are hard to test because they
depend on state that you can’t control. One such example is
<code>interactive()</code>: there’s no way to write a test fixture that
allows you to pretend that interactive is either <code>TRUE</code> or
<code>FALSE</code>. So we now usually use
<code>rlang::is_interactive()</code> which can be controlled by the
<code>rlang_interactive</code> option.</p></li>
<li><p>If you’re using a test fixture in a function, be careful about
what you return. For example, if you write a function that does
<code>dir <- create_local_package()</code> you shouldn’t return
<code>dir</code>, because after the function returns the directory will
no longer exist.</p></li>
</ul>
</div>
<div class="footnotes footnotes-end-of-document">
<hr />
<ol>
<li id="fn1"><p>Don’t worry, I’m restoring global state (specifically,
the <code>digits</code> option) behind the scenes here.<a href="#fnref1" class="footnote-back">↩︎</a></p></li>
</ol>
</div>
<!-- code folding -->
<!-- dynamically load mathjax for compatibility with self-contained -->
<script>
(function () {
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML";
document.getElementsByTagName("head")[0].appendChild(script);
})();
</script>
</body>
</html>
|