mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-09 08:51:18 +08:00
283 lines
21 KiB
HTML
283 lines
21 KiB
HTML
<!DOCTYPE HTML>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>编写测试 - Rust 程序设计语言 简体中文版</title>
|
||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||
<meta name="description" content="Rust 程序设计语言 简体中文版">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
|
||
<base href="">
|
||
|
||
<link rel="stylesheet" href="book.css">
|
||
<link href='https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800' rel='stylesheet' type='text/css'>
|
||
|
||
<link rel="shortcut icon" href="favicon.png">
|
||
|
||
<!-- Font Awesome -->
|
||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
|
||
|
||
<link rel="stylesheet" href="highlight.css">
|
||
<link rel="stylesheet" href="tomorrow-night.css">
|
||
|
||
<!-- MathJax -->
|
||
<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
||
|
||
<!-- Fetch JQuery from CDN but have a local fallback -->
|
||
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
|
||
<script>
|
||
if (typeof jQuery == 'undefined') {
|
||
document.write(unescape("%3Cscript src='jquery.js'%3E%3C/script%3E"));
|
||
}
|
||
</script>
|
||
</head>
|
||
<body class="light">
|
||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||
<script type="text/javascript">
|
||
var theme = localStorage.getItem('theme');
|
||
if (theme == null) { theme = 'light'; }
|
||
$('body').removeClass().addClass(theme);
|
||
</script>
|
||
|
||
<!-- Hide / unhide sidebar before it is displayed -->
|
||
<script type="text/javascript">
|
||
var sidebar = localStorage.getItem('sidebar');
|
||
if (sidebar === "hidden") { $("html").addClass("sidebar-hidden") }
|
||
else if (sidebar === "visible") { $("html").addClass("sidebar-visible") }
|
||
</script>
|
||
|
||
<div id="sidebar" class="sidebar">
|
||
<ul class="chapter"><li><a href="ch01-00-introduction.html"><strong>1.</strong> 介绍</a></li><li><ul class="section"><li><a href="ch01-01-installation.html"><strong>1.1.</strong> 安装</a></li><li><a href="ch01-02-hello-world.html"><strong>1.2.</strong> Hello, World!</a></li></ul></li><li><a href="ch02-00-guessing-game-tutorial.html"><strong>2.</strong> 猜猜看教程</a></li><li><a href="ch03-00-common-programming-concepts.html"><strong>3.</strong> 通用编程概念</a></li><li><ul class="section"><li><a href="ch03-01-variables-and-mutability.html"><strong>3.1.</strong> 变量和可变性</a></li><li><a href="ch03-02-data-types.html"><strong>3.2.</strong> 数据类型</a></li><li><a href="ch03-03-how-functions-work.html"><strong>3.3.</strong> 函数如何工作</a></li><li><a href="ch03-04-comments.html"><strong>3.4.</strong> 注释</a></li><li><a href="ch03-05-control-flow.html"><strong>3.5.</strong> 控制流</a></li></ul></li><li><a href="ch04-00-understanding-ownership.html"><strong>4.</strong> 认识所有权</a></li><li><ul class="section"><li><a href="ch04-01-what-is-ownership.html"><strong>4.1.</strong> 什么是所有权</a></li><li><a href="ch04-02-references-and-borrowing.html"><strong>4.2.</strong> 引用 & 借用</a></li><li><a href="ch04-03-slices.html"><strong>4.3.</strong> Slices</a></li></ul></li><li><a href="ch05-00-structs.html"><strong>5.</strong> 结构体</a></li><li><ul class="section"><li><a href="ch05-01-method-syntax.html"><strong>5.1.</strong> 方法语法</a></li></ul></li><li><a href="ch06-00-enums.html"><strong>6.</strong> 枚举和模式匹配</a></li><li><ul class="section"><li><a href="ch06-01-defining-an-enum.html"><strong>6.1.</strong> 定义枚举</a></li><li><a href="ch06-02-match.html"><strong>6.2.</strong> <code>match</code>控制流运算符</a></li><li><a href="ch06-03-if-let.html"><strong>6.3.</strong> <code>if let</code>简单控制流</a></li></ul></li><li><a href="ch07-00-modules.html"><strong>7.</strong> 模块</a></li><li><ul class="section"><li><a href="ch07-01-mod-and-the-filesystem.html"><strong>7.1.</strong> <code>mod</code>和文件系统</a></li><li><a href="ch07-02-controlling-visibility-with-pub.html"><strong>7.2.</strong> 使用<code>pub</code>控制可见性</a></li><li><a href="ch07-03-importing-names-with-use.html"><strong>7.3.</strong> 使用<code>use</code>导入命名</a></li></ul></li><li><a href="ch08-00-common-collections.html"><strong>8.</strong> 通用集合类型</a></li><li><ul class="section"><li><a href="ch08-01-vectors.html"><strong>8.1.</strong> vector</a></li><li><a href="ch08-02-strings.html"><strong>8.2.</strong> 字符串</a></li><li><a href="ch08-03-hash-maps.html"><strong>8.3.</strong> 哈希 map</a></li></ul></li><li><a href="ch09-00-error-handling.html"><strong>9.</strong> 错误处理</a></li><li><ul class="section"><li><a href="ch09-01-unrecoverable-errors-with-panic.html"><strong>9.1.</strong> <code>panic!</code>与不可恢复的错误</a></li><li><a href="ch09-02-recoverable-errors-with-result.html"><strong>9.2.</strong> <code>Result</code>与可恢复的错误</a></li><li><a href="ch09-03-to-panic-or-not-to-panic.html"><strong>9.3.</strong> <code>panic!</code>还是不<code>panic!</code></a></li></ul></li><li><a href="ch10-00-generics.html"><strong>10.</strong> 泛型、trait 和生命周期</a></li><li><ul class="section"><li><a href="ch10-01-syntax.html"><strong>10.1.</strong> 泛型数据类型</a></li><li><a href="ch10-02-traits.html"><strong>10.2.</strong> trait:定义共享的行为</a></li><li><a href="ch10-03-lifetime-syntax.html"><strong>10.3.</strong> 生命周期与引用有效性</a></li></ul></li><li><a href="ch11-00-testing.html"><strong>11.</strong> 测试</a></li><li><ul class="section"><li><a href="ch11-01-writing-tests.html" class="active"><strong>11.1.</strong> 编写测试</a></li><li><a href="ch11-02-running-tests.html"><strong>11.2.</strong> 运行测试</a></li><li><a href="ch11-03-test-organization.html"><strong>11.3.</strong> 测试的组织结构</a></li></ul></li><li><a href="ch12-00-an-io-project.html"><strong>12.</strong> 一个 I/O 项目</a></li><li><ul class="section"><li><a href="ch12-01-accepting-command-line-arguments.html"><strong>12.1.</strong> 接受命令行参数</a></li><li><a href="ch12-02-reading-a-file.html"><strong>12.2.</strong> 读取文件</a></li><li><a href="ch12-03-improving-error-handling-and-modularity.html"><strong>12.3.</strong> 增强错误处理和模块化</a></li><li><a href="ch12-04-testing-the-librarys-functionality.html"><strong>12.4.</strong> 测试库的功能</a></li><li><a href="ch12-05-working-with-environment-variables.html"><strong>12.5.</strong> 处理环境变量</a></li><li><a href="ch12-06-writing-to-stderr-instead-of-stdout.html"><strong>12.6.</strong> 输出到<code>stderr</code>而不是<code>stdout</code></a></li></ul></li><li><a href="ch13-00-functional-features.html"><strong>13.</strong> Rust 中的函数式语言功能</a></li><li><ul class="section"><li><a href="ch13-01-closures.html"><strong>13.1.</strong> 闭包</a></li><li><a href="ch13-02-iterators.html"><strong>13.2.</strong> 迭代器</a></li><li><a href="ch13-03-improving-our-io-project.html"><strong>13.3.</strong> 改进 I/O 项目</a></li><li><a href="ch13-04-performance.html"><strong>13.4.</strong> 性能</a></li></ul></li><li><a href="ch14-00-more-about-cargo.html"><strong>14.</strong> 更多关于 Cargo 和 Crates.io</a></li><li><ul class="section"><li><a href="ch14-01-release-profiles.html"><strong>14.1.</strong> 发布配置</a></li><li><a href="ch14-02-publishing-to-crates-io.html"><strong>14.2.</strong> 将 crate 发布到 Crates.io</a></li><li><a href="ch14-03-cargo-workspaces.html"><strong>14.3.</strong> Cargo 工作空间</a></li><li><a href="ch14-04-installing-binaries.html"><strong>14.4.</strong> 使用<code>cargo install</code>从 Crates.io 安装文件</a></li><li><a href="ch14-05-extending-cargo.html"><strong>14.5.</strong> Cargo 自定义扩展命令</a></li></ul></li><li><a href="ch15-00-smart-pointers.html"><strong>15.</strong> 智能指针</a></li><li><ul class="section"><li><a href="ch15-01-box.html"><strong>15.1.</strong> <code>Box<T></code>用于已知大小的堆上数据</a></li><li><a href="ch15-02-deref.html"><strong>15.2.</strong> <code>Deref</code> Trait 允许通过引用访问数据</a></li><li><a href="ch15-03-drop.html"><strong>15.3.</strong> <code>Drop</code> Trait 运行清理代码</a></li><li><a href="ch15-04-rc.html"><strong>15.4.</strong> <code>Rc<T></code> 引用计数智能指针</a></li><li><a href="ch15-05-interior-mutability.html"><strong>15.5.</strong> <code>RefCell<T></code>和内部可变性模式</a></li><li><a href="ch15-06-reference-cycles.html"><strong>15.6.</strong> 引用循环和内存泄漏是安全的</a></li></ul></li><li><a href="ch16-00-concurrency.html"><strong>16.</strong> 无畏并发</a></li><li><ul class="section"><li><a href="ch16-01-threads.html"><strong>16.1.</strong> 线程</a></li><li><a href="ch16-02-message-passing.html"><strong>16.2.</strong> 消息传递</a></li><li><a href="ch16-03-shared-state.html"><strong>16.3.</strong> 共享状态</a></li><li><a href="ch16-04-extensible-concurrency-sync-and-send.html"><strong>16.4.</strong> 可扩展的并发:<code>Sync</code>和<code>Send</code></a></li></ul></li></ul>
|
||
</div>
|
||
|
||
<div id="page-wrapper" class="page-wrapper">
|
||
|
||
<div class="page">
|
||
<div id="menu-bar" class="menu-bar">
|
||
<div class="left-buttons">
|
||
<i id="sidebar-toggle" class="fa fa-bars"></i>
|
||
<i id="theme-toggle" class="fa fa-paint-brush"></i>
|
||
</div>
|
||
|
||
<h1 class="menu-title">Rust 程序设计语言 简体中文版</h1>
|
||
|
||
<div class="right-buttons">
|
||
<i id="print-button" class="fa fa-print" title="Print this book"></i>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="content" class="content">
|
||
<a class="header" href="#编写测试" name="编写测试"><h2>编写测试</h2></a>
|
||
<blockquote>
|
||
<p><a href="https://github.com/rust-lang/book/blob/master/src/ch11-01-writing-tests.md">ch11-01-writing-tests.md</a>
|
||
<br>
|
||
commit 77370c073661548dd56bbcb43cc64713585acbba</p>
|
||
</blockquote>
|
||
<p>测试是一种使用特定功能的 Rust 函数,它用来验证非测试的代码按照期望的方式运行。我们讨论过的任何 Rust 代码规则都适用于测试!让我们看看 Rust 提供的具体用来编写测试的功能:<code>test</code>属性、一些宏和<code>should_panic</code>属性。</p>
|
||
<a class="header" href="#test属性" name="test属性"><h3><code>test</code>属性</h3></a>
|
||
<p>作为最简单例子,Rust 中的测试就是一个带有<code>test</code>属性注解的函数。让我们使用 Cargo 来创建一个新的库项目<code>adder</code>:</p>
|
||
<pre><code>$ cargo new adder
|
||
Created library `adder` project
|
||
$ cd adder
|
||
</code></pre>
|
||
<p>Cargo 在创建新的库项目时自动生成一个简单的测试。这是<code>src/lib.rs</code>中的内容:</p>
|
||
<p><span class="filename">Filename: src/lib.rs</span></p>
|
||
<pre><code class="language-rust">#[cfg(test)]
|
||
mod tests {
|
||
#[test]
|
||
fn it_works() {
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>现在让我们暂时忽略<code>tests</code>模块和<code>#[cfg(test)]</code>注解并只关注函数。注意它之前的<code>#[test]</code>:这个属性表明这是一个测试函数。这个函数目前没有任何内容,所以绝对是可以通过的!使用<code>cargo test</code>来运行测试:</p>
|
||
<pre><code>$ cargo test
|
||
Compiling adder v0.1.0 (file:///projects/adder)
|
||
Finished debug [unoptimized + debuginfo] target(s) in 0.22 secs
|
||
Running target/debug/deps/adder-abcabcabc
|
||
|
||
running 1 test
|
||
test it_works ... ok
|
||
|
||
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
|
||
|
||
Doc-tests adder
|
||
|
||
running 0 tests
|
||
|
||
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
|
||
</code></pre>
|
||
<p>Cargo 编译并运行了测试。这里有两部分输出:本章我们将关注第一部分。第二部分是文档测试的输出,第十四章会介绍他们。现在注意看这一行:</p>
|
||
<pre><code class="language-text">test it_works ... ok
|
||
</code></pre>
|
||
<p><code>it_works</code>文本来源于测试函数的名称。</p>
|
||
<p>这里也有一行总结告诉我们所有测试的聚合结果:</p>
|
||
<pre><code>test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
|
||
</code></pre>
|
||
<a class="header" href="#assert宏" name="assert宏"><h3><code>assert!</code>宏</h3></a>
|
||
<p>空的测试函数之所以能通过是因为任何没有<code>panic!</code>的测试都是通过的,而任何<code>panic!</code>的测试都算是失败。让我们使用`assert!宏来使测试失败:</p>
|
||
<p><span class="filename">Filename: src/lib.rs</span></p>
|
||
<pre><code class="language-rust">#[test]
|
||
fn it_works() {
|
||
assert!(false);
|
||
}
|
||
</code></pre>
|
||
<p><code>assert!</code>宏由标准库提供,它获取一个参数,如果参数是<code>true</code>,什么也不会发生。如果参数是<code>false</code>,这个宏会<code>panic!</code>。再次运行测试:</p>
|
||
<pre><code>$ cargo test
|
||
Compiling adder v0.1.0 (file:///projects/adder)
|
||
Finished debug [unoptimized + debuginfo] target(s) in 0.22 secs
|
||
Running target/debug/deps/adder-abcabcabc
|
||
|
||
running 1 test
|
||
test it_works ... FAILED
|
||
|
||
failures:
|
||
|
||
---- it_works stdout ----
|
||
thread 'it_works' panicked at 'assertion failed: false', src/lib.rs:5
|
||
note: Run with `RUST_BACKTRACE=1` for a backtrace.
|
||
|
||
|
||
failures:
|
||
it_works
|
||
|
||
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured
|
||
|
||
error: test failed
|
||
</code></pre>
|
||
<p>Rust 表明测试失败了:</p>
|
||
<pre><code>test it_works ... FAILED
|
||
</code></pre>
|
||
<p>并展示了测试是因为src/lib.rs<code>的第 5 行</code>assert!<code>宏得到了一个</code>false`值而失败的:</p>
|
||
<pre><code>thread 'it_works' panicked at 'assertion failed: false', src/lib.rs:5
|
||
</code></pre>
|
||
<p>失败的测试也体现在了总结行中:</p>
|
||
<pre><code>test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured
|
||
</code></pre>
|
||
<a class="header" href="#使用assert_eq和assert_ne宏来测试相等" name="使用assert_eq和assert_ne宏来测试相等"><h3>使用<code>assert_eq!</code>和<code>assert_ne!</code>宏来测试相等</h3></a>
|
||
<p>测试功能的一个常用方法是将需要测试代码的值与期望值做比较,并检查是否相等。可以通过向<code>assert!</code>宏传递一个使用<code>==</code>宏的表达式来做到。不过这个操作实在是太常见了,以至于标注库提供了一对宏来编译处理这些操作:<code>assert_eq!</code>和<code>assert_ne!</code>。这两个宏分别比较两个值是相等还是不相等。使用这些宏的另一个优势是当断言失败时他们会打印出这两个值具体是什么,以便于观察测试<strong>为什么</strong>失败,而<code>assert!</code>只会打印出它从<code>==</code>表达式中得到了<code>false</code>值。</p>
|
||
<p>下面是分别使用这两个宏其会测试通过的例子:</p>
|
||
<p><span class="filename">Filename: src/lib.rs</span></p>
|
||
<pre><code>#[test]
|
||
fn it_works() {
|
||
assert_eq!("Hello", "Hello");
|
||
|
||
assert_ne!("Hello", "world");
|
||
}
|
||
</code></pre>
|
||
<p>也可以对这些宏指定可选的第三个参数,它是一个会加入错误信息的自定义文本。这两个宏展开后的逻辑看起来像这样:</p>
|
||
<pre><code class="language-rust,ignore">// assert_eq! - panic if the values aren't equal
|
||
if left_val != right_val {
|
||
panic!(
|
||
"assertion failed: `(left == right)` (left: `{:?}`, right: `{:?}`): {}"
|
||
left_val,
|
||
right_val,
|
||
optional_custom_message
|
||
)
|
||
}
|
||
|
||
// assert_ne! - panic if the values are equal
|
||
if left_val == right_val {
|
||
panic!(
|
||
"assertion failed: `(left != right)` (left: `{:?}`, right: `{:?}`): {}"
|
||
left_val,
|
||
right_val,
|
||
optional_custom_message
|
||
)
|
||
}
|
||
</code></pre>
|
||
<p>看看这个因为<code>hello</code>不等于<code>world</code>而失败的测试。我们还增加了一个自定义的错误信息,<code>greeting operation failed</code>:</p>
|
||
<p><span class="filename">Filename: src/lib.rs</span></p>
|
||
<pre><code class="language-rust">#[test]
|
||
fn a_simple_case() {
|
||
let result = "hello"; // this value would come from running your code
|
||
assert_eq!(result, "world", "greeting operation failed");
|
||
}
|
||
</code></pre>
|
||
<p>毫无疑问运行这个测试会失败,而错误信息解释了为什么测试失败了并且带有我们的指定的自定义错误信息:</p>
|
||
<pre><code class="language-text">---- a_simple_case stdout ----
|
||
thread 'a_simple_case' panicked at 'assertion failed: `(left == right)`
|
||
(left: `"hello"`, right: `"world"`): greeting operation failed',
|
||
src/main.rs:4
|
||
</code></pre>
|
||
<p><code>assert_eq!</code>的两个参数被称为 "left" 和 "right" ,而不是 "expected" 和 "actual" ;值的顺序和硬编码的值并没有什么影响。</p>
|
||
<p>因为这些宏使用了<code>==</code>和<code>!=</code>运算符并使用调试格式打印这些值,进行比较的值必须实现<code>PartialEq</code>和<code>Debug</code> trait。Rust 提供的类型实现了这些 trait,不过自定义的结构体和枚举则需要自己实现<code>PartialEq</code>以便能够断言这些值是否相等,和实现<code>Debug</code>以便在断言失败时打印出这些值。因为第五章提到过这两个 trait 都是 derivable trait,所以通常可以直接在结构体或枚举上加上<code>#[derive(PartialEq, Debug)]</code>注解。查看附录 C 来寻找更多关于这些和其他 derivable trait 的信息。</p>
|
||
<a class="header" href="#使用should_panic测试期望的失败" name="使用should_panic测试期望的失败"><h2>使用<code>should_panic</code>测试期望的失败</h2></a>
|
||
<p>可以使用另一个属性来反转测试中的失败:<code>should_panic</code>。这在测试调用特定的函数会产生错误的函数时很有帮助。例如,让我们测试第八章中的一些我们知道会 panic 的代码:尝试使用 range 语法和并不组成完整字母的字节索引来创建一个字符串 slice。在有<code>#[test]</code>属性的函数之前增加<code>#[should_panic]</code>属性,如列表 11-1 所示:</p>
|
||
<figure>
|
||
<span class="filename">Filename: src/lib.rs</span>
|
||
<pre><code class="language-rust">#[test]
|
||
#[should_panic]
|
||
fn slice_not_on_char_boundaries() {
|
||
let s = "Здравствуйте";
|
||
&s[0..1];
|
||
}
|
||
</code></pre>
|
||
<figcaption>
|
||
<p>Listing 11-1: A test expecting a <code>panic!</code></p>
|
||
</figcaption>
|
||
</figure>
|
||
<p>这个测试是成功的,因为我们表示代码应该会 panic。相反如果代码因为某种原因没有产生<code>panic!</code>则测试会失败。</p>
|
||
<p>使用<code>should_panic</code>的测试是脆弱的,因为难以保证测试不会因为一个不同于我们期望的原因失败。为了帮助解决这个问题,<code>should_panic</code>属性可以增加一个可选的<code>expected</code>参数。测试工具会确保错误信息里包含我们提供的文本。一个比列表 11-1 更健壮的版本如列表 11-2 所示:</p>
|
||
<figure>
|
||
<span class="filename">Filename: src/lib.rs</span>
|
||
<pre><code class="language-rust">#[test]
|
||
#[should_panic(expected = "do not lie on character boundary")]
|
||
fn slice_not_on_char_boundaries() {
|
||
let s = "Здравствуйте";
|
||
&s[0..1];
|
||
}
|
||
</code></pre>
|
||
<!-- I will add ghosting in libreoffice /Carol -->
|
||
<figcaption>
|
||
<p>Listing 11-2: A test expecting a <code>panic!</code> with a particular message</p>
|
||
</figcaption>
|
||
</figure>
|
||
<p>请自行尝试当<code>should_panic</code>的测试出现 panic 但并不符合期望的信息时会发生什么:在测试中因为不同原因造成<code>panic!</code>,或者将期望的 panic 信息改为并不与字母字节边界 panic 信息相匹配。</p>
|
||
|
||
</div>
|
||
|
||
<!-- Mobile navigation buttons -->
|
||
|
||
<a href="ch11-00-testing.html" class="mobile-nav-chapters previous">
|
||
<i class="fa fa-angle-left"></i>
|
||
</a>
|
||
|
||
|
||
|
||
<a href="ch11-02-running-tests.html" class="mobile-nav-chapters next">
|
||
<i class="fa fa-angle-right"></i>
|
||
</a>
|
||
|
||
|
||
</div>
|
||
|
||
|
||
<a href="ch11-00-testing.html" class="nav-chapters previous" title="You can navigate through the chapters using the arrow keys">
|
||
<i class="fa fa-angle-left"></i>
|
||
</a>
|
||
|
||
|
||
|
||
<a href="ch11-02-running-tests.html" class="nav-chapters next" title="You can navigate through the chapters using the arrow keys">
|
||
<i class="fa fa-angle-right"></i>
|
||
</a>
|
||
|
||
|
||
</div>
|
||
|
||
|
||
<!-- Local fallback for Font Awesome -->
|
||
<script>
|
||
if ($(".fa").css("font-family") !== "FontAwesome") {
|
||
$('<link rel="stylesheet" type="text/css" href="_FontAwesome/css/font-awesome.css">').prependTo('head');
|
||
}
|
||
</script>
|
||
|
||
<!-- Livereload script (if served using the cli tool) -->
|
||
|
||
|
||
<script src="highlight.js"></script>
|
||
<script src="book.js"></script>
|
||
</body>
|
||
</html>
|