Clojure中如何对本地函数(letfn)进行单元测试?

4
我曾经花了几年时间学习Scheme,现在正在学习Clojure。在Scheme中,最佳实践之一是在父函数内定义辅助函数,从而限制它们在“外部”的可见性。当然,那时候没有TDD(测试驱动开发)的概念,因此测试这些函数并不是一个问题。
我仍然想以这种方式构建Clojure函数;即使用letfn在主函数内绑定辅助函数。当然,测试这样的“本地”函数是有问题的。我知道我可以定义“私有”函数,但这会将可见性范围限定为命名空间,这有所帮助,但粒度不够细。如果你遇到在另一个函数中使用letfn,很明显该函数不能用于普通用途。
因此,我的问题是,是否可以测试这样的本地函数,如果可以,如何测试?如果不能,那么是否有某种约定来帮助代码阅读,以便清楚地表明函数只有一个调用者?
谢谢! 比尔

3
为什么要对函数的内部逻辑进行单元测试?如果它非常复杂,需要分开测试各个部分,那么将定义放在自己的顶级表单中会有好处。 - noisesmith
嗯。除了可见性之外,还有一个问题是letfn可能会关闭一个符号,“交织”它。 - Mike Fikes
Common Lisp的条件系统能够解决这个问题吗?如果可以,Clojure中有相应的功能吗? - Thumbnail
我认为你需要更改思维方式,以编写良好的Clojure代码(也适用于Scheme)。使您的辅助函数更加功能化(即函数式编程),以便它们可以安全地公开使用。 - myguidingstar
我不明白为什么本地函数比顶级函数(或任何其他函数)“更不功能”(或更不安全)。唯一的区别是可见性 - 它是相同的函数。我仍然认为使用本地函数使它们预期的用途对代码读者更明显,但难以测试。 - Bill Cohagan
1个回答

1
通常的做法是将函数放在命名空间中。
一种选择是使用元数据:
user=> (defn ^{::square #(* % %)} cube [x]
  #_=>   (* x ((::square (meta #'cube)) x)))
#'user/cube
user=> (meta #'cube)
{…, :user/square #<user$fn__780 user$fn__780@2e62c3f9>}
user=> (cube 3)
27

当然可以编写宏来使其更美观。

6
我以前从未见过这种元数据的方法,它让我想吐。我看不出任何优点;这只是一种粗暴的处理:私有函数。如果您想要比 :private 更私有的内容,请使用 letfn;元数据不是一个合理的妥协。 - amalloy
@amalloy OP 不希望像 letfn 一样私有化。OP 希望能够从任何作用域访问该函数。 - user1804599
1
将该函数设为公共函数,并在其注释中添加“除非你有充分的理由,否则不要使用此函数”的说明。或者将其设为私有函数,并通过变量进行调用。 - amalloy
1
嘿,这可能不太美观,但它似乎是一个有效的解决方案。此外,正如所指出的那样,这种丑陋可以(相当容易地)放在宏的后面。 - Nathan Davis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接