Fork me on GitHub
#beginners
<
2023-06-20
>
keychera01:06:09

I am currently learning to write clojure test, would you recommend against writing test like this?

(defn damage [player value] (update player :life - value))
(defn heal [player value] (update player :life + value))

(deftest damage-the-player
  (-> {:life 100 :max-life 100}
      (damage 10)
      (doto (as-> player
                  (testing "player is damaged"
                    (is (= (:life player) 90)))))
      (heal 200)
      (doto (as-> player
                  (testing "player is healed and will not exceed max life"
                    (is (= (:life player) 100)))))))
a little context, example above use game terms but I am not actually building a game, just playing around with names. What I am actually testing is some kind of a stateful web server where I would test a sequence of api calls and saves intermediate results via this map passing (I think I read somewhere else that this is called context map)

seancorfield02:06:27

In tests, I lean toward verbose, clear naming, so I would probably give the intermediate values names in let instead of threading the whole thing (e.g., player, damaged-player, healed-player). I'd also break that test in two: one for damage cases (what happens when damage is zero, negative, equal to life, larger than life) and one for healing cases (zero, negative, equal to max/life difference, greater than that).

keychera03:06:46

thanks for the input! I see, explicitly naming the different state it would produce sounds better and I would definitely write the tests for each function separately, it’s just that this example I thought of is another test where I want to make sure sequence of actions would still produce what I expected, also kinda become a small documentation of the flow that I want to create (since I am trying out what if I try writing the test first before the code)

seancorfield03:06:02

Yeah, there's no harm in having a combined test as well as the individual tests, and the combined test could have the threaded workflow to match the intended usage:

(deftest test-damage-heal-workflow
  (testing "combined workflow with damage and healing"
    (let [test-player (-> {:life 100 :max-life 100} (damage 10) (heal 200))]
      (is (= 100 (:life player))))))

💡 3
2
sandeep virdi04:06:26

Hi all, I am learning Clojure and was reading "Clojure in Action". I am running into an issue with this code

(defn stub-fn [return-value]
  (fn [&args]
    return-value))
(defmacro stubbing [stub-forms & body]
  (let [stub-pairs (partition 2 stub-forms)
        returns (map last stub-pairs)
        stub-fns (map #(list 'stub-fn %) returns)
        real-fns (map first stub-pairs)]
    `(binding [~@(interleave real-fns stub-fns)]
       ~@body)))

(defn calc-x [x1 x2]
  (* x1 x2))

(defn calc-y [x1 x2]
  (/ x1 x2))

(defn some-client []
  (println (calc-x 2 3) (calc-y 10 20)))

(some-client)

(comment (stubbing [calc-x 1
                    calc-y 2]
                   (some-client)))
And here is the error that I run into
; --------------------------------------------------------------------------------
; eval (current-form): (some-client)
; (out) 6 1/2
nil
; --------------------------------------------------------------------------------
; eval (current-form): (stubbing [calc-x 1 calc-y 2] (so...
; (err) Execution error (IllegalStateException) at in-action.stubbing/eval7020 (REPL:25).
; (err) Can't dynamically bind non-dynamic var: in-action.stubbing/calc-y
Can someone explain what I am doing wrong. Thanks.

seancorfield04:06:15

Unfortunately, Clojure in Action is an old book and both editions were outdated by the time of their publication (because the publisher wouldn't allow the author extra time to update the book for the then-current version of Clojure).

seancorfield04:06:50

(Clojure in Action 1st ed was one of the first Clojure books I bought, and I have the 2nd ed as well)

seancorfield04:06:30

You've stumbled across some code in CiA that is no longer valid: binding only works with Vars that are declared ^:dynamic now. with-redefs is the way to temporarily redefine functions for use in an expression like this -- but it should only be used in very limited situations, such as testing. See https://clojuredocs.org/clojure.core/with-redefs

seancorfield04:06:31

user=> (with-redefs [calc-x (constantly 1)
                     calc-y (constantly 2)]
         (some-client))
1 2
nil
user=>

sandeep virdi04:06:59

That makes sense, thanks.

lassemaatta04:06:13

(and even in testing you might have problems with with-redefs if you run tests in parallel)

seancorfield04:06:48

1st ed was written against Clojure 1.3 but published just after 1.4 came out, and 2nd ed was written against 1.5 but published just after 1.6 came out, I think. Lots of changes and additions since then.

seancorfield04:06:04

@U05AH90263E I'm curious what editor/REPL setup you're using that produces output like that?

seancorfield05:06:07

Oh, cool! That's one of the few Clojure editor setups I'm not familiar with, so I didn't recognize the REPL output format.

phill09:06:42

Notably, @U05AH90263E, this question does not come up very often. It seems to me that "stubbing" did not get popular. It's just too weird to expect the consumers of a function to know what to stub and when. Instead, folks write higher-order functions, which take as parameters the functions they will need to call.

sandeep virdi22:06:26

@U0HG4EHMH Thanks for the reply, if you can point me to some resource or documentation that I can read that would help.

phill00:06:30

Higher-order functions is on pg 308 of my copy of Clojure in Action. But the technique is pervasive. Ring (around page 225-230) also uses it.

2
Nathan Nolk16:06:40

Hi all, for some reason when I try to import java.awt, I get a ClassNotFoundException error, here's the bit of code that's relevant (this is from Programming Clojure, I'm trying to implement a snake game):

(ns snake-try.core
  (:import (java.awt Color Dimension) 
	   (javax.swing JPanel JFrame Timer JOptionPane)
           (java.awt.event ActionListener KeyListener))
  (:require [clojure.set :refer :all]))
I use Clojure on Emacs and Arch Linux with Java 17 set as the default option. Any idea what the problem is?

2
hiredman16:06:02

you have a headless build of java installed

Nathan Nolk16:06:22

Thank you for answering! What does that mean exactly?

hiredman16:06:56

I dunno, what is the actually exception you are getting, and what are you doing when you get it?

Nathan Nolk16:06:19

I'm using CIDER on Emacs, running the last S-expression which is trying to import java.awt, this is the error I get:

2. Unhandled clojure.lang.Compiler$CompilerException
   Error compiling src/snake_try/core.clj at (2:12)
   #:clojure.error{:phase :compile-syntax-check,
                   :line 2,
                   :column 12,
                   :source
                   "/home/mayumin/Documents/Programming/Clojure Development/snake-try/src/snake_try/core.clj"}
             Compiler.java: 6825  clojure.lang.Compiler/analyze
             Compiler.java: 6762  clojure.lang.Compiler/analyze
             Compiler.java: 3832  clojure.lang.Compiler$InvokeExpr/parse
             Compiler.java: 7126  clojure.lang.Compiler/analyzeSeq
             Compiler.java: 6806  clojure.lang.Compiler/analyze
             Compiler.java: 6762  clojure.lang.Compiler/analyze
             Compiler.java: 3900  clojure.lang.Compiler$InvokeExpr/parse
             Compiler.java: 7126  clojure.lang.Compiler/analyzeSeq
             Compiler.java: 6806  clojure.lang.Compiler/analyze
             Compiler.java: 6762  clojure.lang.Compiler/analyze
             Compiler.java: 6137  clojure.lang.Compiler$BodyExpr$Parser/parse
             Compiler.java: 5479  clojure.lang.Compiler$FnMethod/parse
             Compiler.java: 4041  clojure.lang.Compiler$FnExpr/parse
             Compiler.java: 7122  clojure.lang.Compiler/analyzeSeq
             Compiler.java: 6806  clojure.lang.Compiler/analyze
             Compiler.java: 7191  clojure.lang.Compiler/eval
             Compiler.java: 7149  clojure.lang.Compiler/eval
                  core.clj: 3215  clojure.core/eval
                  core.clj: 3211  clojure.core/eval
    interruptible_eval.clj:   87  nrepl.middleware.interruptible-eval/evaluate/fn/fn
                  AFn.java:  152  clojure.lang.AFn/applyToHelper
                  AFn.java:  144  clojure.lang.AFn/applyTo
                  core.clj:  667  clojure.core/apply
                  core.clj: 1990  clojure.core/with-bindings*
                  core.clj: 1990  clojure.core/with-bindings*
               RestFn.java:  425  clojure.lang.RestFn/invoke
    interruptible_eval.clj:   87  nrepl.middleware.interruptible-eval/evaluate/fn
                  main.clj:  437  clojure.main/repl/read-eval-print/fn
                  main.clj:  437  clojure.main/repl/read-eval-print
                  main.clj:  458  clojure.main/repl/fn
                  main.clj:  458  clojure.main/repl
                  main.clj:  368  clojure.main/repl
               RestFn.java: 1523  clojure.lang.RestFn/invoke
    interruptible_eval.clj:   84  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:   56  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:  152  nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
                  AFn.java:   22  clojure.lang.AFn/run
               session.clj:  218  nrepl.middleware.session/session-exec/main-loop/fn
               session.clj:  217  nrepl.middleware.session/session-exec/main-loop
                  AFn.java:   22  clojure.lang.AFn/run
               Thread.java:  833  java.lang.Thread/run

1. Caused by java.lang.ClassNotFoundException
   java.awt

Nathan Nolk16:06:41

After checking which packages are installed on my system, I have both a full and a headless build of JRE17 which are installed... which is weird.

hiredman16:06:52

check you code

Nathan Nolk16:06:19

Even running import in a REPL without any code gives me that same error though.

hiredman16:06:23

there is an error somewhere where that is treating java.awt as a class

hiredman16:06:48

java.awt isn't a class, which is why you are getting an error about it not being found

Nathan Nolk16:06:55

Ah, thank you!

Nathan Nolk16:06:13

This is weird because it gives me the same issue when I run the code bundled with the book Programming Clojure, which should be correct...

hiredman16:06:33

user=> (ns foo (:import java.awt))
Execution error (ClassNotFoundException) at java.net.URLClassLoader/findClass (URLClassLoader.java:445).
java.awt
foo=>
for example

Nathan Nolk16:06:19

So how should I import java.awt in this namespace?

hiredman16:06:42

import lets you use a short name for a class, that is all it does

hiredman16:06:46

java.awt is not a class

Nathan Nolk16:06:00

So should I use (require ...) or something else?

hiredman16:06:16

what are you trying to do

Nathan Nolk16:06:47

Trying to import the Class and Dimension classes from java.awt, this is from a snake namespace from the book Programming Clojure.

hiredman16:06:08

ok, and what does your ns form look like?

Nathan Nolk16:06:23

You can see it at the beginning of the thread, if I understand your question correctly?

hiredman16:06:33

are you sure that is what it looks like?

Nathan Nolk16:06:46

Yes, I copy pasted the code from Emacs.

hiredman16:06:55

user=> (ns snake-try.core
  (:import (java.awt Color Dimension)
   (javax.swing JPanel JFrame Timer JOptionPane)
           (java.awt.event ActionListener KeyListener))
  (:require [clojure.set :refer :all]))
nil
snake-try.core=>

Nathan Nolk16:06:02

(ns snake-try.core (:import (java.awt Color Dimension) (javax.swing JPanel JFrame Timer JOptionPane) (java.awt.event ActionListener KeyListener)) (:require [clojure.set :refer :all])) Here it is again.

hiredman16:06:11

that code doesn't cause an error, so the code you are running is not that

Nathan Nolk16:06:20

Eh...??? Really?

Nathan Nolk16:06:43

Running it in Emacs gives me the error I mentioned before though...

Nathan Nolk16:06:47

Is it my system then?

hiredman16:06:12

no, you need to look at your setup and make sure the code you expect is running and only that code

Nathan Nolk16:06:12

Lemme boot up a REPL and try it without anything else.

Nathan Nolk16:06:27

Okay, I will check my code and see what's wrong, I assume it has to be some error there because I don't see what else might be wrong. I will report back once I find it.

hiredman16:06:00

that is the error you would get if you tried to treat the symbol java.awt with normal evaluation rules, instead of the ns macro's

hiredman16:06:07

user=> java.awt
Syntax error (ClassNotFoundException) compiling at (REPL:0:0).
java.awt
user=>

hiredman16:06:25

user=> (:import (java.awt Color Dimension))
Syntax error (ClassNotFoundException) compiling at (REPL:1:10).
java.awt
user=>

Nathan Nolk16:06:22

Here is the GUI part of the application, making use of Java:

hiredman16:06:32

i would start with adding a (println "Hello World") to the very top of your source file (above even the ns form) and verify that prints before you get the error, and that will verify the source file you are looking at is what you are actually loading

Nathan Nolk16:06:38

Okay: Hello World! does work, and now it's giving me a different error:

Nathan Nolk16:06:58

Execution error (IllegalArgumentException) at snake-try.core/game (core.clj:190). No matching method setFocusable found taking 1 args for class java.lang.Class

dpsutton16:06:29

(doto JPanel
      (.setFocusable true)
      (.addKeyListener panel))

dpsutton16:06:34

you most likely want to set an instance of a JPanel as focusable. Here JPanel is just the class java.awt.JPanel

dpsutton16:06:49

(or javax.swing.JPanel i mean)

Nathan Nolk16:06:08

I see... Sorry I'm not sure I understand what that means, this is the code from the book and I don't fully understand it. 😞

Nathan Nolk16:06:27

Does that mean I should create an actual instance of it and set that as focusable? If so, how do I do this?

dpsutton16:06:33

which book?

Nathan Nolk16:06:51

Programming Clojure, 3rd edition

Nathan Nolk16:06:08

This is the end of chapter 6, state and concurrency.

Nathan Nolk16:06:30

The source code can be found online, and it is the same as what I wrote which is what I don't understand, surely they'd check their code is working first..?

dpsutton16:06:16

your code is different

Nathan Nolk16:06:22

Wait, did I mess up?

Nathan Nolk16:06:25

Thanks, let me check

dpsutton16:06:28

(doto panel) vs (doto JPanel)

Nathan Nolk16:06:11

Thanks!! It is working now.

Nathan Nolk16:06:28

Okay, so I was asking it to use it on the class JPanel, instead of using it on the panel object, correct?

dpsutton16:06:50

i think your initial issue was unsaved buffers. I suspect what code you saw was not the code that was actually in the files. @U0NCTKEV8’s suggestion to add printlines ensured that you saved all of the files

Nathan Nolk16:06:18

Ahhh, I see! Thank you.

Nathan Nolk16:06:23

This solves my issue, thank you very much!

dpsutton16:06:47

correct. There’s a big difference between an instance of a set #{} and the class clojure.lang.PersistentHashSet

Nathan Nolk16:06:17

Thank you, this clarified everything! 🙂

Andrew Berisha00:08:19

I have a question about the snake game as well, should I post it here or make a new thread?

Nathan Nolk10:08:35

Probably best to make a new thread, you'll get more visibility

Andrew Berisha18:08:35

Yup, made a new thread

Eitan16:06:54

Hello, I’m working on a project that uses shadow-cljs and firebase. What would be the best channel to ask questions in?

dpsutton16:06:26

#C03S1L9DN most likely

Eitan17:06:11

Thank you.

👍 2