我正在寻找有关如何构建我的球拍程序的建议。目前,我有大约 5 个不同版本的程序,每个程序都有相同的单元测试(RackUnit),只是添加到每个文件的末尾。这很难维护。
我想要做的是将测试提取到一个单独的文件中,并要求 RackUnit 为每个程序运行一次测试。但我不知道该怎么做。有什么建议吗?
谢谢!
我正在寻找有关如何构建我的球拍程序的建议。目前,我有大约 5 个不同版本的程序,每个程序都有相同的单元测试(RackUnit),只是添加到每个文件的末尾。这很难维护。
我想要做的是将测试提取到一个单独的文件中,并要求 RackUnit 为每个程序运行一次测试。但我不知道该怎么做。有什么建议吗?
谢谢!
在这种情况下,也许我们可以通过使用 Racket 的反射系统来做一些高度动态的事情。
例如,假设我们要确保一组模块都提供一个f
似乎是单调递增函数的函数。我们如何编写一个框架,用相同的测试组测试一组实现?
我们可以编写一个工具:
requires
一个有问题的实现模块,并且代码可能如下所示:
(define (test-module-with-monotonic-f module-path-name)
(define ns (make-base-namespace))
(printf "testing ~s\n" module-path-name)
(eval `(begin (module a-test-module racket/base
(require rackunit
(file ,(path->string module-path-name)))
(check-true (> (f 1) (f 0))
(format "~a fails to provide monotonic f" ,module-path-name))
(check-true (> (f 3) (f 2))
(format "~a fails to provide monotonic f" ,module-path-name)))
(require 'a-test-module))
ns))
它构建了测试模块,并使用动态eval
. eval
通常被认为是邪恶的,但在这种特殊情况下,我认为这是一个合适的工具。
一旦我们有了这个助手,我们就可以在一个文件集合上运行它,比如在一个impls
子目录中:
(for ([mod-name (in-directory "impls")]
#:when (equal? (filename-extension mod-name) #"rkt"))
(test-module-with-monotonic-f mod-name))
您可以尝试完整的运行示例(https://github.com/dyoo/monotonic-f-example)来查看所有部分。
(顺便说一句,应该清楚的是,上面的测试严重不足。)
我不知道这有多强大,但这里有一个使用破坏卫生的宏的解决方案。(不过 Danny 比我聪明 :) 所以也许你会想听从他的建议。)
文件f1.rkt
:
#lang racket
(define (f x) (displayln "f1's f") (+ x 1))
(define (g x) (displayln "f1's g") (+ x 2))
(require "f-tests.rkt")
(tests)
文件f2.rkt
:
#lang racket
(define (f x) (displayln "f2's f") (+ 1 x))
(define (g x) (displayln "f2's g") (+ 2 x))
(require "f-tests.rkt")
(tests)
文件f-tests.rkt
:
#lang racket
(provide tests)
(define-syntax (tests stx)
(syntax-case stx ()
[(_)
(datum->syntax
stx
'(begin
(require rackunit)
(check-equal? (f 10) 11)
(check-equal? (g 20) 22)))]))
datum->syntax
说宏应该在的tests
上下文中使用标识符stx
,即宏被调用的地方(通常,宏将在定义宏时使用标识符)。运行任一文件f1.rkt
或f2.rkt
将运行测试。(打印只是为了证明正在调用正确的函数。)