在运行时导入脚本

时间:2013-07-04 12:43:37

标签: reflection import clojure namespaces runtime

我正在使用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-all-tests,以及将使用哪些参数。

我想知道是否有一种在运行时导入这些脚本的方法。我已经知道每个脚本的命名空间,所以我拥有所有必需的信息。可以这样做吗? 感谢

2 个答案:

答案 0 :(得分:3)

是的,您可以使用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)使用元数据。不要遵循命名约定,而是要求框架的用户向其命名空间添加元数据。这样可以减少意外测试意外遵循约定的命名空间的可能性。

(参见:What are some uses of Clojure metadata?

请注意,这是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-perforate

祝你好运!

答案 1 :(得分:1)

感谢您帮助我。我设法找到了我要找的东西。它实际上比我想象的要简单。不知道use实际上是一个函数。现在我只是这样做:

(defn- run-all-tests-in-namespace
  [namespace-name]
  (use (symbol namespace-name))
  (map
    run-test
    (vals (ns-publics (symbol namespace-name))))
  )

我从命名空间名称创建一个符号,然后将其传递给use函数。效果很棒!