patternMinor
Simple spec testing DSL
Viewed 0 times
dslsimpletestingspec
Problem
I wrote a very small little DSL for spec testing:
This turned out to be very nice, for writing some simple, concise specs:
I'm new to Clojure and macros so any thoughts on this code would be appreciated. I have a few concerns that you could start with:
(ns funky-spec.core)
(def described-entity (ref nil))
(defn it [fun & v]
(assert (apply fun (cons (deref described-entity) v))))
(def it-is it)
(def it-is-the it)
(defmacro describe [value nest]
`(dosync (ref-set described-entity ~value)
~nest))
(defmacro when-applied [nest]
`(dosync (ref-set described-entity ((deref described-entity)))
~nest))
(defmacro when-applied-to [& args-and-nest]
`(dosync (ref-set described-entity (apply (deref described-entity) (take (dec (count (quote ~args-and-nest))) (quote ~args-and-nest))))
(eval (last (quote ~args-and-nest)))))This turned out to be very nice, for writing some simple, concise specs:
(ns funky-spec.core-test
(:require [clojure.test :refer :all]
[funky-spec.core :refer :all]))
(describe 42
(it = 42))
(describe 99
(it not= 42))
(describe 0
(it-is zero?))
(describe 99
(it-is-the (complement zero?)))
(defn answer [] 42)
(describe answer
(when-applied
(it = 42)))
(describe identity
(when-applied-to 99
(it = 99)))
(describe +
(when-applied-to 42 35
(it = 77)))
(describe +
(when-applied-to 42 35 9 10
(it = 96)))I'm new to Clojure and macros so any thoughts on this code would be appreciated. I have a few concerns that you could start with:
when-appliedcould (I believe) be an alias forwhen-applied-to. I can't get this to work withdef, is there something I am missing?
- Is there a better pattern I could use other than a "global"
reffor thedescribed-entity? I tried usingletand shadowing, but couldn't get it to work with macros
- In
when-applied-to, there's a lot of quoting and aneval, I did this to keepitfrom pre-maturely executing. Is there a cleaner way to thunkit?
Solution
This is a case where a dynamic variable is the ideal solution:
Online resources can probably explain dynamic vars better than I can here. For the purposes here, think of it like your atom except that it's thread local and will restore the original value when the binding context exits.
To use a macro from another macro, you'll want to do it as shown here rather than trying to literally def it in terms of another macro. The reasons why should probably be saved for when you get a little more macro experience.
And of course,
In
(def ^:dynamic *described-entity*)
(defn it [fun & v]
(assert (apply fun *described-entity* v)))
(def it-is it)
(def it-is-the it)
(defmacro describe [value nest]
`(binding [*described-entity* ~value]
~nest))
(defmacro when-applied-to [& args-and-nest]
(let [args (butlast args-and-nest)
nest (last args-and-nest)]
`(binding [*described-entity* (*described-entity* ~@args)]
~nest)))
(defmacro when-applied [nest]
`(when-applied-to
~nest))Online resources can probably explain dynamic vars better than I can here. For the purposes here, think of it like your atom except that it's thread local and will restore the original value when the binding context exits.
To use a macro from another macro, you'll want to do it as shown here rather than trying to literally def it in terms of another macro. The reasons why should probably be saved for when you get a little more macro experience.
And of course,
butlast. In
when-applied-to you can do your argument wrangling outside of the macro. Note though, I'd probably consider passing args in a vector like (when-applied-to [42 35] (it = 77)) to make the arguments more explicit and require less pre-processing.Code Snippets
(def ^:dynamic *described-entity*)
(defn it [fun & v]
(assert (apply fun *described-entity* v)))
(def it-is it)
(def it-is-the it)
(defmacro describe [value nest]
`(binding [*described-entity* ~value]
~nest))
(defmacro when-applied-to [& args-and-nest]
(let [args (butlast args-and-nest)
nest (last args-and-nest)]
`(binding [*described-entity* (*described-entity* ~@args)]
~nest)))
(defmacro when-applied [nest]
`(when-applied-to
~nest))Context
StackExchange Code Review Q#70422, answer score: 2
Revisions (0)
No revisions yet.