Rust能力养成之(13)测试组建和测试基元

2023-11-20 16:59

本文主要是介绍Rust能力养成之(13)测试组建和测试基元,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

图片

 

前言

 

在本章中,我们将继续学习Cargo,并学习如何编写测试,如何写开发文档,以及如何用基准测试来度量代码的性能。然后,将综合利用这些技能来构建一个模拟逻辑门的crate,以亲身体验一下如何编写单元测试,集成测试,以及文档测试。

 

本章内容将涉及:

  • 测试动机(Motivation on testing)

  • 组织测试和测试原语(Organizing tests and testing primitives)

  • 单元测试和集成测试(Unit tests and integration tests)

  • 文档测试(Documentation tests)

  • 基准测试(Benchmark tests)

  • 与Travis CI的持续集成(Continuous integration with Travis CI)

 

而本篇内容将涉及上面的前两项。

 

为什么要测试?

图片

 

虽然基本的原因大家也都了解,这里还是针对性的说一下。一般而言,软件系统就像带有各种零件的机器。如果单个零件发生故障,整个机器就很有可能在不可靠因素的影响下运行,自然出问题的几率也就增加了。在软件中,单个零件可以是函数、模块或所使用的任何库。自然,对软件系统的各个零件组件进行功能测试是维护高质量代码切实有效的方法。固然这并不能保证bug可以被根除,但确实为代码生成后的实际部署提供了可靠依据,而且长期看来,也可以维护并维持住代码的效果和稳健性。

此外,如果没有单元测试(unit test),大规模软件的重构也是很难做到的。在软件开发中,灵活和适当的使用单元测试具有良好效果:在代码实现阶段,编写良好的单元测试已经成为软件组件中的不成文规矩。在维护阶段,现有的单元测试作为对代码基中的回归控制,可以促进即时修复。在像Rust这样的编译语言中,由于编译器提供了有效细致的错误诊断,单元测试回归以及可能所涉及的重构于是有着非常详实的指导信息,不得不说,这一点当真比起其他同类型语言颇具优势。

单元测试的另一个不错的追加影响是,其鼓励程序员编写主要依赖于输入参数的模块化代码,即无状态函数(stateless function),这就使程序员尽量不去写依赖于全局可变状态的代码,因为编写依赖于全局可变状态的测试是很难的,再者,仅仅考虑为一段代码编写测试的行为可以很快帮助程序员找出其代码实现中的错误。最后,对于想要理解代码库的不同部分如何交互的新手来说,也可以作为非常贴近实际的文档。

 

组织测试(Organizing tests)

 

通常,在开发软件时会编写两种测试:单元测试(unit test)集成测试(integration test),各自服务于不同的目的,并以不同的方式与被测试的代码库进行交互。单元测试总是轻量级的,测试单个组件,以便开发人员可以经常运行受测内容,从而提供较短和快捷的反馈循环路径。相比而言,集成测试是重型的,旨在模拟真实场景,会根据其环境和规范做出断言性质的判定。Rust的内置测试框架为开发者编写和组织这些测试提供了常规的默认设置,如下所示:

 

  • 单元测试:通常写在首测代码的同一个模块中。当这些测试数量增加时,会被组织到一个实体中作为嵌套模块。一般在当前模块中创建一个子模块,按照惯例,将其命名为tests,并在上面加上#[cfg(test)]属性的注释,再将所有与测试相关的函数放在其中。此属性只是告诉编译器包含在测试模块中的代码,但条件仅仅是在运行cargo测试时。稍后会再详细介绍相关属性。

  • 集成测试:是在cargo root的“tests/目录”中单独编写,写起来就如同是在真实的使用相关代码。在test /目录中的任何.rs文件都可以添加use声明,以引入需要测试的任何公共API。

 

测试基元(Testing primitives)

 

Rust的内置测试框架基于一组主要由属性(attributes)宏(macro)组成的基本元素,在我们编写任何实际的测试之前,需要熟悉一下相关的用法。

 

属性(Attributes)

 

Rust语言中的属性,其实就是对某一个条目(item)的注释信息。所谓的这些条目,在结构上被称为顶层语言结构(top-level language construct),比如函数(functions),模块(modules),结构体(structs),枚举(enums),常数声明(constant declarations),以及其他任何需要在crate的根目录进行定义的对象。属性用来指示编译器,来为出现在它们下面的条目,添加额外的代码或含义,如果适用于某个模块,则为模块添加额外的代码或含义。有关这方面的内容,在后续篇章中还会讨论。这里,我们先介绍两种属性。

  • #[<name>]:该属性适用于该名称所涉及的各个部分,通常出现在其定义之上。例如,Rust中的测试函数使用#[test]属性进行注释,表示该函数将被视为测试工具的一部分。

  • #![<name>]:加了一个叹号,代表该属性适用于整个软件包(whole crate),通常放在crate 根目录的最顶层位置。

在模块中编写测试时,还可以使用其他形式的属性,如#[cfg(test)]。此属性添加在测试模块顶层,以提示编译器根据相应条件去编译模块,但者仅仅是当代码在测试模式下进行编译之时。显然,属性并不仅仅在测试代码中使用;而是在Rust中广泛存在,在接下来的章节中还会看到很多。

 

断言宏(Assertion macros)

 

在测试中,当给定一个测试用例时,我们会试图断言(assert)或推断一下软件组件在给定输入范围内的可能行为。编程语言通常会提供断言函数(assertion function)来执行上述所提及的这些断言,而Rust为我们提供了以宏来实现的断言函数,来帮助我们实现同样的目的。我们来看看一些常用的语句:

assert!(true);assert!(a == b, "{} was not equal to {}", a, b);

 

  • assert!: 这是最简单的断言宏,其接受一个布尔值来进行断言。如果该值为false,test panic 方面会显示哪里出现了问题。当然,断言宏还可以接收格式字符串,后跟相应数量的变量,以提供可定制的错误消息,如下所示:

let a = 23;let b = 87;assert_eq!(a, b, "{} and {} are not equal", a, b);

 

  • assert_eq!: 该宏接受两个值,如果不相等,则会显示失败信息,同时也可以接受定制性错误消息的格式字符串

  • assert_ne!:该宏与assert_eq!类似,不同处在于用来断言两个值是否不相等

  • debug_assert!:该宏与assert!类似,不止用于测试,大多用于断言在代码运行阶段的不变或规则性内容。而这些断言仅在调试构建时有效,在调试模式下运行时,可以帮助捕获违反断言的情况。当代码以优化模式编译时,这些宏调用完全被忽略,并被优化为无操作。类似的,还有debug_assert_eq!和debug_assert_ne !,其工作方式与assert!宏同属一类。

 

为了比较这些断言宏内的值,Rust会使用特性/特征(trait)。例如,assert!(a == b)实际上是一个方法调用,a.eq(&b),而后返回一个bool值,而eq方法来自 PartialEq特性 。绝大多数Rust的内建类型都会执行PartialEq Eq特性 来进行比较,而这两个特性的区别,我们还要等到下一章来讲。

 

但是,对于用户定义的类型,我们需要实现这些特性。幸运的是,Rust为我们提供了一个名为derive的宏,非常方便,可以接收一个或多个特性来进行实现,该宏可以通过在任何用户定义的类型上添加#[derive(Eq, PartialEq)]这样的注释就能使用。注意括号内的特性质名称,很熟悉吧。derive是一个过程宏,只是为有其出现的类型的impl块生成代码,并实现trait的方法或相关的函数。而关于各种宏,会在后续专门涉及宏的章节中为大家介绍。

 

结语

 

至此,终于可以要写一些测试了,那么我们从下一章开始。

 

主要参考和建议读者进一步阅读的文献

https://doc.rust-lang.org/book

1.Rust编程之道,2019, 张汉东

2.The Complete Rust Programming Reference Guide,2019, Rahul Sharma,Vesa Kaihlavirta,Claus Matzinger

3.Hands-On Data Structures and Algorithms with Rust,2018,Claus Matzinger

4.Beginning Rust ,2018,Carlo Milanesi

5.Rust Cookbook,2017,Vigneshwer Dhinakaran

这篇关于Rust能力养成之(13)测试组建和测试基元的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/396089

相关文章

性能测试介绍

性能测试是一种测试方法,旨在评估系统、应用程序或组件在现实场景中的性能表现和可靠性。它通常用于衡量系统在不同负载条件下的响应时间、吞吐量、资源利用率、稳定性和可扩展性等关键指标。 为什么要进行性能测试 通过性能测试,可以确定系统是否能够满足预期的性能要求,找出性能瓶颈和潜在的问题,并进行优化和调整。 发现性能瓶颈:性能测试可以帮助发现系统的性能瓶颈,即系统在高负载或高并发情况下可能出现的问题

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

字节面试 | 如何测试RocketMQ、RocketMQ?

字节面试:RocketMQ是怎么测试的呢? 答: 首先保证消息的消费正确、设计逆向用例,在验证消息内容为空等情况时的消费正确性; 推送大批量MQ,通过Admin控制台查看MQ消费的情况,是否出现消费假死、TPS是否正常等等问题。(上述都是临场发挥,但是RocketMQ真正的测试点,还真的需要探讨) 01 先了解RocketMQ 作为测试也是要简单了解RocketMQ。简单来说,就是一个分

【测试】输入正确用户名和密码,点击登录没有响应的可能性原因

目录 一、前端问题 1. 界面交互问题 2. 输入数据校验问题 二、网络问题 1. 网络连接中断 2. 代理设置问题 三、后端问题 1. 服务器故障 2. 数据库问题 3. 权限问题: 四、其他问题 1. 缓存问题 2. 第三方服务问题 3. 配置问题 一、前端问题 1. 界面交互问题 登录按钮的点击事件未正确绑定,导致点击后无法触发登录操作。 页面可能存在

业务中14个需要进行A/B测试的时刻[信息图]

在本指南中,我们将全面了解有关 A/B测试 的所有内容。 我们将介绍不同类型的A/B测试,如何有效地规划和启动测试,如何评估测试是否成功,您应该关注哪些指标,多年来我们发现的常见错误等等。 什么是A/B测试? A/B测试(有时称为“分割测试”)是一种实验类型,其中您创建两种或多种内容变体——如登录页面、电子邮件或广告——并将它们显示给不同的受众群体,以查看哪一种效果最好。 本质上,A/B测

EasyPlayer.js网页H5 Web js播放器能力合集

最近遇到一个需求,要求做一款播放器,发现能力上跟EasyPlayer.js基本一致,满足要求: 需求 功性能 分类 需求描述 功能 预览 分屏模式 单分屏(单屏/全屏) 多分屏(2*2) 多分屏(3*3) 多分屏(4*4) 播放控制 播放(单个或全部) 暂停(暂停时展示最后一帧画面) 停止(单个或全部) 声音控制(开关/音量调节) 主辅码流切换 辅助功能 屏

【Rust练习】12.枚举

练习题来自:https://practice-zh.course.rs/compound-types/enum.html 1 // 修复错误enum Number {Zero,One,Two,}enum Number1 {Zero = 0,One,Two,}// C语言风格的枚举定义enum Number2 {Zero = 0.0,One = 1.0,Two = 2.0,}fn m

linux中使用rust语言在不同进程之间通信

第一种:使用mmap映射相同文件 fn main() {let pid = std::process::id();println!(

Verybot之OpenCV应用一:安装与图像采集测试

在Verybot上安装OpenCV是很简单的,只需要执行:         sudo apt-get update         sudo apt-get install libopencv-dev         sudo apt-get install python-opencv         下面就对安装好的OpenCV进行一下测试,编写一个通过USB摄像头采

第二十四章 rust中的运算符重载

注意 本系列文章已升级、转移至我的自建站点中,本章原文为:rust中的运算符重载 目录 注意一、前言二、基本使用三、常用运算符四、通用约束 一、前言 C/C++中有运算符重载这一概念,它的目的是让即使含不相干的内容也能通过我们自定义的方法进行运算符操作运算。 比如字符串本身是不能相加的,但由于C++中的String重载了运算符+,所以我们就可以将两个字符串进行相加、但实际