code-reviews

Jakub Holý 2019-05-07T09:42:36.008800Z

Hello, I would really appreciate feedback on my API design. I am integrating with a Java test framework that requires that I pass it an object with user-determined methods (used via reflection; there is no interface defining them). I also want to be able to supply options to the test framework. This is a macro (wrapping gen-class & more) that I came up with to do this:

(deffixture
  "math.algebra.AdditionFixture"
  [add subtract]  ;; expose these two functions as methods on the class instances 
  {:concordion/full-ognl                    false  ;; some options for the test framework, optional
   :concordion/fail-fast-exceptions        [IndexOutOfBoundsException]
   ::before-suite                          #(println "AdditionFixture: I run before each Suite")  ;; setup & teardown functions, optional
   ::before-spec                           #(println "AdditionFixture: I run before each Spec")
   ::before-example                        #(println "AdditionFixture: I run before each example")
   ::after-example                         #(println "AdditionFixture: I run after each example")
   ::after-spec                            #(println "AdditionFixture: I run after each Spec")
   ::after-suite                           #(println "AdditionFixture: I run after each Suite")})
I am especially unsure about the setup/teardown functions. Perhaps I should instead pass them along with the methods, relying on well-known names, i.e. [add subtract beforeSuite ... afterSuite]? Thoughts? (You can see the code here https://github.com/holyjak/clj-concordion/blob/0.0.3/src/clj_concordion/core.clj#L234 - it does gen-class, defn for each of the methods, deftest so that we can run it via clojure.test, and adds an _opts method to the class for passing the options map around)

2019-05-07T17:47:14.009700Z

I assume you need gen-class instead of proxy because you need to be able to refer to the class by name, as well as extending a concrete base class?

2019-05-07T18:03:11.010400Z

@holyjak what would it look like if you used proxy and deftype instead of genclass? is there something that definitely wouldn't work?

Jakub Holý 2019-05-07T21:17:23.010600Z

I need to add custom methods, not described by any interface. The only construct that supports that is gen-class, I believe?

2019-05-07T21:29:15.010800Z

I wonder if defining an interface in order to use its methods is weirder than generating gen-class and functions from a macro

2019-05-07T21:30:23.011Z

it's a legitimate question to me, I find gen-class frustrating enough that I often opt to just making a shim class in a .java file because that's simpler and more readable than the hoops I go through in clojure

Jakub Holý 2019-05-07T21:47:33.011200Z

I guess it could work just as well. I would still likely need a macro, though, to save the user from repeating the details... I will look into it.

Jakub Holý 2019-05-07T22:14:12.025400Z

To clarify: 1. I need a class with custom methods (so that the Java framework can invoke them via reflection, for good reasons) 2. I need to associate extra static data with the class (namely test configuration, setup functions etc) 3. I likely want the class named so that, given a file path, I can translate it to the corresponding fixture class and instantiate it. 4. The methods on the class could actually be static, I guess reflection would find them anyway and there is no dynamic state 5. The instances could be singletons, I will ever need just one instance of a fixture class I've considered using just namespaces as they generate classes with static methods but 1) users would need to remember adding :gen-class to ns and prefix functions with - (ie -add though the test uses just "add") 2) I couldn't Spec valid option and extra fn names such as before-suite