|
|
<!DOCTYPE HTML>
|
|
|
<html lang="en" class="light sidebar-visible" dir="ltr">
|
|
|
<head>
|
|
|
<!-- Book generated using mdBook -->
|
|
|
<meta charset="UTF-8">
|
|
|
<title>如何编写测试 - Rust 程序设计语言 简体中文版</title>
|
|
|
|
|
|
|
|
|
<!-- Custom HTML head -->
|
|
|
|
|
|
<meta name="description" content="">
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
|
<meta name="theme-color" content="#ffffff">
|
|
|
|
|
|
<link rel="icon" href="favicon-de23e50b.svg">
|
|
|
<link rel="shortcut icon" href="favicon-8114d1fc.png">
|
|
|
<link rel="stylesheet" href="css/variables-8adf115d.css">
|
|
|
<link rel="stylesheet" href="css/general-2459343d.css">
|
|
|
<link rel="stylesheet" href="css/chrome-ae938929.css">
|
|
|
<link rel="stylesheet" href="css/print-9e4910d8.css" media="print">
|
|
|
|
|
|
<!-- Fonts -->
|
|
|
<link rel="stylesheet" href="fonts/fonts-9644e21d.css">
|
|
|
|
|
|
<!-- Highlight.js Stylesheets -->
|
|
|
<link rel="stylesheet" id="mdbook-highlight-css" href="highlight-493f70e1.css">
|
|
|
<link rel="stylesheet" id="mdbook-tomorrow-night-css" href="tomorrow-night-4c0ae647.css">
|
|
|
<link rel="stylesheet" id="mdbook-ayu-highlight-css" href="ayu-highlight-3fdfc3ac.css">
|
|
|
|
|
|
<!-- Custom theme stylesheets -->
|
|
|
<link rel="stylesheet" href="ferris-d33b75bf.css">
|
|
|
<link rel="stylesheet" href="theme/2018-edition-4e126c62.css">
|
|
|
<link rel="stylesheet" href="theme/semantic-notes-9b5766c0.css">
|
|
|
<link rel="stylesheet" href="theme/listing-cab26221.css">
|
|
|
|
|
|
|
|
|
<!-- Provide site root and default themes to javascript -->
|
|
|
<script>
|
|
|
const path_to_root = "";
|
|
|
const default_light_theme = "light";
|
|
|
const default_dark_theme = "navy";
|
|
|
window.path_to_searchindex_js = "searchindex-7d935aa5.js";
|
|
|
</script>
|
|
|
<!-- Start loading toc.js asap -->
|
|
|
<script src="toc-9d5d6d0e.js"></script>
|
|
|
</head>
|
|
|
<body>
|
|
|
<div id="mdbook-help-container">
|
|
|
<div id="mdbook-help-popup">
|
|
|
<h2 class="mdbook-help-title">Keyboard shortcuts</h2>
|
|
|
<div>
|
|
|
<p>Press <kbd>←</kbd> or <kbd>→</kbd> to navigate between chapters</p>
|
|
|
<p>Press <kbd>S</kbd> or <kbd>/</kbd> to search in the book</p>
|
|
|
<p>Press <kbd>?</kbd> to show this help</p>
|
|
|
<p>Press <kbd>Esc</kbd> to hide this help</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div id="mdbook-body-container">
|
|
|
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
|
|
<script>
|
|
|
try {
|
|
|
let theme = localStorage.getItem('mdbook-theme');
|
|
|
let sidebar = localStorage.getItem('mdbook-sidebar');
|
|
|
|
|
|
if (theme.startsWith('"') && theme.endsWith('"')) {
|
|
|
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
|
|
}
|
|
|
|
|
|
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
|
|
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
|
|
}
|
|
|
} catch (e) { }
|
|
|
</script>
|
|
|
|
|
|
<!-- Set the theme before any content is loaded, prevents flash -->
|
|
|
<script>
|
|
|
const default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? default_dark_theme : default_light_theme;
|
|
|
let theme;
|
|
|
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
|
|
if (theme === null || theme === undefined) { theme = default_theme; }
|
|
|
const html = document.documentElement;
|
|
|
html.classList.remove('light')
|
|
|
html.classList.add(theme);
|
|
|
html.classList.add("js");
|
|
|
</script>
|
|
|
|
|
|
<input type="checkbox" id="mdbook-sidebar-toggle-anchor" class="hidden">
|
|
|
|
|
|
<!-- Hide / unhide sidebar before it is displayed -->
|
|
|
<script>
|
|
|
let sidebar = null;
|
|
|
const sidebar_toggle = document.getElementById("mdbook-sidebar-toggle-anchor");
|
|
|
if (document.body.clientWidth >= 1080) {
|
|
|
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
|
|
sidebar = sidebar || 'visible';
|
|
|
} else {
|
|
|
sidebar = 'hidden';
|
|
|
sidebar_toggle.checked = false;
|
|
|
}
|
|
|
if (sidebar === 'visible') {
|
|
|
sidebar_toggle.checked = true;
|
|
|
} else {
|
|
|
html.classList.remove('sidebar-visible');
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<nav id="mdbook-sidebar" class="sidebar" aria-label="Table of contents">
|
|
|
<!-- populated by js -->
|
|
|
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
|
|
|
<noscript>
|
|
|
<iframe class="sidebar-iframe-outer" src="toc.html"></iframe>
|
|
|
</noscript>
|
|
|
<div id="mdbook-sidebar-resize-handle" class="sidebar-resize-handle">
|
|
|
<div class="sidebar-resize-indicator"></div>
|
|
|
</div>
|
|
|
</nav>
|
|
|
|
|
|
<div id="mdbook-page-wrapper" class="page-wrapper">
|
|
|
|
|
|
<div class="page">
|
|
|
<div id="mdbook-menu-bar-hover-placeholder"></div>
|
|
|
<div id="mdbook-menu-bar" class="menu-bar sticky">
|
|
|
<div class="left-buttons">
|
|
|
<label id="mdbook-sidebar-toggle" class="icon-button" for="mdbook-sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="mdbook-sidebar">
|
|
|
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M0 96C0 78.3 14.3 64 32 64H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H416c17.7 0 32 14.3 32 32z"/></svg></span>
|
|
|
</label>
|
|
|
<button id="mdbook-theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="mdbook-theme-list">
|
|
|
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M371.3 367.1c27.3-3.9 51.9-19.4 67.2-42.9L600.2 74.1c12.6-19.5 9.4-45.3-7.6-61.2S549.7-4.4 531.1 9.6L294.4 187.2c-24 18-38.2 46.1-38.4 76.1L371.3 367.1zm-19.6 25.4l-116-104.4C175.9 290.3 128 339.6 128 400c0 3.9 .2 7.8 .6 11.6c1.8 17.5-10.2 36.4-27.8 36.4H96c-17.7 0-32 14.3-32 32s14.3 32 32 32H240c61.9 0 112-50.1 112-112c0-2.5-.1-5-.2-7.5z"/></svg></span>
|
|
|
</button>
|
|
|
<ul id="mdbook-theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
|
|
<li role="none"><button role="menuitem" class="theme" id="mdbook-theme-default_theme">Auto</button></li>
|
|
|
<li role="none"><button role="menuitem" class="theme" id="mdbook-theme-light">Light</button></li>
|
|
|
<li role="none"><button role="menuitem" class="theme" id="mdbook-theme-rust">Rust</button></li>
|
|
|
<li role="none"><button role="menuitem" class="theme" id="mdbook-theme-coal">Coal</button></li>
|
|
|
<li role="none"><button role="menuitem" class="theme" id="mdbook-theme-navy">Navy</button></li>
|
|
|
<li role="none"><button role="menuitem" class="theme" id="mdbook-theme-ayu">Ayu</button></li>
|
|
|
</ul>
|
|
|
<button id="mdbook-search-toggle" class="icon-button" type="button" title="Search (`/`)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="/ s" aria-controls="mdbook-searchbar">
|
|
|
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352c79.5 0 144-64.5 144-144s-64.5-144-144-144S64 128.5 64 208s64.5 144 144 144z"/></svg></span>
|
|
|
</button>
|
|
|
</div>
|
|
|
|
|
|
<h1 class="menu-title">Rust 程序设计语言 简体中文版</h1>
|
|
|
|
|
|
<div class="right-buttons">
|
|
|
<a href="print.html" title="Print this book" aria-label="Print this book">
|
|
|
<span class=fa-svg id="print-button"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M128 0C92.7 0 64 28.7 64 64v96h64V64H354.7L384 93.3V160h64V93.3c0-17-6.7-33.3-18.7-45.3L400 18.7C388 6.7 371.7 0 354.7 0H128zM384 352v32 64H128V384 368 352H384zm64 32h32c17.7 0 32-14.3 32-32V256c0-35.3-28.7-64-64-64H64c-35.3 0-64 28.7-64 64v96c0 17.7 14.3 32 32 32H64v64c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V384zm-16-88c-13.3 0-24-10.7-24-24s10.7-24 24-24s24 10.7 24 24s-10.7 24-24 24z"/></svg></span>
|
|
|
</a>
|
|
|
<a href="https://github.com/KaiserY/trpl-zh-cn/tree/main" title="Git repository" aria-label="Git repository">
|
|
|
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg></span>
|
|
|
</a>
|
|
|
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div id="mdbook-search-wrapper" class="hidden">
|
|
|
<form id="mdbook-searchbar-outer" class="searchbar-outer">
|
|
|
<div class="search-wrapper">
|
|
|
<input type="search" id="mdbook-searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="mdbook-searchresults-outer" aria-describedby="searchresults-header">
|
|
|
<div class="spinner-wrapper">
|
|
|
<span class=fa-svg id="fa-spin"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M304 48c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zm0 416c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zM48 304c26.5 0 48-21.5 48-48s-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48zm464-48c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zM142.9 437c18.7-18.7 18.7-49.1 0-67.9s-49.1-18.7-67.9 0s-18.7 49.1 0 67.9s49.1 18.7 67.9 0zm0-294.2c18.7-18.7 18.7-49.1 0-67.9S93.7 56.2 75 75s-18.7 49.1 0 67.9s49.1 18.7 67.9 0zM369.1 437c18.7 18.7 49.1 18.7 67.9 0s18.7-49.1 0-67.9s-49.1-18.7-67.9 0s-18.7 49.1 0 67.9z"/></svg></span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</form>
|
|
|
<div id="mdbook-searchresults-outer" class="searchresults-outer hidden">
|
|
|
<div id="mdbook-searchresults-header" class="searchresults-header"></div>
|
|
|
<ul id="mdbook-searchresults">
|
|
|
</ul>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
|
|
<script>
|
|
|
document.getElementById('mdbook-sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
|
|
document.getElementById('mdbook-sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
|
|
Array.from(document.querySelectorAll('#mdbook-sidebar a')).forEach(function(link) {
|
|
|
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
|
|
});
|
|
|
</script>
|
|
|
|
|
|
<div id="mdbook-content" class="content">
|
|
|
<main>
|
|
|
<h2 id="如何编写测试"><a class="header" href="#如何编写测试">如何编写测试</a></h2>
|
|
|
<!-- https://github.com/rust-lang/book/blob/main/src/ch11-01-writing-tests.md -->
|
|
|
<!-- commit 02e053cdbbb3bf9edd9ad32ed49eb533404350a9 -->
|
|
|
<p>Rust 中的测试函数是用来验证非测试代码是否按照期望的方式运行的。测试函数体通常执行如下三种操作:</p>
|
|
|
<ul>
|
|
|
<li>设置任何所需的数据或状态</li>
|
|
|
<li>运行需要测试的代码</li>
|
|
|
<li>断言其结果是我们所期望的</li>
|
|
|
</ul>
|
|
|
<p>让我们看看 Rust 提供的专门用来编写测试的功能:<code>test</code> 属性、一些宏和 <code>should_panic</code> 属性。</p>
|
|
|
<h3 id="测试函数剖析"><a class="header" href="#测试函数剖析">测试函数剖析</a></h3>
|
|
|
<p>作为最简单例子,Rust 中的测试就是一个带有 <code>test</code> 属性注解的函数。属性(attribute)是关于 Rust 代码片段的元数据;第五章中结构体中用到的 <code>derive</code> 属性就是一个例子。为了将一个函数变成测试函数,需要在 <code>fn</code> 行之前加上 <code>#[test]</code>。当使用 <code>cargo test</code> 命令运行测试时,Rust 会构建一个测试执行程序用来调用被标注的函数,并报告每一个测试是通过还是失败。</p>
|
|
|
<p>每次使用 Cargo 新建一个库项目时,它会自动为我们生成一个测试模块和一个测试函数。这个模块提供了一个编写测试的模板,为此每次开始新项目时不必去查找测试函数的具体结构和语法了。当然你也可以额外增加任意多的测试函数以及测试模块!</p>
|
|
|
<p>在实际编写测试代码之前,让我们先通过尝试那些自动生成的测试模版来探索测试是如何工作的。接着,我们会写一些真正的测试,调用我们编写的代码并断言它们的行为的正确性。</p>
|
|
|
<p>让我们创建一个新的库项目 <code>adder</code>,它会将两个数字相加:</p>
|
|
|
<pre><code class="language-console">$ cargo new adder --lib
|
|
|
Created library `adder` project
|
|
|
$ cd adder
|
|
|
</code></pre>
|
|
|
<p><code>adder</code> 库中 <em>src/lib.rs</em> 的内容应该看起来如示例 11-1 所示:</p>
|
|
|
<p><span class="filename">文件名:src/lib.rs</span></p>
|
|
|
<pre><code class="language-rust noplayground">pub fn add(left: u64, right: u64) -> u64 {
|
|
|
left + right
|
|
|
}
|
|
|
|
|
|
#[cfg(test)]
|
|
|
mod tests {
|
|
|
use super::*;
|
|
|
|
|
|
#[test]
|
|
|
fn it_works() {
|
|
|
let result = add(2, 2);
|
|
|
assert_eq!(result, 4);
|
|
|
}
|
|
|
}</code></pre>
|
|
|
<p><span class="caption">示例 11-1:由 <code>cargo new</code> 自动生成的测试模块和函数</span></p>
|
|
|
<p>文件以一个示例 <code>add</code> 函数开头,这样我们就有东西可以测试。</p>
|
|
|
<p>现在让我们只关注 <code>it_works</code> 函数本身。注意 <code>fn</code> 行之前的 <code>#[test]</code>:这个属性表明这是一个测试函数,这样测试执行者就知道将其作为测试处理。<code>tests</code> 模块中也可以有非测试的函数来帮助我们建立通用场景或进行常见操作,必须每次都标明哪些函数是测试。</p>
|
|
|
<p>示例函数体通过使用 <code>assert_eq!</code> 宏来断言 <code>result</code>(其中包含 2 加 2 的结果)等于 4。这个断言示例展示了典型测试的格式。接下来运行就可以看到测试通过。</p>
|
|
|
<p><code>cargo test</code> 命令会运行项目中所有的测试,如示例 11-2 所示:</p>
|
|
|
<pre><code class="language-console">$ cargo test
|
|
|
Compiling adder v0.1.0 (file:///projects/adder)
|
|
|
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.57s
|
|
|
Running unittests src/lib.rs (target/debug/deps/adder-01ad14159ff659ab)
|
|
|
|
|
|
running 1 test
|
|
|
test tests::it_works ... ok
|
|
|
|
|
|
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
|
|
|
|
|
Doc-tests adder
|
|
|
|
|
|
running 0 tests
|
|
|
|
|
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
|
|
|
|
|
</code></pre>
|
|
|
<p><span class="caption">示例 11-2:运行自动生成测试的输出</span></p>
|
|
|
<p>Cargo 编译并运行了测试。可以看到 <code>running 1 test</code> 这一行。下一行显示了生成的测试函数的名称,<code>tests::it_works</code>,以及测试的运行结果,<code>ok</code>。接着可以看到全体测试运行结果的摘要:<code>test result: ok.</code> 意味着所有测试都通过了。<code>1 passed; 0 failed</code> 表示通过或失败的测试数量。</p>
|
|
|
<p>可以将一个测试标记为忽略以便在特定情况下它就不会运行;本章之后的<a href="ch11-02-running-tests.html#除非特别指定否则忽略某些测试">“除非特别指定否则忽略某些测试”</a>部分会介绍它。因为之前我们并没有将任何测试标记为忽略,所以摘要中会显示 <code>0 ignored</code>。</p>
|
|
|
<p><code>0 measured</code> 统计是针对性能测试的。性能测试(benchmark tests)在编写本书时,仍只能用于 Rust 开发版(nightly Rust)。请查看 <a href="https://doc.rust-lang.org/unstable-book/library-features/test.html">性能测试的文档</a> 了解更多。</p>
|
|
|
<p>我们可以将参数传递给 <code>cargo test</code> 命令,以便只运行名称与字符串匹配的测试;这就是所谓的<strong>过滤</strong>(<em>filtering</em>),我们会在 <a href="ch11-02-running-tests.html#通过名称运行部分测试">“通过名称运行部分测试”</a> 讨论这一点。这里我们没有过滤需要运行的测试,所以摘要中会显示<code>0 filtered out</code>。</p>
|
|
|
<p>测试输出中的以 <code>Doc-tests adder</code> 开头的这一部分是所有文档测试的结果。我们现在并没有任何文档测试,不过 Rust 会编译任何在 API 文档中的代码示例。这个功能帮助我们使文档和代码保持同步!在第十四章的 <a href="ch14-02-publishing-to-crates-io.html#文档注释作为测试">“文档注释作为测试”</a> 部分会讲到如何编写文档测试。现在我们将忽略 <code>Doc-tests</code> 部分的输出。</p>
|
|
|
<p>让我们开始自定义测试来满足我们的需求。首先给 <code>it_works</code> 函数起个不同的名字,比如 <code>exploration</code>,像这样:</p>
|
|
|
<p><span class="filename">文件名:src/lib.rs</span></p>
|
|
|
<pre><code class="language-rust noplayground">pub fn add(left: u64, right: u64) -> u64 {
|
|
|
left + right
|
|
|
}
|
|
|
|
|
|
#[cfg(test)]
|
|
|
mod tests {
|
|
|
use super::*;
|
|
|
|
|
|
#[test]
|
|
|
fn exploration() {
|
|
|
let result = add(2, 2);
|
|
|
assert_eq!(result, 4);
|
|
|
}
|
|
|
}</code></pre>
|
|
|
<p>并再次运行 <code>cargo test</code>。现在输出中将出现 <code>exploration</code> 而不是 <code>it_works</code>:</p>
|
|
|
<pre><code class="language-console">$ cargo test
|
|
|
Compiling adder v0.1.0 (file:///projects/adder)
|
|
|
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.59s
|
|
|
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
|
|
|
|
|
|
running 1 test
|
|
|
test tests::exploration ... ok
|
|
|
|
|
|
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
|
|
|
|
|
Doc-tests adder
|
|
|
|
|
|
running 0 tests
|
|
|
|
|
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
|
|
|
|
|
</code></pre>
|
|
|
<p>现在让我们增加另一个测试,不过这一次是一个会失败的测试!当测试函数中出现 panic 时测试就失败了。每一个测试都在一个新线程中运行,当主线程发现测试线程异常了,就将对应测试标记为失败。第九章讲到了最简单的造成 panic 的方法:调用 <code>panic!</code> 宏。写入新测试 <code>another</code> 后,<em>src/lib.rs</em> 现在看起来如示例 11-3 所示:</p>
|
|
|
<p><span class="filename">文件名:src/lib.rs</span></p>
|
|
|
<pre><code class="language-rust panics noplayground">pub fn add(left: u64, right: u64) -> u64 {
|
|
|
left + right
|
|
|
}
|
|
|
|
|
|
#[cfg(test)]
|
|
|
mod tests {
|
|
|
use super::*;
|
|
|
|
|
|
#[test]
|
|
|
fn exploration() {
|
|
|
let result = add(2, 2);
|
|
|
assert_eq!(result, 4);
|
|
|
}
|
|
|
|
|
|
#[test]
|
|
|
fn another() {
|
|
|
panic!("Make this test fail");
|
|
|
}
|
|
|
}</code></pre>
|
|
|
<p><span class="caption">示例 11-3:增加第二个因调用了 <code>panic!</code> 而失败的测试</span></p>
|
|
|
<p>再次 <code>cargo test</code> 运行测试。输出应该看起来像示例 11-4,它表明 <code>exploration</code> 测试通过了而 <code>another</code> 失败了:</p>
|
|
|
<pre><code class="language-console">$ cargo test
|
|
|
Compiling adder v0.1.0 (file:///projects/adder)
|
|
|
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.72s
|
|
|
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
|
|
|
|
|
|
running 2 tests
|
|
|
test tests::another ... FAILED
|
|
|
test tests::exploration ... ok
|
|
|
|
|
|
failures:
|
|
|
|
|
|
---- tests::another stdout ----
|
|
|
|
|
|
thread 'tests::another' panicked at src/lib.rs:17:9:
|
|
|
Make this test fail
|
|
|
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
|
|
|
|
|
|
|
|
failures:
|
|
|
tests::another
|
|
|
|
|
|
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
|
|
|
|
|
error: test failed, to rerun pass `--lib`
|
|
|
</code></pre>
|
|
|
<p><span class="caption">示例 11-4:一个测试通过和一个测试失败的测试结果</span></p>
|
|
|
<p><code>test tests::another</code> 这一行是 <code>FAILED</code> 而不是 <code>ok</code> 了。在单独测试结果和摘要之间多了两个新的部分:第一个部分显示了测试失败的详细原因。在这个例子中,我们看到 <code>another</code> 因为在 <em>src/lib.rs</em> 的第 17 行 <code>panicked at 'Make this test fail'</code> 而失败的详细信息。下一部分列出了所有失败的测试,这在有很多测试和很多失败测试的详细输出时很有帮助。我们可以通过使用失败测试的名称来只运行这个测试,以便调试;下一部分 <a href="ch11-02-running-tests.html#控制测试如何运行">“控制测试如何运行”</a> 会讲到更多运行测试的方法。</p>
|
|
|
<p>最后是摘要行:总体上讲,测试结果是 <code>FAILED</code>。有一个测试通过和一个测试失败。</p>
|
|
|
<p>现在我们见过不同场景中测试结果是什么样子的了,再来看看除 <code>panic!</code> 之外的一些在测试中有帮助的宏吧。</p>
|
|
|
<h3 id="使用-assert-宏来检查结果"><a class="header" href="#使用-assert-宏来检查结果">使用 <code>assert!</code> 宏来检查结果</a></h3>
|
|
|
<p><code>assert!</code> 宏由标准库提供,在希望确保测试中一些条件为 <code>true</code> 时非常有用。需要向 <code>assert!</code> 宏提供一个求值为布尔值的参数。如果值是 <code>true</code>,<code>assert!</code> 什么也不做,同时测试会通过。如果值为 <code>false</code>,<code>assert!</code> 调用 <code>panic!</code> 宏,这会导致测试失败。<code>assert!</code> 宏帮助我们检查代码是否以期望的方式运行。</p>
|
|
|
<p>回忆一下第五章中,示例 5-15 中有一个 <code>Rectangle</code> 结构体和一个 <code>can_hold</code> 方法,在示例 11-5 中再次使用它们。将它们放进 <em>src/lib.rs</em> 并使用 <code>assert!</code> 宏编写一些测试。</p>
|
|
|
<p><span class="filename">文件名:src/lib.rs</span></p>
|
|
|
<pre><code class="language-rust noplayground">#[derive(Debug)]
|
|
|
struct Rectangle {
|
|
|
width: u32,
|
|
|
height: u32,
|
|
|
}
|
|
|
|
|
|
impl Rectangle {
|
|
|
fn can_hold(&self, other: &Rectangle) -> bool {
|
|
|
self.width > other.width && self.height > other.height
|
|
|
}
|
|
|
}</code></pre>
|
|
|
<p><span class="caption">示例 11-5:第五章中 <code>Rectangle</code> 结构体和其 <code>can_hold</code> 方法</span></p>
|
|
|
<p><code>can_hold</code> 方法返回一个布尔值,这意味着它完美符合 <code>assert!</code> 宏的使用场景。在示例 11-6 中,让我们编写一个 <code>can_hold</code> 方法的测试来作为练习,这里创建一个宽为 8 高为 7 的 <code>Rectangle</code> 实例,并假设它可以放得下另一个宽为 5 高为 1 的 <code>Rectangle</code> 实例:</p>
|
|
|
<p><span class="filename">文件名:src/lib.rs</span></p>
|
|
|
<pre><code class="language-rust noplayground"><span class="boring">#[derive(Debug)]
|
|
|
</span><span class="boring">struct Rectangle {
|
|
|
</span><span class="boring"> width: u32,
|
|
|
</span><span class="boring"> height: u32,
|
|
|
</span><span class="boring">}
|
|
|
</span><span class="boring">
|
|
|
</span><span class="boring">impl Rectangle {
|
|
|
</span><span class="boring"> fn can_hold(&self, other: &Rectangle) -> bool {
|
|
|
</span><span class="boring"> self.width > other.width && self.height > other.height
|
|
|
</span><span class="boring"> }
|
|
|
</span><span class="boring">}
|
|
|
</span><span class="boring">
|
|
|
</span>#[cfg(test)]
|
|
|
mod tests {
|
|
|
use super::*;
|
|
|
|
|
|
#[test]
|
|
|
fn larger_can_hold_smaller() {
|
|
|
let larger = Rectangle {
|
|
|
width: 8,
|
|
|
height: 7,
|
|
|
};
|
|
|
let smaller = Rectangle {
|
|
|
width: 5,
|
|
|
height: 1,
|
|
|
};
|
|
|
|
|
|
assert!(larger.can_hold(&smaller));
|
|
|
}
|
|
|
}</code></pre>
|
|
|
<p><span class="caption">示例 11-6:一个 <code>can_hold</code> 的测试,检查一个较大的矩形确实能放得下一个较小的矩形</span></p>
|
|
|
<p>注意在 <code>tests</code> 模块中新增加了一行:<code>use super::*;</code>。<code>tests</code> 是一个普通的模块,它遵循第七章 <a href="ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html">“路径用于引用模块树中的项”</a> 部分介绍的可见性规则。因为 <code>tests</code> 模块是一个内部模块,要测试外部模块中的代码,需要将其引入到内部模块的作用域中。这里选择使用 glob 全局导入,以便在 <code>tests</code> 模块中使用所有在外部模块定义的内容。</p>
|
|
|
<p>我们将测试命名为 <code>larger_can_hold_smaller</code>,并创建所需的两个 <code>Rectangle</code> 实例。接着调用 <code>assert!</code> 宏并传递 <code>larger.can_hold(&smaller)</code> 调用的结果作为参数。这个表达式预期会返回 <code>true</code>,所以测试应该通过。让我们拭目以待!</p>
|
|
|
<pre><code class="language-console">$ cargo test
|
|
|
Compiling rectangle v0.1.0 (file:///projects/rectangle)
|
|
|
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.66s
|
|
|
Running unittests src/lib.rs (target/debug/deps/rectangle-6584c4561e48942e)
|
|
|
|
|
|
running 1 test
|
|
|
test tests::larger_can_hold_smaller ... ok
|
|
|
|
|
|
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
|
|
|
|
|
Doc-tests rectangle
|
|
|
|
|
|
running 0 tests
|
|
|
|
|
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
|
|
|
|
|
</code></pre>
|
|
|
<p>它确实通过了!再来增加另一个测试,这一回断言一个更小的矩形不能放下一个更大的矩形:</p>
|
|
|
<p><span class="filename">文件名:src/lib.rs</span></p>
|
|
|
<pre><code class="language-rust noplayground"><span class="boring">#[derive(Debug)]
|
|
|
</span><span class="boring">struct Rectangle {
|
|
|
</span><span class="boring"> width: u32,
|
|
|
</span><span class="boring"> height: u32,
|
|
|
</span><span class="boring">}
|
|
|
</span><span class="boring">
|
|
|
</span><span class="boring">impl Rectangle {
|
|
|
</span><span class="boring"> fn can_hold(&self, other: &Rectangle) -> bool {
|
|
|
</span><span class="boring"> self.width > other.width && self.height > other.height
|
|
|
</span><span class="boring"> }
|
|
|
</span><span class="boring">}
|
|
|
</span><span class="boring">
|
|
|
</span>#[cfg(test)]
|
|
|
mod tests {
|
|
|
use super::*;
|
|
|
|
|
|
#[test]
|
|
|
fn larger_can_hold_smaller() {
|
|
|
// --snip--
|
|
|
<span class="boring"> let larger = Rectangle {
|
|
|
</span><span class="boring"> width: 8,
|
|
|
</span><span class="boring"> height: 7,
|
|
|
</span><span class="boring"> };
|
|
|
</span><span class="boring"> let smaller = Rectangle {
|
|
|
</span><span class="boring"> width: 5,
|
|
|
</span><span class="boring"> height: 1,
|
|
|
</span><span class="boring"> };
|
|
|
</span><span class="boring">
|
|
|
</span><span class="boring"> assert!(larger.can_hold(&smaller));
|
|
|
</span> }
|
|
|
|
|
|
#[test]
|
|
|
fn smaller_cannot_hold_larger() {
|
|
|
let larger = Rectangle {
|
|
|
width: 8,
|
|
|
height: 7,
|
|
|
};
|
|
|
let smaller = Rectangle {
|
|
|
width: 5,
|
|
|
height: 1,
|
|
|
};
|
|
|
|
|
|
assert!(!smaller.can_hold(&larger));
|
|
|
}
|
|
|
}</code></pre>
|
|
|
<p>因为这里 <code>can_hold</code> 函数的正确结果是 <code>false</code> ,我们需要将这个结果取反后传递给 <code>assert!</code> 宏。因此 <code>can_hold</code> 返回 <code>false</code> 时测试就会通过:</p>
|
|
|
<pre><code class="language-console">$ cargo test
|
|
|
Compiling rectangle v0.1.0 (file:///projects/rectangle)
|
|
|
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.66s
|
|
|
Running unittests src/lib.rs (target/debug/deps/rectangle-6584c4561e48942e)
|
|
|
|
|
|
running 2 tests
|
|
|
test tests::larger_can_hold_smaller ... ok
|
|
|
test tests::smaller_cannot_hold_larger ... ok
|
|
|
|
|
|
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
|
|
|
|
|
Doc-tests rectangle
|
|
|
|
|
|
running 0 tests
|
|
|
|
|
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
|
|
|
|
|
</code></pre>
|
|
|
<p>两个测试通过了!现在让我们看看如果引入一个 bug 的话测试结果会发生什么。将 <code>can_hold</code> 方法中比较宽度时本应使用大于号的地方改成小于号:</p>
|
|
|
<pre><code class="language-rust not_desired_behavior noplayground"><span class="boring">#[derive(Debug)]
|
|
|
</span><span class="boring">struct Rectangle {
|
|
|
</span><span class="boring"> width: u32,
|
|
|
</span><span class="boring"> height: u32,
|
|
|
</span><span class="boring">}
|
|
|
</span><span class="boring">
|
|
|
</span>// --snip--
|
|
|
impl Rectangle {
|
|
|
fn can_hold(&self, other: &Rectangle) -> bool {
|
|
|
self.width < other.width && self.height > other.height
|
|
|
}
|
|
|
}
|
|
|
<span class="boring">
|
|
|
</span><span class="boring">#[cfg(test)]
|
|
|
</span><span class="boring">mod tests {
|
|
|
</span><span class="boring"> use super::*;
|
|
|
</span><span class="boring">
|
|
|
</span><span class="boring"> #[test]
|
|
|
</span><span class="boring"> fn larger_can_hold_smaller() {
|
|
|
</span><span class="boring"> let larger = Rectangle {
|
|
|
</span><span class="boring"> width: 8,
|
|
|
</span><span class="boring"> height: 7,
|
|
|
</span><span class="boring"> };
|
|
|
</span><span class="boring"> let smaller = Rectangle {
|
|
|
</span><span class="boring"> width: 5,
|
|
|
</span><span class="boring"> height: 1,
|
|
|
</span><span class="boring"> };
|
|
|
</span><span class="boring">
|
|
|
</span><span class="boring"> assert!(larger.can_hold(&smaller));
|
|
|
</span><span class="boring"> }
|
|
|
</span><span class="boring">
|
|
|
</span><span class="boring"> #[test]
|
|
|
</span><span class="boring"> fn smaller_cannot_hold_larger() {
|
|
|
</span><span class="boring"> let larger = Rectangle {
|
|
|
</span><span class="boring"> width: 8,
|
|
|
</span><span class="boring"> height: 7,
|
|
|
</span><span class="boring"> };
|
|
|
</span><span class="boring"> let smaller = Rectangle {
|
|
|
</span><span class="boring"> width: 5,
|
|
|
</span><span class="boring"> height: 1,
|
|
|
</span><span class="boring"> };
|
|
|
</span><span class="boring">
|
|
|
</span><span class="boring"> assert!(!smaller.can_hold(&larger));
|
|
|
</span><span class="boring"> }
|
|
|
</span><span class="boring">}</span></code></pre>
|
|
|
<p>现在运行测试会产生以下结果:</p>
|
|
|
<pre><code class="language-console">$ cargo test
|
|
|
Compiling rectangle v0.1.0 (file:///projects/rectangle)
|
|
|
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.66s
|
|
|
Running unittests src/lib.rs (target/debug/deps/rectangle-6584c4561e48942e)
|
|
|
|
|
|
running 2 tests
|
|
|
test tests::larger_can_hold_smaller ... FAILED
|
|
|
test tests::smaller_cannot_hold_larger ... ok
|
|
|
|
|
|
failures:
|
|
|
|
|
|
---- tests::larger_can_hold_smaller stdout ----
|
|
|
|
|
|
thread 'tests::larger_can_hold_smaller' panicked at src/lib.rs:28:9:
|
|
|
assertion failed: larger.can_hold(&smaller)
|
|
|
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
|
|
|
|
|
|
|
|
failures:
|
|
|
tests::larger_can_hold_smaller
|
|
|
|
|
|
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
|
|
|
|
|
error: test failed, to rerun pass `--lib`
|
|
|
</code></pre>
|
|
|
<p>我们的测试捕获了 bug!因为 <code>larger.width</code> 是 8 而 <code>smaller.width</code> 是 5,<code>can_hold</code> 中的宽度比较现在因为 8 不小于 5 而返回 <code>false</code>:8 并不小于 5。</p>
|
|
|
<h3 id="使用-assert_eq-和-assert_ne-宏测试相等"><a class="header" href="#使用-assert_eq-和-assert_ne-宏测试相等">使用 <code>assert_eq!</code> 和 <code>assert_ne!</code> 宏测试相等</a></h3>
|
|
|
<p>测试功能的一个常用方法是将需要测试代码的值与期望值做比较,并检查是否相等。可以通过向 <code>assert!</code> 宏传递一个使用 <code>==</code> 运算符的表达式来做到。不过这个操作实在是太常见了,以至于标准库提供了一对宏来更方便的处理这些操作 —— <code>assert_eq!</code> 和 <code>assert_ne!</code>。这两个宏分别比较两个值是相等还是不相等。当断言失败时它们也会打印出这两个值具体是什么,以便于观察测试<strong>为什么</strong>失败,而 <code>assert!</code> 只会打印出它从 <code>==</code> 表达式中得到了 <code>false</code> 值,而不是打印导致 <code>false</code> 的具体值。</p>
|
|
|
<p>示例 11-7 中,让我们编写一个对其参数加二并返回结果的函数 <code>add_two</code>。接着使用 <code>assert_eq!</code> 宏测试这个函数。</p>
|
|
|
<p><span class="filename">文件名:src/lib.rs</span></p>
|
|
|
<pre><code class="language-rust noplayground">pub fn add_two(a: usize) -> usize {
|
|
|
a + 2
|
|
|
}
|
|
|
|
|
|
#[cfg(test)]
|
|
|
mod tests {
|
|
|
use super::*;
|
|
|
|
|
|
#[test]
|
|
|
fn it_adds_two() {
|
|
|
let result = add_two(2);
|
|
|
assert_eq!(result, 4);
|
|
|
}
|
|
|
}</code></pre>
|
|
|
<p><span class="caption">示例 11-7:使用 <code>assert_eq!</code> 宏测试 <code>add_two</code> 函数</span></p>
|
|
|
<p>测试通过了!</p>
|
|
|
<pre><code class="language-console">$ cargo test
|
|
|
Compiling adder v0.1.0 (file:///projects/adder)
|
|
|
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.58s
|
|
|
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
|
|
|
|
|
|
running 1 test
|
|
|
test tests::it_adds_two ... ok
|
|
|
|
|
|
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
|
|
|
|
|
Doc-tests adder
|
|
|
|
|
|
running 0 tests
|
|
|
|
|
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
|
|
|
|
|
</code></pre>
|
|
|
<p>我们创建一个名为 <code>result</code> 的变量,用于保存调用 <code>add_two(2)</code> 的结果。然后我们将 <code>result</code> 和 <code>4</code> 作为参数传递给 <code>assert_eq!</code>。测试中的这一行 <code>test tests::it_adds_two ... ok</code> 中 <code>ok</code> 表明测试通过!</p>
|
|
|
<p>在代码中引入一个 bug 来看看使用 <code>assert_eq!</code> 的测试失败是什么样的。修改 <code>add_two</code> 函数的实现使其加 <code>3</code>:</p>
|
|
|
<pre><code class="language-rust not_desired_behavior noplayground">pub fn add_two(a: usize) -> usize {
|
|
|
a + 3
|
|
|
}
|
|
|
<span class="boring">
|
|
|
</span><span class="boring">#[cfg(test)]
|
|
|
</span><span class="boring">mod tests {
|
|
|
</span><span class="boring"> use super::*;
|
|
|
</span><span class="boring">
|
|
|
</span><span class="boring"> #[test]
|
|
|
</span><span class="boring"> fn it_adds_two() {
|
|
|
</span><span class="boring"> let result = add_two(2);
|
|
|
</span><span class="boring"> assert_eq!(result, 4);
|
|
|
</span><span class="boring"> }
|
|
|
</span><span class="boring">}</span></code></pre>
|
|
|
<p>再次运行测试:</p>
|
|
|
<pre><code class="language-console">$ cargo test
|
|
|
Compiling adder v0.1.0 (file:///projects/adder)
|
|
|
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.61s
|
|
|
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
|
|
|
|
|
|
running 1 test
|
|
|
test tests::it_adds_two ... FAILED
|
|
|
|
|
|
failures:
|
|
|
|
|
|
---- tests::it_adds_two stdout ----
|
|
|
|
|
|
thread 'tests::it_adds_two' panicked at src/lib.rs:12:9:
|
|
|
assertion `left == right` failed
|
|
|
left: 5
|
|
|
right: 4
|
|
|
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
|
|
|
|
|
|
|
|
failures:
|
|
|
tests::it_adds_two
|
|
|
|
|
|
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
|
|
|
|
|
error: test failed, to rerun pass `--lib`
|
|
|
</code></pre>
|
|
|
<p>测试捕获到了 bug!<code>it_adds_two</code> 测试失败,错误信息告诉我们断言失败了,它告诉我们 <code>assertion `left == right` failed</code> 以及 <code>left</code> 和 <code>right</code> 的值是什么。这个错误信息有助于我们开始调试:它说 <code>assert_eq!</code> 的 <code>left</code> 参数(也就是 <code>add_two(2)</code> 的结果)是 <code>5</code>,而 <code>right</code> 参数是 <code>4</code>。可以想象当有很多测试在运行时这些信息是多么的有用。</p>
|
|
|
<p>需要注意的是,在一些语言和测试框架中,断言两个值相等的函数的参数被称为 <code>expected</code> 和 <code>actual</code>,而且指定参数的顺序非常重要。然而在 Rust 中,它们则叫做 <code>left</code> 和 <code>right</code>,同时指定期望的值和被测试代码产生的值的顺序并不重要。这个测试中的断言也可以写成 <code>assert_eq!(add_two(2), result)</code>,这时失败信息仍同样是 <code>assertion failed: `(left == right)`</code>。</p>
|
|
|
<p><code>assert_ne!</code> 宏在传递给它的两个值不相等时通过,而在相等时失败。当我们不确定值<strong>会</strong>是什么,不过能确定值绝对<strong>不会</strong>是什么的时候,这个宏最有用处。例如,如果一个函数保证会以某种方式改变其输入,不过这种改变方式是由运行测试时是星期几来决定的,这时最好的断言可能就是函数的输出不等于其输入。</p>
|
|
|
<p><code>assert_eq!</code> 和 <code>assert_ne!</code> 宏在底层分别使用了 <code>==</code> 和 <code>!=</code>。当断言失败时,这些宏会使用调试格式打印出其参数,这意味着被比较的值必须实现了 <code>PartialEq</code> 和 <code>Debug</code> trait。所有的基本类型和大部分标准库类型都实现了这些 trait。对于自定义的结构体和枚举,需要实现 <code>PartialEq</code> 才能断言它们的值是否相等。需要实现 <code>Debug</code> 才能在断言失败时打印它们的值。因为这两个 trait 都是派生 trait,如第五章示例 5-12 所提到的,通常可以直接在结构体或枚举上添加 <code>#[derive(PartialEq, Debug)]</code> 注解。附录 C <a href="appendix-03-derivable-traits.html">“可派生 trait”</a> 中有更多关于这些和其他派生 trait 的详细信息。</p>
|
|
|
<h3 id="自定义失败信息"><a class="header" href="#自定义失败信息">自定义失败信息</a></h3>
|
|
|
<p>你也可以向 <code>assert!</code>、<code>assert_eq!</code> 和 <code>assert_ne!</code> 宏传递一个可选的失败信息参数,可以在测试失败时将自定义失败信息一同打印出来。任何在 <code>assert!</code> 的一个必需参数和 <code>assert_eq!</code> 和 <code>assert_ne!</code> 的两个必需参数之后指定的参数都会传递给 <code>format!</code> 宏(在第八章的 <a href="ch08-02-strings.html#使用--运算符或-format-宏拼接字符串">“使用 <code>+</code> 运算符或 <code>format!</code> 宏拼接字符串”</a> 部分讨论过),所以可以传递一个包含 <code>{}</code> 占位符的格式字符串和需要放入占位符的值。自定义信息有助于记录断言的意义;当测试失败时就能更好的理解代码出了什么问题。</p>
|
|
|
<p>例如,比如说有一个根据人名进行问候的函数,而我们希望测试将传递给函数的人名显示在输出中:</p>
|
|
|
<p><span class="filename">文件名:src/lib.rs</span></p>
|
|
|
<pre><code class="language-rust noplayground">pub fn greeting(name: &str) -> String {
|
|
|
format!("Hello {name}!")
|
|
|
}
|
|
|
|
|
|
#[cfg(test)]
|
|
|
mod tests {
|
|
|
use super::*;
|
|
|
|
|
|
#[test]
|
|
|
fn greeting_contains_name() {
|
|
|
let result = greeting("Carol");
|
|
|
assert!(result.contains("Carol"));
|
|
|
}
|
|
|
}</code></pre>
|
|
|
<p>这个程序的需求还没有被确定,因此问候文本开头的 <code>Hello</code> 文本很可能会改变。然而我们并不想在需求改变时不得不更新测试,所以相比检查 <code>greeting</code> 函数返回的确切值,我们将仅仅断言输出的文本中包含输入参数。</p>
|
|
|
<p>让我们通过将 <code>greeting</code> 改为不包含 <code>name</code> 在代码中引入一个 bug 来测试失败时是怎样的:</p>
|
|
|
<pre><code class="language-rust not_desired_behavior noplayground">pub fn greeting(name: &str) -> String {
|
|
|
String::from("Hello!")
|
|
|
}
|
|
|
<span class="boring">
|
|
|
</span><span class="boring">#[cfg(test)]
|
|
|
</span><span class="boring">mod tests {
|
|
|
</span><span class="boring"> use super::*;
|
|
|
</span><span class="boring">
|
|
|
</span><span class="boring"> #[test]
|
|
|
</span><span class="boring"> fn greeting_contains_name() {
|
|
|
</span><span class="boring"> let result = greeting("Carol");
|
|
|
</span><span class="boring"> assert!(result.contains("Carol"));
|
|
|
</span><span class="boring"> }
|
|
|
</span><span class="boring">}</span></code></pre>
|
|
|
<p>运行测试会产生:</p>
|
|
|
<pre><code class="language-console">$ cargo test
|
|
|
Compiling greeter v0.1.0 (file:///projects/greeter)
|
|
|
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.91s
|
|
|
Running unittests src/lib.rs (target/debug/deps/greeter-170b942eb5bf5e3a)
|
|
|
|
|
|
running 1 test
|
|
|
test tests::greeting_contains_name ... FAILED
|
|
|
|
|
|
failures:
|
|
|
|
|
|
---- tests::greeting_contains_name stdout ----
|
|
|
|
|
|
thread 'tests::greeting_contains_name' panicked at src/lib.rs:12:9:
|
|
|
assertion failed: result.contains("Carol")
|
|
|
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
|
|
|
|
|
|
|
|
failures:
|
|
|
tests::greeting_contains_name
|
|
|
|
|
|
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
|
|
|
|
|
error: test failed, to rerun pass `--lib`
|
|
|
</code></pre>
|
|
|
<p>结果仅仅告诉了我们断言失败了和失败的行号。一个更有用的失败信息应该打印出 <code>greeting</code> 函数的值。让我们为测试函数增加一个自定义失败信息参数:带占位符的格式字符串,以及 <code>greeting</code> 函数的值:</p>
|
|
|
<pre><code class="language-rust ignore"><span class="boring">pub fn greeting(name: &str) -> String {
|
|
|
</span><span class="boring"> String::from("Hello!")
|
|
|
</span><span class="boring">}
|
|
|
</span><span class="boring">
|
|
|
</span><span class="boring">#[cfg(test)]
|
|
|
</span><span class="boring">mod tests {
|
|
|
</span><span class="boring"> use super::*;
|
|
|
</span><span class="boring">
|
|
|
</span> #[test]
|
|
|
fn greeting_contains_name() {
|
|
|
let result = greeting("Carol");
|
|
|
assert!(
|
|
|
result.contains("Carol"),
|
|
|
"Greeting did not contain name, value was `{result}`"
|
|
|
);
|
|
|
}
|
|
|
<span class="boring">}</span></code></pre>
|
|
|
<p>现在如果再次运行测试,将会看到更有价值的信息:</p>
|
|
|
<pre><code class="language-console">$ cargo test
|
|
|
Compiling greeter v0.1.0 (file:///projects/greeter)
|
|
|
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.93s
|
|
|
Running unittests src/lib.rs (target/debug/deps/greeter-170b942eb5bf5e3a)
|
|
|
|
|
|
running 1 test
|
|
|
test tests::greeting_contains_name ... FAILED
|
|
|
|
|
|
failures:
|
|
|
|
|
|
---- tests::greeting_contains_name stdout ----
|
|
|
|
|
|
thread 'tests::greeting_contains_name' panicked at src/lib.rs:12:9:
|
|
|
Greeting did not contain name, value was `Hello!`
|
|
|
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
|
|
|
|
|
|
|
|
failures:
|
|
|
tests::greeting_contains_name
|
|
|
|
|
|
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
|
|
|
|
|
error: test failed, to rerun pass `--lib`
|
|
|
</code></pre>
|
|
|
<p>可以在测试输出中看到所取得的确切的值,这会帮助我们调试真正发生了什么,而不是期望发生什么。</p>
|
|
|
<h3 id="使用-should_panic-检查-panic"><a class="header" href="#使用-should_panic-检查-panic">使用 <code>should_panic</code> 检查 panic</a></h3>
|
|
|
<p>除了检查返回值之外,检查代码是否按照期望处理错误也是很重要的。例如,考虑第九章示例 9-13 创建的 <code>Guess</code> 类型。其他使用 <code>Guess</code> 的代码都是基于 <code>Guess</code> 实例仅有的值范围在 1 到 100 的前提。可以编写一个测试来确保创建一个超出范围的值的 <code>Guess</code> 实例会 panic。</p>
|
|
|
<p>可以通过对函数增加另一个属性 <code>should_panic</code> 来实现这些。这个属性在函数中的代码 panic 时会通过,而在其中的代码没有 panic 时失败。</p>
|
|
|
<p>示例 11-8 展示了一个测试,检查 <code>Guess::new</code> 在错误条件下是否如我们所料那样。</p>
|
|
|
<p><span class="filename">文件名:src/lib.rs</span></p>
|
|
|
<pre><code class="language-rust noplayground">pub struct Guess {
|
|
|
value: i32,
|
|
|
}
|
|
|
|
|
|
impl Guess {
|
|
|
pub fn new(value: i32) -> Guess {
|
|
|
if value < 1 || value > 100 {
|
|
|
panic!("Guess value must be between 1 and 100, got {value}.");
|
|
|
}
|
|
|
|
|
|
Guess { value }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#[cfg(test)]
|
|
|
mod tests {
|
|
|
use super::*;
|
|
|
|
|
|
#[test]
|
|
|
#[should_panic]
|
|
|
fn greater_than_100() {
|
|
|
Guess::new(200);
|
|
|
}
|
|
|
}</code></pre>
|
|
|
<p><span class="caption">示例 11-8:测试会造成 <code>panic!</code> 的条件</span></p>
|
|
|
<p><code>#[should_panic]</code> 属性位于 <code>#[test]</code> 之后,对应的测试函数之前。让我们看看测试通过时它是什么样子:</p>
|
|
|
<pre><code class="language-console">$ cargo test
|
|
|
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
|
|
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.58s
|
|
|
Running unittests src/lib.rs (target/debug/deps/guessing_game-57d70c3acb738f4d)
|
|
|
|
|
|
running 1 test
|
|
|
test tests::greater_than_100 - should panic ... ok
|
|
|
|
|
|
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
|
|
|
|
|
Doc-tests guessing_game
|
|
|
|
|
|
running 0 tests
|
|
|
|
|
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
|
|
|
|
|
</code></pre>
|
|
|
<p>看起来不错!现在在代码中引入 bug,移除 <code>new</code> 函数在值大于 100 时会 panic 的条件:</p>
|
|
|
<pre><code class="language-rust not_desired_behavior noplayground"><span class="boring">pub struct Guess {
|
|
|
</span><span class="boring"> value: i32,
|
|
|
</span><span class="boring">}
|
|
|
</span><span class="boring">
|
|
|
</span>// --snip--
|
|
|
impl Guess {
|
|
|
pub fn new(value: i32) -> Guess {
|
|
|
if value < 1 {
|
|
|
panic!("Guess value must be between 1 and 100, got {value}.");
|
|
|
}
|
|
|
|
|
|
Guess { value }
|
|
|
}
|
|
|
}
|
|
|
<span class="boring">
|
|
|
</span><span class="boring">#[cfg(test)]
|
|
|
</span><span class="boring">mod tests {
|
|
|
</span><span class="boring"> use super::*;
|
|
|
</span><span class="boring">
|
|
|
</span><span class="boring"> #[test]
|
|
|
</span><span class="boring"> #[should_panic]
|
|
|
</span><span class="boring"> fn greater_than_100() {
|
|
|
</span><span class="boring"> Guess::new(200);
|
|
|
</span><span class="boring"> }
|
|
|
</span><span class="boring">}</span></code></pre>
|
|
|
<p>如果运行示例 11-8 的测试,它会失败:</p>
|
|
|
<pre><code class="language-console">$ cargo test
|
|
|
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
|
|
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.62s
|
|
|
Running unittests src/lib.rs (target/debug/deps/guessing_game-57d70c3acb738f4d)
|
|
|
|
|
|
running 1 test
|
|
|
test tests::greater_than_100 - should panic ... FAILED
|
|
|
|
|
|
failures:
|
|
|
|
|
|
---- tests::greater_than_100 stdout ----
|
|
|
note: test did not panic as expected
|
|
|
|
|
|
failures:
|
|
|
tests::greater_than_100
|
|
|
|
|
|
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
|
|
|
|
|
error: test failed, to rerun pass `--lib`
|
|
|
</code></pre>
|
|
|
<p>这回并没有得到非常有用的信息,不过一旦我们观察测试函数,会发现它标注了 <code>#[should_panic]</code>。这个错误意味着代码中测试函数 <code>Guess::new(200)</code> 并没有产生 panic。</p>
|
|
|
<p>然而 <code>should_panic</code> 测试结果可能会非常含糊不清。<code>should_panic</code> 甚至在一些不是我们期望的原因而导致 panic 时也会通过。为了使 <code>should_panic</code> 测试结果更精确,我们可以给 <code>should_panic</code> 属性增加一个可选的 <code>expected</code> 参数。测试工具会确保错误信息中包含其提供的文本。例如,考虑示例 11-9 中修改过的 <code>Guess</code>,这里 <code>new</code> 函数根据其值是过大还或者过小而提供不同的 panic 信息:</p>
|
|
|
<p><span class="filename">文件名:src/lib.rs</span></p>
|
|
|
<pre><code class="language-rust noplayground"><span class="boring">pub struct Guess {
|
|
|
</span><span class="boring"> value: i32,
|
|
|
</span><span class="boring">}
|
|
|
</span><span class="boring">
|
|
|
</span>// --snip--
|
|
|
|
|
|
impl Guess {
|
|
|
pub fn new(value: i32) -> Guess {
|
|
|
if value < 1 {
|
|
|
panic!(
|
|
|
"Guess value must be greater than or equal to 1, got {value}."
|
|
|
);
|
|
|
} else if value > 100 {
|
|
|
panic!(
|
|
|
"Guess value must be less than or equal to 100, got {value}."
|
|
|
);
|
|
|
}
|
|
|
|
|
|
Guess { value }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#[cfg(test)]
|
|
|
mod tests {
|
|
|
use super::*;
|
|
|
|
|
|
#[test]
|
|
|
#[should_panic(expected = "less than or equal to 100")]
|
|
|
fn greater_than_100() {
|
|
|
Guess::new(200);
|
|
|
}
|
|
|
}</code></pre>
|
|
|
<p><span class="caption">示例 11-9:一个错误信息包含特定子字符串的 <code>panic!</code> 条件的测试</span></p>
|
|
|
<p>这个测试会通过,因为 <code>should_panic</code> 属性中 <code>expected</code> 参数提供的值是 <code>Guess::new</code> 函数 panic 信息的子串。我们可以指定期望的整个 panic 信息,在这个例子中是 <code>Guess value must be less than or equal to 100, got 200</code> 。信息的选择取决于 panic 信息有多独特或动态,和你希望测试有多准确。在这个例子中,错误信息的子字符串足以确保函数在 <code>else if value > 100</code> 的情况下运行。</p>
|
|
|
<p>为了观察带有 <code>expected</code> 信息的 <code>should_panic</code> 测试失败时会发生什么,让我们再次引入一个 bug,将 <code>if value < 1</code> 和 <code>else if value > 100</code> 的代码块对换:</p>
|
|
|
<pre><code class="language-rust ignore not_desired_behavior"><span class="boring">pub struct Guess {
|
|
|
</span><span class="boring"> value: i32,
|
|
|
</span><span class="boring">}
|
|
|
</span><span class="boring">
|
|
|
</span><span class="boring">impl Guess {
|
|
|
</span><span class="boring"> pub fn new(value: i32) -> Guess {
|
|
|
</span> if value < 1 {
|
|
|
panic!(
|
|
|
"Guess value must be less than or equal to 100, got {value}."
|
|
|
);
|
|
|
} else if value > 100 {
|
|
|
panic!(
|
|
|
"Guess value must be greater than or equal to 1, got {value}."
|
|
|
);
|
|
|
}
|
|
|
<span class="boring">
|
|
|
</span><span class="boring"> Guess { value }
|
|
|
</span><span class="boring"> }
|
|
|
</span><span class="boring">}
|
|
|
</span><span class="boring">
|
|
|
</span><span class="boring">#[cfg(test)]
|
|
|
</span><span class="boring">mod tests {
|
|
|
</span><span class="boring"> use super::*;
|
|
|
</span><span class="boring">
|
|
|
</span><span class="boring"> #[test]
|
|
|
</span><span class="boring"> #[should_panic(expected = "less than or equal to 100")]
|
|
|
</span><span class="boring"> fn greater_than_100() {
|
|
|
</span><span class="boring"> Guess::new(200);
|
|
|
</span><span class="boring"> }
|
|
|
</span><span class="boring">}</span></code></pre>
|
|
|
<p>这一次运行 <code>should_panic</code> 测试,它会失败:</p>
|
|
|
<pre><code class="language-console">$ cargo test
|
|
|
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
|
|
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.66s
|
|
|
Running unittests src/lib.rs (target/debug/deps/guessing_game-57d70c3acb738f4d)
|
|
|
|
|
|
running 1 test
|
|
|
test tests::greater_than_100 - should panic ... FAILED
|
|
|
|
|
|
failures:
|
|
|
|
|
|
---- tests::greater_than_100 stdout ----
|
|
|
|
|
|
thread 'tests::greater_than_100' panicked at src/lib.rs:12:13:
|
|
|
Guess value must be greater than or equal to 1, got 200.
|
|
|
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
|
|
note: panic did not contain expected string
|
|
|
panic message: `"Guess value must be greater than or equal to 1, got 200."`,
|
|
|
expected substring: `"less than or equal to 100"`
|
|
|
|
|
|
failures:
|
|
|
tests::greater_than_100
|
|
|
|
|
|
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
|
|
|
|
|
error: test failed, to rerun pass `--lib`
|
|
|
</code></pre>
|
|
|
<p>失败信息表明测试确实如期望 panic 了,不过 panic 信息中并没有包含期望的信息 <code>less than or equal to 100</code>。而我们得到的 panic 信息是 <code>Guess value must be greater than or equal to 1, got 200.</code>。这样就可以开始寻找 bug 在哪了!</p>
|
|
|
<h3 id="在测试中使用-resultt-e"><a class="header" href="#在测试中使用-resultt-e">在测试中使用 <code>Result<T, E></code></a></h3>
|
|
|
<p>目前为止,我们编写的测试在失败时都会 panic。我们也可以使用 <code>Result<T, E></code> 编写测试!这是一个延伸自示例 11-1 的测试,使用 <code>Result<T, E></code> 重写,并在失败时返回 <code>Err</code> 而非 panic:</p>
|
|
|
<pre><code class="language-rust noplayground"><span class="boring">pub fn add(left: u64, right: u64) -> u64 {
|
|
|
</span><span class="boring"> left + right
|
|
|
</span><span class="boring">}
|
|
|
</span><span class="boring">
|
|
|
</span><span class="boring">#[cfg(test)]
|
|
|
</span><span class="boring">mod tests {
|
|
|
</span><span class="boring"> use super::*;
|
|
|
</span><span class="boring">
|
|
|
</span> #[test]
|
|
|
fn it_works() -> Result<(), String> {
|
|
|
let result = add(2, 2);
|
|
|
|
|
|
if result == 4 {
|
|
|
Ok(())
|
|
|
} else {
|
|
|
Err(String::from("two plus two does not equal four"))
|
|
|
}
|
|
|
}
|
|
|
<span class="boring">}</span></code></pre>
|
|
|
<p>现在 <code>it_works</code> 函数的返回值类型为 <code>Result<(), String></code>。在函数体中,不同于调用 <code>assert_eq!</code> 宏,而是在测试通过时返回 <code>Ok(())</code>,在测试失败时返回带有 <code>String</code> 的 <code>Err</code>。</p>
|
|
|
<p>这样编写测试来返回 <code>Result<T, E></code> 就可以在函数体中使用问号运算符,如此可以方便的编写任何会返回 <code>Err</code> 变体的操作的测试。</p>
|
|
|
<p>不能对这些使用 <code>Result<T, E></code> 的测试使用 <code>#[should_panic]</code> 注解。为了断言一个操作返回 <code>Err</code> 成员,<strong>不要</strong>对 <code>Result<T, E></code> 值使用问号表达式(<code>?</code>)。而是使用 <code>assert!(value.is_err())</code>。</p>
|
|
|
<p>现在你知道了几种编写测试的方法,让我们看看运行测试时会发生什么,以及可以用于 <code>cargo test</code> 的不同选项。</p>
|
|
|
|
|
|
</main>
|
|
|
|
|
|
<nav class="nav-wrapper" aria-label="Page navigation">
|
|
|
<!-- Mobile navigation buttons -->
|
|
|
<a rel="prev" href="ch11-00-testing.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|
|
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg></span>
|
|
|
</a>
|
|
|
|
|
|
<a rel="next prefetch" href="ch11-02-running-tests.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
|
|
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M278.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-160 160c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L210.7 256 73.4 118.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l160 160z"/></svg></span>
|
|
|
</a>
|
|
|
|
|
|
<div style="clear: both"></div>
|
|
|
</nav>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
|
|
<a rel="prev" href="ch11-00-testing.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|
|
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg></span>
|
|
|
</a>
|
|
|
|
|
|
<a rel="next prefetch" href="ch11-02-running-tests.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
|
|
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M278.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-160 160c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L210.7 256 73.4 118.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l160 160z"/></svg></span>
|
|
|
</a>
|
|
|
</nav>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<template id=fa-eye><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM432 256c0 79.5-64.5 144-144 144s-144-64.5-144-144s64.5-144 144-144s144 64.5 144 144zM288 192c0 35.3-28.7 64-64 64c-11.5 0-22.3-3-31.6-8.4c-.2 2.8-.4 5.5-.4 8.4c0 53 43 96 96 96s96-43 96-96s-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6z"/></svg></span></template>
|
|
|
<template id=fa-eye-slash><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M38.8 5.1C28.4-3.1 13.3-1.2 5.1 9.2S-1.2 34.7 9.2 42.9l592 464c10.4 8.2 25.5 6.3 33.7-4.1s6.3-25.5-4.1-33.7L525.6 386.7c39.6-40.6 66.4-86.1 79.9-118.4c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C465.5 68.8 400.8 32 320 32c-68.2 0-125 26.3-169.3 60.8L38.8 5.1zM223.1 149.5C248.6 126.2 282.7 112 320 112c79.5 0 144 64.5 144 144c0 24.9-6.3 48.3-17.4 68.7L408 294.5c5.2-11.8 8-24.8 8-38.5c0-53-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6c0 10.2-2.4 19.8-6.6 28.3l-90.3-70.8zm223.1 298L373 389.9c-16.4 6.5-34.3 10.1-53 10.1c-79.5 0-144-64.5-144-144c0-6.9 .5-13.6 1.4-20.2L83.1 161.5C60.3 191.2 44 220.8 34.5 243.7c-3.3 7.9-3.3 16.7 0 24.6c14.9 35.7 46.2 87.7 93 131.1C174.5 443.2 239.2 480 320 480c47.8 0 89.9-12.9 126.2-32.5z"/></svg></span></template>
|
|
|
<template id=fa-copy><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M502.6 70.63l-61.25-61.25C435.4 3.371 427.2 0 418.7 0H255.1c-35.35 0-64 28.66-64 64l.0195 256C192 355.4 220.7 384 256 384h192c35.2 0 64-28.8 64-64V93.25C512 84.77 508.6 76.63 502.6 70.63zM464 320c0 8.836-7.164 16-16 16H255.1c-8.838 0-16-7.164-16-16L239.1 64.13c0-8.836 7.164-16 16-16h128L384 96c0 17.67 14.33 32 32 32h47.1V320zM272 448c0 8.836-7.164 16-16 16H63.1c-8.838 0-16-7.164-16-16L47.98 192.1c0-8.836 7.164-16 16-16H160V128H63.99c-35.35 0-64 28.65-64 64l.0098 256C.002 483.3 28.66 512 64 512h192c35.2 0 64-28.8 64-64v-32h-47.1L272 448z"/></svg></span></template>
|
|
|
<template id=fa-play><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80V432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z"/></svg></span></template>
|
|
|
<template id=fa-clock-rotate-left><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M75 75L41 41C25.9 25.9 0 36.6 0 57.9V168c0 13.3 10.7 24 24 24H134.1c21.4 0 32.1-25.9 17-41l-30.8-30.8C155 85.5 203 64 256 64c106 0 192 86 192 192s-86 192-192 192c-40.8 0-78.6-12.7-109.7-34.4c-14.5-10.1-34.4-6.6-44.6 7.9s-6.6 34.4 7.9 44.6C151.2 495 201.7 512 256 512c141.4 0 256-114.6 256-256S397.4 0 256 0C185.3 0 121.3 28.7 75 75zm181 53c-13.3 0-24 10.7-24 24V256c0 6.4 2.5 12.5 7 17l72 72c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-65-65V152c0-13.3-10.7-24-24-24z"/></svg></span></template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
window.playground_copyable = true;
|
|
|
</script>
|
|
|
|
|
|
|
|
|
<script src="elasticlunr-ef4e11c1.min.js"></script>
|
|
|
<script src="mark-09e88c2c.min.js"></script>
|
|
|
<script src="searcher-c2a407aa.js"></script>
|
|
|
|
|
|
<script src="clipboard-1626706a.min.js"></script>
|
|
|
<script src="highlight-abc7f01d.js"></script>
|
|
|
<script src="book-a0b12cfe.js"></script>
|
|
|
|
|
|
<!-- Custom JS scripts -->
|
|
|
<script src="ferris-2317480c.js"></script>
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
</body>
|
|
|
</html>
|