我正在寻找有关如何构建我的Racket程序的建议。目前,我有大约5个不同版本的程序,每个程序都有相同的单元测试(RackUnit),只是添加到每个文件的末尾。这很难维护。
我想要做的是将测试拉出到一个单独的文件中,并要求RackUnit为每个程序运行一次测试。但我不知道该怎么做。有什么建议吗?
谢谢!
答案 0 :(得分:3)
在这种情况下,也许我们可以通过使用Racket的reflection system来做一些高度动态的事情。
比如说,我们确保一组模块都提供了一个函数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))
您可以尝试complete running example (https://github.com/dyoo/monotonic-f-example)查看所有内容。
(顺便说一下,上面的测试显然是不够的。)
答案 1 :(得分:1)
我不知道这是多么强大,但这是一个使用卫生破坏宏的解决方案。 (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
将运行测试。 (打印只是为了证明正在调用正确的函数。)