我正在使用Clojure编写一个小型测试框架。
(ns pvt.core.runner
(use
[pvt.tests.deployment]
[pvt.tests.files]
[pvt.tests.jms]))
(defn- run-test
[test-name]
{test-name (test-and-log test-name)})
(defn- run-all-tests-in-namespace
[namespace-name]
(map
run-test
(vals (ns-publics (symbol namespace-name))))
)
(defn run-all-tests
[namespace-list]
(map run-all-tests-in-namespace namespace-list))
我的run-all-tests
函数接受clojure脚本的列表,加载这些脚本中的所有公共函数并运行它们。这很好,只是我必须实际导入那些脚本。我像这样调用函数(run-all-tests ["pvt.tests.deployment" "pvt.tests.files" "pvt.tests.jms"])
,但只有在导入代码摘录开头所示的每个脚本时,这才有效。这是不好的,因为我不知道谁将调用run所有测试,以及将使用什么参数。
我想知道是否有一种方法可以在运行时导入这些脚本。我已经知道每个脚本的名称空间,所以我拥有所有必需的信息。这能做到吗?感谢
是的,您可以使用load-file
从任意文件路径导入Clojure源文件。如果源文件包含名称空间声明,那么这些名称空间现在可用于Clojure应用程序(框架)。
显然,您至少需要编写一些代码,这些代码要么从命令行获取Clojure源文件的名称,要么指向源文件所在的目录。然后,您的代码将使用(load-file)
加载这些文件。
您指出的问题是,您希望在不事先知道命名空间名称的情况下从命名空间执行一些测试。有两种方法可以实现这一点:
1)使用命名约定即为每个名称空间运行测试,该名称空间的名称与您的约定相匹配,即
user=> (load-file "/home/noahlz/foo.clj")
#<Var@1e955d29: #<core$foo foo.test.core$foo@48a7a9bd>>
user=> (filter #(re-matches #".*.test..*" %) (map str (all-ns)))
("foo.test.core")
使用类似上面的代码,您已经获得了一个名称空间列表,您可以在该列表上执行框架代码。
2)使用元数据与其遵循命名约定,不如要求框架的用户向其命名空间中添加元数据。这减少了意外测试意外遵循约定的命名空间的可能性。
(请参阅:Clojure元数据的一些用途是什么?)
请注意,这是Clojure自己的clojure.test/deftest
宏使用的方法。
下面是一个使用自定义元数据查找名称空间的示例。定义测试的源文件中的命名空间声明:
(ns ^{:doc "some documentation" :my-framework-tests true}
foo.test.core)
在REPL,一个如何通过编程获得这些信息的例子:
user=> (load-file "foo.clj")
user=> (filter (fn [[n m]] (:my-framework-tests m))
(map #(vector (str %) (meta %)) (all-ns)))
(["foo.test.core" {:my-framework-tests true, :doc "some documentation"}])
现在您有了一个名称空间列表,这些名称空间已被标记为包含自定义测试框架的测试。您甚至可以在命名空间函数中使用元数据,以避免对这些函数使用命名约定。
可能有一种更简洁的方法可以获得具有特定元数据的名称空间(如果有人知道的话,一定要注释!)
另一个重要的注意事项是:我加载任意文件是为了证明这是可能的,买吧,你真的应该考虑Leiningen、Maven或其他构建框架遵循的以下约定。例如,参见lein穿孔
祝你好运!
谢谢你帮我。我设法找到了我要找的东西。它实际上比我想象的要简单。不知道use
其实是一个函数。现在我只做这个:
(defn- run-all-tests-in-namespace
[namespace-name]
(use (symbol namespace-name))
(map
run-test
(vals (ns-publics (symbol namespace-name))))
)
我根据名称空间名称创建一个符号,然后将其传递给use
函数。效果很好!