我目前正在研究“小计划者”#39;我已按照本书编写了一些函数,但我也想为它们编写一些单元测试。
我想创建一个对列表,每对包含一个带参数的函数,以及它的预期输出。然后我将重复此列表并检查每个评估的函数是否与其预期输出相匹配。如果其中任何一个不匹配,整个事情应该返回false。
使用代码示例将更容易证明这一点:
(define (test-fns list-of-tests)
; takes a list of pairs (functions and expected outputs) and returns true only
; if all outputs match expected
(define (test-fn test)
(let
([fn (car test)]
[expected (car (cdr test))])
(equal? (eval fn) expected))
)
(cond
((null? list-of-tests) #t)
(else
(and (test-fn (car list-of-tests)) (test-fns (cdr list-of-tests))))))
(let
([lat '(ice cream with fudge for dessert)]
[lat2 '(coffee cup tea cup and hick cup)])
(test-fns '(
((multisubst 'brown 'cup lat2) '(coffee brown tea brown and hick brown))
((multiinsertL 'brown 'cup lat2) '(coffee brown cup tea brown cup and hick brown cup))
((multiinsertR 'brown 'cup lat2) '(coffee cup brown tea cup brown and hick cup brown))
((multirember 'cup lat2) '(coffee tea and hick))
((subst 'topping 'fudge lat) '(ice cream with topping for dessert)))))
但是,这给了我一个错误:multisubst: unbound identifier;
我使用DrRacket逐步完成执行并意识到我传递了符号'(multisubst 'brown 'cup lat2)
,当然lat2
未在test-fns
内定义。
所以我想在第二个代码块中做的是以某种方式'扩展'将每个局部变量(例如lat
和lat2
)放入传递给test-fns的列表中的组件部分。
所以我们最终会以(multisubst 'brown 'cup '(coffee cup tea cup and hick cup))
代替第一对的车。
我有一个模糊的概念,这可能是宏的用途,但我不确定。有什么想法吗?
-
V2
所以我按照之前的回答应用了quasiquote,它起作用了(sorta)。我简化了测试代码,使其更容易复制。
这是新代码:
(define (test-fns list-of-tests)
; takes a list of pairs (functions and expected outputs) and returns true only
; if all outputs match expected
(define (test-fn test)
(let
([fn (car test)]
[expected (car (cdr test))])
(equal? (eval fn) expected))
)
(cond
((null? list-of-tests) #t)
(else
(and (test-fn (car list-of-tests)) (test-fns (cdr list-of-tests))))))
(let
([lat '(coffee cup tea cup and hick cup)])
(test-fns `(
((identity (quote ,lat)) '(coffee cup tea cup and hick cup)))))
这仍然失败multisubst: unbound identifier;
,但这次我不明白为什么。在DrRacket调试器中,我们看到以下内容:
fn => (identity (quote (coffee cup tea cup and hick cup)))
expected => (quote (coffee cup tea cup and hick cup))
test => ((identity (quote (coffee cup tea cup and hick cup))) (quote (coffee cup tea cup and hick cup)))
list-of-tests => (((identity (quote (... ... ... ... ... ... ...))) (quote (coffee cup tea cup and hick cup))))
所以fn
实际定义得很好。然而,如果我尝试用(fn)
直接评估它,我得到:
application: not a procedure;
expected a procedure that can be applied to arguments
given: '(identity '(coffee cup tea cup and hick cup))
arguments...: [none]
如果我尝试(eval fn)
,我会identity: unbound identifier; also, no #%app syntax transformer is bound in: identity
也许我的方法根本就是错误的,我应该采用更好的方法来构建它?
-
V3 - 解决了!
好的,对于我的最后一次尝试,我简化了这种方式。我现在有这个:
(define (test-fns list-of-tests)
; takes a list of pairs (functions and expected outputs) and returns true only
; if all outputs match expected
(define (test-fn test)
(let
([fn (car test)]
[args (cadr test)]
[expected (caddr test)])
(equal? (fn args) expected))
)
(cond
((null? list-of-tests) #t)
(else
(and (test-fn (car list-of-tests)) (test-fns (cdr list-of-tests))))))
示例:
(test-fns (list (list identity '(coffee cup tea cup and hick cup) '(coffee cup tea cup and hick cup))))
> #t
(test-fns (list (list identity 1 0)))
> #f
并且,在混合中引入quasiquote允许我们扩展本地lat
变量:
(let ([lat '(coffee cup tea cup and hick cup)])
(test-fns (list (list identity `,lat '(coffee cup tea cup and hick cup))
(list identity 0 0))))
> #t
耶!
-
V4 - 最终解决方案(我保证)
所以上面还有一个问题 - 我们只能为一个函数提供一个参数。最后的调整使用apply
和一个参数列表,如:
;tests for chapter 3
(define (test-fns list-of-tests)
; takes a list of pairs (functions and expected outputs) and returns true only
; if all outputs match expected
(define (test-fn test)
(let
([fn (car test)]
[args (cadr test)]
[expected (caddr test)])
(equal? (apply fn args) expected))
)
(cond
((null? list-of-tests) #t)
(else
(and (test-fn (car list-of-tests)) (test-fns (cdr list-of-tests))))))
; tests for the tester
(let ([lat '(coffee cup tea cup and hick cup)])
(test-fns (list (list identity `(,lat) '(coffee cup tea cup and hick cup))
(list identity '(0) 0))))
; tests for chapter 3
(let
([lat '(ice cream with fudge for dessert)]
[lat2 '(coffee cup tea cup and hick cup)])
(test-fns (list (list multisubst `(brown cup ,lat2) '(coffee brown tea brown and hick brown)))))
输出
>#t
>#t
>万岁,它有效!
-
V5 - 没有准引号
根据LePetitPrince的回答,准引号实际上是完全没必要的。我们可以直接调用测试人员传递标识符,如下所示:
; tests for the tester
(let ([lat '(coffee cup tea cup and hick cup)])
(test-fns (list (list identity (list lat) '(coffee cup tea cup and hick cup))
(list identity '(0) 0))))
; tests for chapter 3
(let
([lat '(ice cream with fudge for dessert)]
[lat2 '(coffee cup tea cup and hick cup)])
(test-fns (list (list multisubst (list 'brown 'cup lat2) '(coffee brown tea brown and hick brown)))))
输出:
>#t
>#t
答案 0 :(得分:1)
您可以使用quasiquote
,或只使用list
(我更喜欢后者)。另外,我没有看到使用eval
的原因。这是一个例子:
(define (test-fns list-of-tests)
(or (null? list-of-tests)
(let ((test (car list-of-tests)))
(let ((fn (car test)) (params (cadr test)) (expected-result (caddr test)))
(let ((real-result (apply fn params)))
(printf "~a ~a -> ~a -> ~a\n" fn params expected-result real-result)
(and (equal? expected-result real-result)
(test-fns (cdr list-of-tests))))))))
(请注意,我使用printf
是为了方便。根据您的实施情况,您可能需要将其分解为display
和newline
来电。 )
测试:
(test-fns (list (list add1 '(1) 2)
(list sub1 '(0) -1)
(list + '(1 2 3) 6)))
产量
#<procedure:add1> (1) -> 2 -> 2
#<procedure:sub1> (0) -> -1 -> -1
#<procedure:+> (1 2 3) -> 6 -> 6
#t
和
(test-fns (list (list add1 '(1) 3)
(list sub1 '(0) -1)
(list + '(1 2 3) 6)))
产量
#<procedure:add1> (1) -> 3 -> 2
#f