Closed zcaudate closed 3 years ago
Hi Chris,
thanks very much for the feedback,
I looked into hara.test
and there are few things I can definitely reuse ;-)
When i refer to the nesting
I'm NOT talking about the nesting of facts like Midje, I don't mind that one.
What I don't like is the let
nesting to build-up state.
This problem mostly appears on integration tests where you have to build up some state in order to be able to test something. There is a better example in the Readme Motivation, but here is the crux:
(fact "something"
(let [a (create-something)]
a => {some state}
(let [b (create-child a)]
b => {some other state}
(let [c (create-subnode b)]
c => {whatever}))))
Here a
and b
only serve to reach c
which is the item I want to test, but adding few other checks along the way.
Now if c
is failing, and I want to try this on the REPL, I have to unpack/undo all the let
binding into def
s so that i can evaluate them individually.
So I will need to write something like
(def a (create-something))
(def b (create-child a))
(def c (create-subnode b))
;; eval 1-by-1 and check the issue
This is really tedious, time-consuming and error-prone. So what I did with RDT is to use the latter as a test
(repl-test
(def a (create-something))
a => {some state}
(def b (create-child a))
b => {some other state}
(def c (create-subnode b))
c => {whatever})
And what RDT does, is to convert the latter form into the first form (with let
), so that your tests are safe to run in a multi-thread environment.
I think for tests, less structure is better, it is definitely more readable, and easier to work with.
As I said, this problem dominates in integration tests, where the test has to build up some state before it can verify something, such as start the test cluster, create queues, tables, items etc, before being able to test something.
I haven't seen anything in hara.test
that would suggest a similar strategy.
Have you come across a similar issue? how do you solve this problem?
Bruno
What I don't like is the
let
nesting to build-up state
I agree with that. hara.test
doesn't do the let
nesting - I just parse for the =>
arrow and split on left and right sides.
The non-nested syntax you wrote out for rdt
is the only valid syntax for hara.test
. I had thought that it would be a disadvantage when I started doing the library but found exactly what you found when needing more fine grained control over testing - The simpler and more explicit the better. What you are doing is pretty much what I'm doing - that's why I thought I'd leave a comment.
I've since add more features like tabular data, retesting but the parsing is more or less the same basic code.
one thing that you might want to consider is to have :setup
and :teardown
options to your blocks.
I added those to the meta section of the tag form:
ie:
^{;; *************
;;
;; TICKER FULL
;;
;; *************
:refer statsnet.system.events-ticker/ticker-es-register :added "4.0"
:setup [(l/rt:inner-restart)
(config/setup-bench)
(!.lua
(u/<! DEBUG :es-handler ticker/ticker-es-connect)
true)
(ticker/ticker-redis-register)
(ticker/ticker-redis-start)
(ticker/ticker-es-register)]}
(fact "registers the ticker es service"
^:hidden
;;
;; DO INITIAL SETUP
;;
(def -conn- (redis/test:connection {:port 17003}))
(def -ds- (c/http-stream (str "http://127.0.0.1:"
(:port (l/rt:inner :lua))
"/eval/es")))
(ws/connection-count ticker/TICKER_ES_KEY "event-stream")
=> 1
(t/task-count ticker/TICKER_REDIS_KEY)
=> 1
;;
;; PUBLISH VIA REDIS
;;
(h/req -conn- ["PUBLISH" (last (:form @ticker/TICKER_PUBLIC_CHANNEL))
"hello"])
=> pos?
(h/req -conn- ["PUBLISH" (last (:form @ticker/TICKER_PUBLIC_CHANNEL))
"world"])
=> pos?
;;
;; CHECK WEBSOCKET OUTPUT
;;
(Thread/sleep 1000)
@(:events -ds-)
=> ["hello" "world"]
(h/close -conn-))
You also may be interested in other testing tools I've done. - here is a video of my test/documentation workflow: https://www.youtube.com/watch?v=dO7eyFtDDOY.
I'm still undecided on how to handle the :setup
and :teardown
. My current line of thinking is that I need probably two levels.
fact
or repl-test
)The reason I think I would need something at namespace level is to avoid setting up and tearing down things like localstack on each test which would be quite time consuming.
One idea I have is to try to automatically determine what needs to be torn down with a strategy similar to closeable-map using the list of def
in the block.
I had a look a the video you shared, I have to say you built an amazing platform for yourself. You should consider talking a bit more about it.
In my current setup, I have a fact:global
form that does that.
(fact:global
{:setup [(l/rt:inner-restart)
(def +port+ (:port (l/rt :lua)))]
:teardown [(l/rt:stop)]})
Here is the current code.test.compile namespace for reference.
I'm quite happy to open source the code management
libraries shown on the video. The old code is in the hara repo. The new code has changed a lot but the ideas are pretty much the same so let me know which functionalities may be useful for you.
There are a couple of things that have made me a bit tentative/lazy about open sourcing (which I had been doing quite a lot in the past):
hara.function.task
which provides abstractions on how a task can be selected, run and displayed. This is used pretty much everywhere but it's something that's very difficult to explain.small, single function
approach of most clojure libs - ie. here is my current util
namespace. This is the base
library that I build out from and would be considered way too big for anyone else to use.complete code.test
files:
https://gist.github.com/zcaudate/31fdf5437d43db7af058bd88c684e41f
it differs from the old hara.test
I agree with many of the above points. Especially in the Clojure ecosystem, it seems to be easier to rewrite rather than reuse.
In regards to my libraries, I mostly open-source stuff that I build for a project and I want to reuse in other projects. So like yourself, most of my libraries are very specific to my own use-cases. safely
for example, it was in use in many of my projects long before I decided to make it opensource, and like for hara
, my libraries when used together, their combined value increases.
I definitely would be interested in understanding and exploring more what you mean when you say:
I'm using clojure now as a compiler rather than a runtime
I do organise online Clojure Meetups via the London Clojurians, if you want I'd like to organise a talk for you to explore better your system/platform/library/approach and understand a bit more about the ideas you have. If you want we can plan something for around December. What's the best way to get in touch with you?
Alternatively, we are also organising an online free conference called re:Clojure and we will issue a Call for Papers in the next few days.
Thank you for the insightful discussion and the hara.test
code
@BrunoBonacci, that's great to know. A lot of great ideas have come out from London. You can contact me via (<my github username> at outlook dot com
). Unfortunately, I'm a little busy to prepare a public talk at this stage as I'm months behind on things right now. It'd be great to chat if you have time. I'm interested in what's happening over in Europe at the moment, especially in these strange times.
I'm mainly using clojure as a transpiler ie.
(defn.js isPlainObject
"checks if object is a plain object"
[val]
(return (== (-/typeString val) "Object")))
gets transformed to javascript code
function isPlainObject(val){
return typeString(val) == "Object";
}
It's kind of like cljs but it's a braindead transpile on the clj side. These is no immutable datastructures, only what the native language provides. Similar project exist - https://github.com/timothypratley/rustly but the difference is that I can customise the grammer. The native toolchain is used for builds so in the case of js, it's npm and webpack. There's pros and cons to this approach, the pros being the ability to use macros and to target exotic languages. The cons are obvious things like language/type safety but I'm more interested in performance at the moment so it's not a huge issue.
Especially in the Clojure ecosystem, it seems to be easier to rewrite rather than reuse.
I think that's the curse of being able to customise code exactly the way you want it - but it's still infinitely better than writing javascript.
I just found this:
https://github.com/echeran/kalai
which is similar to what I'm doing (but the code is pretty unmaintainable imo).
hi @BrunoBonacci
You might want to check out https://github.com/zcaudate/hara/tree/master/src/hara/test as it was a simplified version of midje. There's no nesting
=>
in blocks as the parser just splits the expressions into pairs.There's only 2 or 3 really important files so you can probably extract them out and customise it the way you want. It's a much simpler codebase than Midje (15%) with about 80% of the functionality. Example usages are in the
test/
directory:https://github.com/zcaudate/hara/blob/master/test/hara/lib/jsoup_test.clj
test runner output: