96SEO 2026-02-19 09:33 2
Dijkstra在他1972年的文章《谦逊的程序员》中说“程序测试可以是一种非常有效的方法来显示错误的存在但它对于显示它们的不存在是完全不够的。
”这并不意味着我们不应该尽可能多地尝试测试
我们程序的正确性是指我们的代码在多大程度上做了我们想要它做的事情。
Rust的设计高度关注程序的正确性但正确性很复杂不容易证明。
Rust的类型系统承担了这个负担的很大一部分但是类型系统不能捕获所有的东西。
因此Rust包含了对编写自动化软件测试的支持。
假设我们编写了一个函数add_two它将传递给它的任何数字加2。
这个函数的签名接受一个整数作为参数并返回一个整数作为结果。
当我们实现和编译这个函数时Rust会进行所有的类型检查和借用检查就像你到目前为止所学的那样以确保我们不会向这个函数传递一个String值或一个无效的引用。
但是Rust不能检查这个函数是否能准确地达到我们的目的即返回参数加2而不是参数加10或参数减50这就是测试的由来。
我们可以编写一些测试来断言例如当我们将3传递给add_two函数时返回值是5。
每当我们对代码进行更改时我们都可以运行这些测试以确保任何现有的正确行为都没有改变。
测试是一项复杂的技能:虽然我们无法在一章中涵盖如何编写好的测试的每个细节但我们将讨论Rust测试工具的机制。
我们将讨论编写测试时可用的注释和宏为运行测试提供的默认行为和选项以及如何将测试组织成单元测试和集成测试。
测试是Rust函数它验证非测试代码是否以预期的方式运行。
测试函数的主体通常执行这三个动作:
设置任何需要的数据或状态。
运行您想要测试的代码。
断言结果是你所期望的。
让我们来看看Rust专门为编写执行这些操作的测试提供的特性包括test属性、一些宏和should_panic属性。
最简单地说Rust中的测试是一个用test属性注释的函数。
属性是关于Rust代码片段的元数据一个例子是我们在第5章中对结构使用的derive属性。
要将函数更改为测试函数请在fn之前的行中添加#[test]。
当您使用cargo
test命令运行您的测试时Rust会构建一个测试运行二进制文件运行带注释的函数并报告每个测试函数是通过还是失败。
每当我们用Cargo创建一个新的库项目时就会自动为我们生成一个包含测试功能的测试模块。
这个模块为您提供了一个编写测试的模板这样您就不必在每次开始一个新项目时都去查找精确的结构和语法。
您可以添加任意多的额外测试函数和测试模块
在实际测试任何代码之前我们将通过模板测试来探索测试工作的某些方面。
然后我们将编写一些真实世界的测试调用我们编写的一些代码并断言其行为是正确的。
adderadder库中src/lib.rs文件的内容应该如示例11-1所示。
现在让我们忽略上面两行把注意力集中在函数上。
注意#[test]注释:这个属性表明这是一个测试函数所以测试运行人员知道将这个函数视为一个测试。
在tests模块中我们还可能有非测试函数来帮助设置常见的场景或执行常见的操作所以我们总是需要指出哪些函数是测试。
示例函数体使用了assert_eq宏来断言包含2和2相加result的结果等于4。
这个断言作为一个典型测试格式的例子。
让我们运行它来看看这个测试是否通过。
(file:///projects/adder)Finished
(target/debug/deps/adder-92948b65e88960b4)running
test的行。
下一行显示了生成的测试函数的名称名为it_works运行该测试的结果是ok的。
总体总结test
measured测量统计值用于测量性能的基准测试。
在撰写本文时基准测试只在夜间Rust中可用。
adder开始的测试输出的下一部分是任何文档测试的结果。
我们还没有任何文档测试但Rust可以编译任何出现在我们API文档中的代码示例。
这个特性有助于保持您的文档和代码同步现在我们将忽略Doc-tests输出。
让我们开始根据自己的需要定制测试。
首先将it_works函数的名称改为不同的名称例如exploration如下所示:
test。
现在输出显示的是exploration而不是it_works:
(file:///projects/adder)Finished
(target/debug/deps/adder-92948b65e88960b4)running
现在我们将添加另一个测试但这次我们将做一个失败的测试当测试函数出现问题时测试就会失败。
每个测试都在一个新线程中运行当主线程发现一个测试线程已经死亡时该测试就会被标记为失败。
在第9章中我们谈到了恐慌的最简单的方法是如何打电话给panic!宏观。
输入新的测试作为一个名为another的函数这样你的src/lib.rs文件看起来如示例11-3所示。
示例11-3:添加第二个测试该测试将失败因为我们称之为panic!宏指令
test再次运行测试。
输出应该如示例11-4所示这表明我们的exploration测试通过了而another测试失败了。
(file:///projects/adder)Finished
(target/debug/deps/adder-92948b65e88960b4)running
backtracefailures:tests::anothertest
--lib示例11-4:当一个测试通过而另一个测试失败时的测试结果
tests::another显示FAILED而不是ok。
在单独的结果和总结之间出现两个新的部分:第一部分显示每个测试失败的详细原因。
在这种情况下我们获得another失败的详细信息因为它在src/lib.rs文件的第10行panicked
总结行显示在最后:总的来说我们的测试结果是FAILED的。
我们有一个测试通过一个测试失败。
现在您已经看到了不同场景下的测试结果让我们来看看除了panic!之外的一些宏在测试中很有用。
assert!当您希望确保测试中的某个条件评估为真时标准库提供的宏非常有用。
我们给出assert!计算结果为布尔值的参数。
如果该值为true则什么都不会发生测试通过。
如果值为false则assert!调用panic!导致测试失败。
使用assert!帮助我们检查我们的代码是否按照我们想要的方式运行。
在第五章的示例5-15中我们使用了一个Rectangular结构和一个can_hold方法它们在示例11-5中重复出现。
让我们将这段代码放到src/lib.rs文件中然后使用assert!为它编写一些测试。
示例11-5:使用第5章中的Rectangular结构及其can_hold方法
can_hold方法返回一个布尔值这意味着它是assert!的完美用例。
在示例11-6中我们编写了一个测试通过创建一个宽度为8、高度为7的Rectangular实例并断言它可以容纳另一个宽度为5、高度为1的Rectangular实例来练习can_hold方法。
1,};assert!(larger.can_hold(smaller));}
示例11-6:对can_hold的测试检查一个较大的矩形是否可以容纳一个较小的矩形
super::*;。
tests模块是一个常规的模块它遵循我们在第7章节中提到的常见的可见性规则。
因为tests模块是一个内部模块我们需要将外部模块中的测试代码放到内部模块的范围内。
我们在这里使用了一个glob所以我们在外部模块中定义的任何东西都可以用于这个tests模块。
我们将我们的测试命名为llarger_can_hold_smaller并且创建了我们需要的两个矩形实例。
然后我们调用了assert!宏并将调用larger.can_hold(smaller)的结果传递给它。
这个表达式应该返回true所以我们的测试应该通过。
让我们来了解一下
(file:///projects/rectangle)Finished
(target/debug/deps/rectangle-6584c4561e48942e)running
确实测试通过了让我们添加另一个测试这次断言较小的矩形不能容纳较大的矩形:
1,};assert!(!smaller.can_hold(larger));}
因为在这种情况下can_hold函数的正确结果是false所以我们需要在将结果传递给assert!之前对其求反。
因此如果can_hold返回false我们的测试将通过:
(file:///projects/rectangle)Finished
(target/debug/deps/rectangle-6584c4561e48942e)running
tests::smaller_cannot_hold_larger
两项测试通过现在让我们看看当我们在代码中引入一个bug时测试结果会发生什么。
我们将更改can_hold方法的实现在比较宽度时用小于号替换大于号:
(file:///projects/rectangle)Finished
(target/debug/deps/rectangle-6584c4561e48942e)running
tests::smaller_cannot_hold_larger
backtracefailures:tests::larger_can_hold_smallertest
--lib我们的测试发现了漏洞因为larger.width是8smaller.width是5所以can_hold中的宽度比较现在返回false:
验证功能的一种常见方法是测试被测代码的结果与您期望代码返回的值之间是否相等。
您可以使用assert!来做到这一点并使用运算符向其传递一个表达式。
然而这是一个非常常见的测试标准库提供了一对宏——assert_eq!和assert_eq!—为了更方便地执行该测试。
这些宏分别比较相等或不相等的两个参数。
如果断言失败它们还会打印这两个值这更容易看出测试失败的原因反之assert!只指示它为表达式获得了一个false而不打印导致false的值。
在示例11-7中我们编写了一个名为add_two的函数将2加到它的参数中然后我们使用assert_eq!测试这个函数。
(file:///projects/adder)Finished
(target/debug/deps/adder-92948b65e88960b4)running
我们将4作为参数传递给assert_eq!等于调用add_two(2)的结果。
这个测试的代码行是test
tests::it_adds_two...okok文本表示我们的测试通过了
让我们在代码中引入一个bug看看assert_eq!是什么看起来当它失败时。
将add_two函数的实现改为添加3:
(file:///projects/adder)Finished
(target/debug/deps/adder-92948b65e88960b4)running
backtracefailures:tests::it_adds_twotest
--lib我们的测试发现了bugit_adds_two测试失败消息告诉我们失败的断言是assertion
和right值是什么。
这条消息帮助我们开始调试:left的参数是4但是right的参数是5这里我们有add_two(2)。
你可以想象当我们有很多测试正在进行时这将特别有帮助。
注意在一些语言和测试框架中等式断言函数的参数被称为expected和actual我们指定参数的顺序很重要。
然而在Rust中它们被称为left和right我们指定我们期望的值和代码产生的值的顺序并不重要。
我们可以把这个测试中的断言写成assert_eq!(add_two(2),
assert_ne!如果我们给它的两个值不相等将通过如果相等将失败。
当我们不确定一个值是什么但是我们知道这个值绝对不应该是什么时这个宏是最有用的。
例如如果我们正在测试一个函数它肯定会以某种方式改变它的输入但是改变输入的方式取决于我们在一周中的哪一天运行测试那么最好的断言可能是函数的输出不等于输入。
表面之下是assert_eq!和assert_ne!宏使用运算符和!分别为。
当断言失败时这些宏使用调试格式打印它们的参数这意味着被比较的值必须实现PartialEq和Debug特征。
所有基本类型和大多数标准库类型都实现了这些特征。
对于您自己定义的结构和枚举您需要实现PartialEq来断言这些类型的相等性。
当断言失败时您还需要实现Debug来打印值。
因为这两个特征都是可派生的特征正如第5章示例5-12中提到的这通常就像在你的结构或枚举定义中添加#[derive(PartialEqDebug)]注释一样简单。
请参阅附录C“可衍生特征”了解有关这些和其他可衍生特征的更多详细信息。
您还可以添加一个自定义消息作为assert!的可选参数与失败消息一起打印assert_eq和assert_ne!。
所需参数之后指定的任何参数都将传递给format!宏(在第8章的“用运算符串联or格式宏”部分)因此您可以传递一个包含{}占位符和要放入这些占位符的值的格式字符串。
自定义消息对于记录断言的含义非常有用当一个测试失败时您会对代码的问题有更好的了解。
例如假设我们有一个用名字问候别人的函数我们想测试我们传递给函数的名字是否出现在输出中:
greeting(Carol);assert!(result.contains(Carol));}
对这个程序的要求还没有达成一致我们很确定开头的Hello文本会改变。
我们决定当需求改变时我们不需要更新测试所以我们不检查greeting函数返回的值是否完全相等而是断言输出包含输入参数的文本。
现在让我们通过将greeting改为排除name来引入一个bug看看默认测试失败是什么样子的:
(file:///projects/greeter)Finished
(target/debug/deps/greeter-170b942eb5bf5e3a)running
backtracefailures:tests::greeting_contains_nametest
--lib这个结果只是表明断言失败了以及断言在哪一行。
更有用的失败消息是打印greeting函数的值。
让我们添加一个定制的失败消息该消息由一个格式字符串组成该格式字符串带有一个占位符占位符中填充了我们从greeting函数中获得的实际值:
greeting(Carol);assert!(result.contains(Carol),Greeting
{},result);}现在当我们运行测试时我们将得到一个更具信息性的错误消息:
(file:///projects/greeter)Finished
(target/debug/deps/greeter-170b942eb5bf5e3a)running
backtracefailures:tests::greeting_contains_nametest
--lib我们可以在测试输出中看到我们实际得到的值这将帮助我们调试发生了什么而不是我们预期会发生什么。
除了检查返回值检查我们的代码是否如我们所期望的那样处理错误情况也很重要。
例如考虑我们在第9章示例9-13中创建的猜测类型。
其他使用Guess的代码依赖于Guess实例只包含1到100之间的值的保证。
我们可以编写一个测试确保试图创建一个值在该范围之外的Guess实例时会出错。
我们通过向测试函数添加属性should_panic来实现这一点。
如果函数内部的代码出现混乱则测试通过如果函数内部的代码没有死机测试就会失败。
示例11-8显示了一个测试它检查Guess::new的错误条件是否在我们期望的时候发生。
super::*;#[test]#[should_panic]fn
我们将#[should_panic]属性放在#[test]属性之后它所应用的测试函数之前。
让我们看看测试通过后的结果:
(file:///projects/guessing_game)Finished
(target/debug/deps/guessing_game-57d70c3acb738f4d)running
看起来不错现在让我们在代码中引入一个错误删除如果值大于100new将会死机的条件:
(file:///projects/guessing_game)Finished
(target/debug/deps/guessing_game-57d70c3acb738f4d)running
expectedfailures:tests::greater_than_100test
--lib在这种情况下我们没有得到非常有用的消息但是当我们查看测试函数时我们看到它被注释为#[should_panic]。
我们得到的失败意味着测试函数中的代码没有导致死机。
使用should_panic的测试可能不精确。
should_panic测试将会通过即使测试因不同于我们预期的原因而死机。
为了使should_panic测试更加精确我们可以向should_panic属性添加一个可选的预期参数。
测试工具将确保失败消息包含所提供的文本。
例如考虑示例11-9中Guess的修改代码其中new根据值是太小还是太大而出现不同的消息。
super::*;#[test]#[should_panic(expected
这个测试将会通过因为我们在should_panic属性的expected参数中输入的值是Guess::new函数出错的消息的子字符串。
我们可以指定我们期望的整个紧急消息在本例中Guess
200.。
您选择指定的内容取决于恐慌消息中有多少是独特的或动态的以及您希望测试有多精确。
在这种情况下紧急消息的子字符串足以确保测试函数中的代码执行else
为了查看当带有expected消息的should_panic测试失败时会发生什么让我们通过交换if
{}.,value);}这一次当我们运行should_panic测试时它将失败:
(file:///projects/guessing_game)Finished
(target/debug/deps/guessing_game-57d70c3acb738f4d)running
100failures:tests::greater_than_100test
到目前为止我们的测试失败时都会惊慌失措。
我们也可以编写使用ResultTE
返回类型。
在函数体中而不是调用assert_eq!当测试通过时我们返回Ok(())当测试失败时返回一个包含String的Err。
的测试使您能够在测试体中使用问号操作符这是一种编写测试的便捷方式如果测试中的任何操作返回Err变量测试就会失败。
的测试上使用#[should_panic]批注。
要断言一个操作返回一个EErrrr变量不要在ResultTE
值上使用问号运算符。
而是使用assert!(value.is_err())。
现在您已经知道了编写测试的几种方法让我们看看运行测试时会发生什么并探索我们可以使用cargo
自动化测试的概念如何编写测试用例assert!在测试用例中如何使用assert_eq!和assert_ne!在测试用例中如何使用添加自定义失败信息使用should_panic检查panic使用ResultT,
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback