【问题标题】:Importing scripts at runtime在运行时导入脚本
【发布时间】:2013-07-04 12:43:37
【问题描述】:

我正在使用 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,以及将使用什么参数。

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

【问题讨论】:

  • 命名空间是一等公民。我不明白你的问题是什么。请参阅:clojure.org/namespacesclojure.github.io/clojure/clojure.core-api.html#clojure.core/…
  • 好吧,命名空间是一等公民,但是对于初学者来说,完成 sebi 所需的技术可能并不那么明显。请适当地查看我的回答和评论(尤其是关于我如何映射和过滤命名空间的任何反馈)。
  • 您的回答非常好且全面,但是我猜想所需要的只是在函数内部调用 (use ...) 。 OP 的回答证实了这种情况。
  • 1) use 现在被广泛劝阻。 2)“这仅在我导入代码摘录开头所见的每个脚本时才有效。这不好......” - OP不想在源代码中硬编码命名空间名称,这就是我回答了。

标签: reflection import clojure namespaces runtime


【解决方案1】:

是的,您可以使用 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

祝你好运!

【讨论】:

    【解决方案2】:

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

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

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

    【讨论】:

    • 哈,真的吗?您的问题表明您正在创建一个框架,并且不想对命名空间名称进行硬编码。
    • 我没有。 run-all-tests 是一个公共方法,它将从调用者那里接收命名空间名称。
    • 好吧,你应该更喜欢 (require):refer [test-fns...] 代替:stackoverflow.com/questions/10358149/… - 我不认为你真的想使用 use
    猜你喜欢
    • 2017-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-23
    • 2021-12-29
    • 1970-01-01
    • 2019-03-18
    • 1970-01-01
    相关资源
    最近更新 更多