I'm using Clojure to write a small test framework.
(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))
My run-all-tests function accepts a list of clojure scripts, loads all the public functions in those scripts and runs them. This is great, only that i have to actually import those scripts. I call my function like this (run-all-tests ["pvt.tests.deployment" "pvt.tests.files" "pvt.tests.jms"]), but this only works if I import each of these scripts as seen at the beginning of my code excerpt. This is not ok, since I hvae no idea who will call run-all-tests, and what parameters will be used.
I was wondering if there's a way of importing these scripts at runtime. I already know the namespace of each script, so I have all the required information. Can this be done? Thanks
Yes, you can import Clojure source files from arbitrary file paths using load-file. If the source file contains a namespace declaration, those namespaces are now available to your Clojure application (framework).
Obviously, at a minimum you'll have to write some code that either takes names of Clojure source files from the command-line, or points to directories where the source files are located. Then your code will load the files using (load-file).
Your stated problem is that you want to execute some tests from the namespace without knowing the namespace names in advance. There are two ways to achieve this:
1) Use a naming convention. i.e. run your tests for each namespace that has the name matching your convention, i.e.
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")
Using code like the above, you've obtained a list of namespaces upon which you can execute your framework code.
2) Use metadata. Rather than follow a naming convention, require users of your framework to add metadata to their namespaces. This reduces the chance of accidentally testing a namespace that accidentally followed your convention.
(See: What are some uses of Clojure metadata?)
Note that this is the approach used by Clojure's own clojure.test/deftest macro.
Here is an example of finding namespaces with your custom metadata. Your namespace declaration in a source file defining tests:
(ns ^{:doc "some documentation" :my-framework-tests true}
foo.test.core)
At the REPL, an example of how you can obtain these programmatically:
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"}])
Now you have a list of namespaces that have been flagged as containing tests for your custom test framework. You could even use metadata in the namespace functions to avoid needing a naming convention for those as well.
There might be a more concise way to obtain namespaces having certain metadata (if someone knows of it, by all means, comment!)
Another important note: I'm loading arbitrary files to demonstrate it's possible, buy you really should consider following conventions followed by Leiningen, Maven or other build frameworks. For example, see lein-perforate
Good luck!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With