惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hacker News: Front Page
P
Palo Alto Networks Blog
T
ThreatConnect
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
T
True Tiger Recordings
P
Privacy & Cybersecurity Law Blog
B
Blog
IT之家
IT之家
Last Week in AI
Last Week in AI
F
Full Disclosure
Hacker News: Ask HN
Hacker News: Ask HN
C
Comments on: Blog
Microsoft Azure Blog
Microsoft Azure Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
N
News and Events Feed by Topic
NISL@THU
NISL@THU
腾讯CDC
雷峰网
雷峰网
Security Latest
Security Latest
李成银的技术随笔
M
Microsoft Research Blog - Microsoft Research
L
LangChain Blog
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Check Point Blog
Y
Y Combinator Blog
Recent Announcements
Recent Announcements
博客园 - Franky
N
News | PayPal Newsroom
V
V2EX
A
About on SuperTechFans
The Register - Security
The Register - Security
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google Online Security Blog
Google Online Security Blog
MyScale Blog
MyScale Blog
Cisco Talos Blog
Cisco Talos Blog
Vercel News
Vercel News
WordPress大学
WordPress大学
C
Cyber Attacks, Cyber Crime and Cyber Security
The Hacker News
The Hacker News
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
爱范儿
爱范儿
A
Arctic Wolf
L
LINUX DO - 最新话题
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

Qubik的小站

Building an Interpreter: Introduction and Lexer 在 Rocky Linux 10 Workstation KDE 上安装中文输入法 2025 上半年记 | Qubik的小站 Rust 写脚本?可以的!(上) | Qubik的小站 2024 年终总结 | Qubik的小站 Racket 入门 - 条件表达式、递归与复合数据 | Qubik的小站 Racket 入门 - 配置与基础 | Qubik的小站 2024 年上半年总结 | Qubik的小站 Java 中的 Lambda 表达式 | Qubik的小站 大 O 表示法 | Qubik的小站 递归与归并排序 | Qubik的小站 基础的排序与搜索算法 | Qubik的小站 Java 中的泛型 | Qubik的小站 数据结构:链表(LinkedList) | Qubik的小站 数据结构:栈与队列 | Qubik的小站
Racket 入门 - 模块与测试 | Qubik的小站
me@qubik.top (Qubik65536) · 2024-07-24 · via Qubik的小站

本文我将简单介绍一下 Racket 的模块以及如何进行测试。

模块

我们在开发程序的时候,通常会讲代码分成多个文件以更好的组织代码,提高代码的可维护性。与几乎所有其他语言一样,Racket 同样支持将代码分成多个文件。但对于熟悉 Java、Python 等语言的读者来说,Racket 的模块系统可能稍微有些不同:因为你不只需要进行“导入”,还需要进行“导出”。

我们暂且想象有一个文件 utils.rkt,其中定义了我们程序所需要的一些工具函数。例如:

scheme

#lang racket

(define hello "Hello, World!")
(define num1 3)
(define num2 4.2)

(define (cube x) (* x x x))

(define (sum-of-cube x y)
  (+ (cube x)
     (cube y)))

而我们希望在我们的主程序文件 main.rkt 中使用 hello, num1, num2 三个变量以 sum-of-cube 函数。我们首先需要从 utils.rkt 中“导出”这些变量和函数。

”导出“,或者说声明本文件提供哪些变量和函数,可以通过在文件的开头使用 provide 关键字来实现。在我们的示例中,utils.rkt 文件最终应该如下:

scheme

#lang racket

(provide hello num1 num2)
(provide sum-of-cube)

(define hello "Hello, World!")
(define num1 3)
(define num2 4.2)

(define (cube x) (* x x x))

(define (sum-of-cube x y)
  (+ (cube x)
     (cube y)))

其中的 (provide hello num1 num2)(provide sum-of-cube) 分别声明了 hello, num1, num2 以及 sum-of-cubeutils.rkt 中是可以被其他文件访问的。当然,你其实可以将 (provide hello num1 num2)(provide sum-of-cube) 合并成 (provide hello num1 num2 sum-of-cube),或者将其分成四行:

scheme

(provide hello)
(provide num1)
(provide num2)
(provide sum-of-cube)

都是可以的。

最后,我们在 main.rkt 中使用 require 关键字来导入 utils.rkt 提供的变量和函数即可:

scheme

#lang racket

(require "helper.rkt")

hello ;; "Hello, World!"
num1 ;; 3
num2 ;; 4.2

(define x (sum-of-cube num1 2))
x ;; 35

注意

通常来讲,providerequire 语句应该放在文件的开头,#lang 声明之后。

另外,除了使用 (require "my-helpers.rkt") 的方式,你还可以通过 (require net/url) 的方式来导入网络上的一些其他模块,或者一些安装 Racket 时附带的模块。例如提供测试功能的 test-engine/racket-tests 模块。

测试

有的时候,我们需要对我们的代码进行测试。Racket 提供了一个简单的测试框架,可以帮助我们进行测试。我们可以通过 test-engine/racket-tests 模块来使用这个测试框架。首先,我们需要导入这个模块:

scheme

#lang racket

(require test-engine/racket-tests)

然后,假如我们有一些函数需要测试,或者有一些变量值需要校验。例如:

scheme

(define (fact n)
  (cond
    [(= n 0) 1]
    [(= n 1) 1]
    [else (* n (fact (- n 1)))]))

(define x 16)
(define y 12)

我们分别需要测试斐波那契数列函数输出是否正确,以及 xy 的和是否等于 32。我们可以通过 check-expect 表达式来建立测试用例:

scheme

(check-expect (fact 5) 120)
(define (fact n)
  (cond
    [(= n 0) 1]
    [(= n 1) 1]
    [else (* n (fact (- n 1)))]))

(check-expect (+ x y) 32)
(define x 16)
(define y 12)

其中,(check-expect (fact 5) 120) 会检查 (fact 5) 表达式的值是否等于 120,而 (check-expect (+ x y) 32) 会检查 (x + y) 的值是否等于 32

注意,与其它一些语言的测试框架不同,我们可以把测试用例写在其相关的定义前面。这样我们其实可以通过文件中的空行等方式分隔不同的功能,并通过每个部分开头的测试用例来简单的描述这部分的功能。

目前,你的代码文件应该是这样的:

scheme

#lang racket

(require test-engine/racket-tests)

(check-expect (fact 5) 120)
(define (fact n)
  (cond
    [(= n 0) 1]
    [(= n 1) 1]
    [else (* n (fact (- n 1)))]))

(check-expect (+ x y) 32)
(define x 16)
(define y 12)

如果你运行这段代码,你会发现什么都没有发生。实际上,我们需要在代码的结尾加入 (test) 表达式来一次性运行所有的测试用例:

scheme

#lang racket

(require test-engine/racket-tests)

(check-expect (fact 5) 120)
(define (fact n)
  (cond
    [(= n 0) 1]
    [(= n 1) 1]
    [else (* n (fact (- n 1)))]))

(check-expect (+ x y) 32)
(define x 16)
(define y 12)

(test)

输出应该是这样的:

shell

Ran 2 tests.                                                        
1 of the 2 tests failed.                                            
Check failures:                                                     
                     ┌────┐              ┌────┐                     
        Actual value 28 differs from 32 │, the expected value.
                     └────┘              └────┘                     
in test.rkt, line 12, column 0

其中不仅会告诉一共运行了多少个测试用例,还会告诉你有多少个测试用例失败了,以及失败的原因。这就是最简单的 Racket 测试。

The testing modules also provide check-error, which checks if its single argument expression produces an error when evaluated; check-within, used to test whether an inexact computed value is within a certain tolerance of an expected value; and check-member-of, which permits several possible expected values.

这个测试框架还提供了 check-error,用于检查其参数表达式在求值时是否产生错误;check-within,用于测试计算出的非精确值是否在预期值的某个容差范围内;以及 check-member-of,允许多个可能的预期值。

Ragde [1],Cubik65536 译

总结

本文简单讲述了如何通过 providerequire 表达式来在 Racket 中进行模块化开发,以及如何使用 Racket 自带的 test-engine/racket-tests 模块来进行简单的功能测试。

随着 Teach Yourself Racket 的 Basic 一章的结束,本文就是 Racket 入门系列的最后一篇文章了。虽然我们只是简单的介绍了一些 Racket 中最基础的内容,但是这些内容已经足够读者开始使用 Racket 来进行一些简单的编程了。

在即将开始的《Racket 学习笔记》系列中,我们将继续跟随 Teach Yourself Racket,了解 Racket 中的纯函数式编程(Lambda,高阶函数,本地变量),非纯函数式编程(I/O,可变性),以及 Racket 中的迭代和正则表达式。之后我们将跟随 Racket 官方的 The Racket Guide 来了解 Teach Yourself Racket 没有提到的内容,例如 Racket 的异常机制,宏,包管理等。

GL & HF!


Footer

  1. P. Ragde, "1 Basics," Teach Yourself Racket. Accessed: Jul. 23, 2024.
    Available: https://cs.uwaterloo.ca/~plragde/flaneries/TYR/Basics.html ↩︎