Hello, as previously threatened here the anatomy of my TAP library.
The testing and reporting is split in separate parts. The testing part provides is as main interface. I first followed Perl's Test::More, but thought it would be better to be closer to test-is. So I adopted is, but - as I think - in slightly easier fashion. The core is (as with test-is) the is macro, which is implemented as a multimethod. Since there is no multimacro, we use is* for the method. (defmulti is* (fn [x & _] (if (seq? t) (first t) t))) (defmacro is [t & desc] (is* t (first desc))) Now we can start to define tests. ; (is (= actual expected) "description") (defmethod is* '= [t desc] (let [actual (nth t 1) exptd (nth t 2)] `(test-driver (fn [] ~actual) (quote ~actual) (fn [] ~exptd) ~desc (fn [e# a#] (= e# a#)) (fn [e# a# r#] (diag (.concat "Expected: " a#)) (diag (.concat "to be: " e#)) (diag (.concat "but was: " r#)))))) Now this looks scary. So please let me explain. The first argument packages about the "actual" expression. The second quotes it. The third is the "expected" expression. It is also packaged up in a closure. The fourth is the test description and the fifth the actual test. The last argument is a callback, which is called in case the test fails and which might be used to provide specialised diagnostic message. I like having my tests tell me, why they failed. While this seems quite complicated, I think it really is this essence of a test. Everything else is boilerplate which is handled in the test-driver function. (defn test-driver [actual qactual exp desc pred diagnose] (try (let [e (exp) a (actual) r (pred e a)] (report-result r desc) (when-not r (let [es (pr-str e) as (pr-str qactual) rs (pr-str a)] (diagnose es as rs))) a) (catch Exception e (report-result false desc) (diag (str "Exception was thrown: " e)) `test-failed))) So this is pretty straightforward. Fire up a try, evaluate the expected and actual expression, run the predicate and check the result. In case the test fails or an Exception is thrown, the failure is reported and some diagnostics are printed. So that completes the testing part. From the user point of view one has function - is -, which handles all the testing. To provide a new test form, one simply defines a new method. In the method, one simply passes the work to test-driver which takes care for reporting and proper test execution. The reporting side already showed up here and there in form of the diag and the report-result functions. Others are plan (for TAP), get-result to retrieve the test results. They act on a global Var *the-harness*. They can also be implemented as multimethods. A TAP harness would produce TAP output, an "interactive" harness would only report failures and maybe some statistics, a "batch" harness just keeps book of failing tests and diagnostics for recursive use. Changing a harness is a simple matter of (binding [*the-harness* (make-some-harness)] (do-some tests here)) Although this looks terribly complicated and is a hard to digest bunch of stuff, I'd appreciate your comments. However, how this can be made more functional... I have no clue. Sincerely Meikel
smime.p7s
Description: S/MIME cryptographic signature