One of the ways to get an organization to accept an alternate JVM language is to first use it for unit testing Java code -- "Boss, I am just going to write some unit tests in XXX. It'll never go out into production."
Are there any tutorials for doing this in Clojure?
I have just started using Scala to do this to test a Java REST server. Writing the tests in Scala allows me to embed expected XML output, mock the database calls with literal List objects, etc., not to mention that traits make it very easy to abstract out common code for the tests.
Basically what you need is clojure.test (or one of the many other clojure test libs) and standard Clojure Java interop.
Example:
(ns example.test-java-util
(:use
[clojure.test])
(:import [java.util HashSet]))
(defn new-empty-set []
(HashSet.))
(deftest test-empty-set
(is (= 0 (.size (new-empty-set))))
(is (= true (.isEmpty (new-empty-set))))
(is (= (new-empty-set) (new-empty-set))))
(deftest test-add-remove
(is (= (new-empty-set)
(doto (new-empty-set)
(.add "xyz")
(.remove "xyz")))))
And you would then run them in a variety of ways. Build tools like Maven using the maven clojure plugin run them automatically as part of "mvn test". In a repl, you can do something like:
example.test-java-util> (run-tests 'example.test-java-util)
Testing example.test-java-util
Ran 1 tests containing 4 assertions.
0 failures, 0 errors.
{:type :summary, :test 1, :pass 4, :fail 0, :error 0}
Here is an example using Leiningen, test.check and assuming a standard Maven layout:
pom.xml
project.clj
src
main
java
quicktest
Discontinuities.java
test
clojure
quicktest
test_discontinuities.clj
The Java function to test:
package quicktest;
public class Discontinuities {
public static double f5(double x) {
return x / (x-5);
}
}
The Clojure test case:
(ns quicktest.test-discontinuities
(:import [quicktest Discontinuities])
(:require [clojure.test :refer :all]
[clojure.test.check :as tc]
[clojure.test.check.generators :as gen]
[clojure.test.check.properties :as prop]
[clojure.test.check.clojure-test :as ct :refer (defspec)]))
(deftest test-single-case
(is (= 2.0 (Discontinuities/f5 10))))
(defspec test-discontinuities 1e4
(prop/for-all [x gen/nat ]
(let [y (Discontinuities/f5 x)]
(is (<= y x)))))
The project:
(defproject quicktest/discontinuities "0.1"
:dependencies [[org.clojure/clojure "1.8.0"]
[org.clojure/test.check "0.9.0"]]
:java-source-paths ["src/main/java"]
:test-paths ["src/test/clojure"])
The pom:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>quicktest</groupId>
<artifactId>discontinuities</artifactId>
<version>0.1</version>
</project>
Run with:
mvn compile
lein deps
lein test
Results
The flaw in the function is quickly found:
FAIL in (test-discontinuities) (test_discontinuities.clj:13)
expected: (<= y x)
actual: (not (<= Infinity 5))
{:test-var "test-discontinuities",
:result false,
:seed 1431128331945,
:failing-size 23,
:num-tests 24,
:fail [5],
:shrunk {:total-nodes-visited 3, :depth 0, :result false, :smallest [5]}}
Related
I'm used to using Clojure with Leiningen.
But I have a new project that I booted into existence with LightMod. And it uses CLI tools and deps.
This works but I now want to add some unit testing to it.
So here's (a slightly simplified version of) my deps.edn file :
{:paths ["src" "resources"],
:aliases
{
:dev
{:extra-deps
{orchestra #:mvn{:version "2018.12.06-2"},
expound #:mvn{:version "0.7.2"},
nightlight #:mvn{:version "RELEASE"},
com.bhauman/figwheel-main #:mvn{:version "0.2.0"}},
:main-opts ["dev.clj"]},
:test
{:extra-paths ["test"]
:extra-deps {com.cognitect/test-runner {:git/url "https://github.com/cognitect-labs/test-runner.git" :sha "209b64504cb3bd3b99ecfec7937b358a879f55c1"}}
:main-opts ["-m" "cognitect.test-runner"]}
:prod
{:extra-deps
{leiningen #:mvn{:version "2.9.0"},
org.clojure/clojurescript #:mvn{:version "1.10.439"}},
:main-opts ["prod.clj"]},
:app
{:extra-deps
{
markdown-clj {:mvn/version "1.10.1"}
instaparse {
:mvn/version"1.4.10"
}
... MORE ...
}}}}
Now this file has worked OK with both :dev and :prod compilation.
But now I've just added the :test alias.
And put my tests into test/myns/core_tests.clj
However, when I try to run tests, I get this
Could not locate markdown/core__init.class, markdown/core.clj or markdown/core.cljc on classpath
So my interpretation. While the :dev and the :prod branches are successfully pulling in all the dependencies in the :app branch. The :test branch is not pulling in markdown. (And probably none of the other dependencies declared in :app
So why should this be? And how can I explicitly tell my :test clause that it needs to use all the same dependencies that are used in :dev and :prod?
You can use the Kaocha test runner for this. A working template project can be found here. It even includes the ability to compile Java source files as well as Clojure.
To get started, set up deps.edn like so
{:paths ["src/clj"]
:deps {
cambium/cambium.codec-simple {:mvn/version "0.9.3"}
cambium/cambium.core {:mvn/version "0.9.3"}
cambium/cambium.logback.core {:mvn/version "0.4.3"}
org.clojure/clojure {:mvn/version "1.10.1"}
prismatic/schema {:mvn/version "1.1.12"}
tupelo {:mvn/version "0.9.200"}
}
:aliases {:test {
:extra-deps {lambdaisland/kaocha {:mvn/version "1.0-612"}}
}}
}
and add another file tests.edn to configure Kaocha:
; ***** Be sure to use tagged reader literal '#kaocha/v1' *****
#kaocha/v1 {:tests
[{:id :unit
:test-paths ["test/clj"]
:ns-patterns ["^tst..*"]
}]
; :reporter kaocha.report.progress/report
; :plugins [:kaocha.plugin/profiling :kaocha.plugin/notifier]
}
then create a short shell script to launch Koacha in the subdir ./bin. Name it bin/kaocha with contents:
#!/bin/bash
clojure -A:test -m kaocha.runner "$#"
Now you are ready to go! A test file:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test) )
(dotest
(is= 5 (+ 2 3))
(isnt= 9 (+ 2 3))
(throws? (/ 1 0)) ; verify that an illegal operation throws an exception
(is= 3 (add2 1 2))
(throws? (add2 1 "two"))) ; Prismatic Schema will throw since "two" is not a number
; NOTE: Clojure Deps/CLI can't handle Java source code at present
and we are off to the races:
~/io.tupelo/clj-template > bin/kaocha
[(.....)]
1 tests, 5 assertions, 0 failures.
In my clojure project I built several java classes using gen-class command. They are [extractor.yaml YAMLExtractor YAMLExtractorFactory]. I wanted now build unit test against those classes but I have error: java.lang.ClassNotFoundException: extractor.yaml.YAMLExtractor when I run test.
File which cause the error is:
yaml_extrator_factory.clj
(ns extractor.yaml-extractor-factory
(:gen-class :name extractor.yaml.YAMLExtractorFactory
:extends org.apache.any23.extractor.SimpleExtractorFactory
:implements [org.apache.any23.extractor.ExtractorFactory]
:init init
:constructors {[] [String org.apache.any23.rdf.Prefixes java.util.Collection String]}
:main false)
(:require [extractor.yaml-extractor])
(:import [extractor.yaml YAMLExtractor]
[org.apache.any23.rdf Prefixes]
[org.apache.any23.extractor SimpleExtractorFactory ExtractorFactory Extractor ExtractionContext ]))
The error occurs only during testing. Whole project is AOT-compilable with no error and it is fine when I build a jar file as well.
The test simple.clj contains head:
(ns extractor.simple
(:use [clojure.tools.logging :as log]
[clojure.java.io :as jio]
[clojure.test :as test])
(:require [extractor.yaml-extractor-factory])
(:import [java.util Arrays]
[extractor.yaml.YAMLExtractorFactory]))
And test which prints CLASSPATH. yaml-extractor-factory was not used.
Test is run with command:
boot aot -a update-classpath run-test -t extractor.simple
where task update-classpath adds (get-env :directories) into classpath
and run-test runs a test. run-test works fine with normal clojure code.
run-test is my task with following content:
(deftask run-test "Run unit tests"
[t test-name NAME str "Test to execute. Run all tests if not given."]
(require '[extractor.simple])
(with-pass-thru [_]
(if (nil? test-name)
(do
(util/info "Run all tests")
(test/run-all-tests))
(do
(util/info (format "Run test: %s" (:test-name *opts*)))
(test/run-tests (symbol (:test-name *opts*)))
))))
Is it possible to grab the project information within the clojure repl?
For example, if there was a project defined:
(defproject blahproject "0.1.2" ....)
When running a repl in the project directory, is there a function like this?
> (project-version)
;=> 0.1.2
While you can parse project.clj yourself, this may be annoying. It's also a lot of work. Instead, you can just do:
(System/getProperty "projectname.version")
Leiningen project files are just Clojure data :)
(-> "/path/to/project.clj" slurp read-string (nth 2))
I use environ (https://github.com/weavejester/environ) which sucks in settings from a number of sources, including system properties. The project version appears as :<project-name>-version:
foobar.repl=> (require '[environ.core :refer [env]])
nil
foobar.repl=> (:foobar-version env)
"0.1.0-SNAPSHOT"
Add the below code to the end of project.clj:
(def project (assoc-in project [:repl-options :init]
`(~'def ~'project-version ~(project :version))))
Now you will have a var called project-version in the initial namespace for the repl.
As described in this discussion.
(ns myproject.example
(:require [clojure.java.io :as io])
(:import java.util.Properties))
(defn get-version [dep]
(let [path (str "META-INF/maven/" (or (namespace dep) (name dep))
"/" (name dep) "/pom.properties")
props (io/resource path)]
(when props
(with-open [stream (io/input-stream props)]
(let [props (doto (Properties.) (.load stream))]
(.getProperty props "version"))))))
(get-version 'myproject) ; => 0.1.0
(get-version 'org.clojure/clojure) ; => 1.3.0
As vemv said, Leiningen project files are just Clojure data. So, it's easy to access your project as an ordinary hash-map:
(->> "project.clj"
slurp
read-string
(drop 2)
(cons :version)
(apply hash-map)
(def project))
If you need this variable only in your repl, you can add it to repl-options to your project.clj:
(defproject yourproject "0.1.0"
:description ""
:url ""
:dependencies [ [org.clojure/clojure "1.4.0"]]
:repl-options { :init (->> "project.clj"
slurp
read-string
(drop 2)
(cons :version)
(apply hash-map)
(def project))})
Now, you have project variable in your repl. So, to access the version of your project you can simply type (:version project).
Of course, you can simply use native Leiningen code to parse you project file:
(defproject yourproject "0.1.0"
:description ""
:url ""
:dependencies [ [org.clojure/clojure "1.4.0"]
[leiningen-core "2.1.3"]]
:repl-options { :init (do (require 'leiningen.core.project)
(def project
(leiningen.core.project/read)))})
But, if you need only the version of your project and nothing more, then it's best to use Ankur's solution.
For a more fully-featured approach, you might want to take a look at the configleaf plugin for Leiningen (https://github.com/davidsantiago/configleaf). It will make the project map, with active profiles merged in, available to project code in a namespace of your choosing.
In case you need to do this from clojurescript you could create a macro (from another clj file) and call it from the cljs code :
;;ex: macro.clj
(defmacro get-project-version []
(System/getProperty "penelope.version"))
;;my_logic_code.cljs
(ns my-logic-code
(:require-macros [macros :as m]))
(def project-version (m/get-project-version))
I have a clojure application called mr1; its project.clj is located in ./mr1/, and mr1.clj is located in ./mr1/src.
I cannot figure out how to structure the directories, namespace, and project.clj file correctly, so I can build a binary using cake bin.
Enclosed are my current project.clj and the head and tail of mr1.clj.
src/mr1.clj loads fine into cake repl and executes. cake bin produces an mr1 file that contains no -main.
(defproject mr1 "0.0.1-SNAPSHOT"
:description "TODO: add summary of your project"
:dependencies [[org.clojure/clojure "1.3.0"]
[org.clojure/tools.cli "0.1.0"]]
:main mr1)
(ns mr1
(:use [clojure.string :only [split]]
[clojure.string :only [join]]))
.
.
.
(defn -main
[& args]
(do
(reset! grid-dim (prompt-for-grid-dim))
(reset! mr1-pos (prompt-for-rover-pos 1))
(let [moves (prompt-for-rover-moves)]
(execute-each-move moves #mr1-pos))
(reset! mr2-pos (prompt-for-rover-pos 2))
(let [moves (prompt-for-rover-moves)]
(execute-each-move moves #mr2-pos))
)
)
As I mentioned in answer to your first question, you have to enable AOT-compilation of your namespace. I've copied that answer here in case if someone would have same problem.
As sample.project.clj file says, :main key should have as an assigned value a namespace which contains -main function. So you should have such function
(defn -main [& args]
(do-things-you-want-to-do-on-program-start))
in your mr1.clj. Also AFAIR if you want to use your program as a standalone jar you have to have this namespace gen-classed. By this I mean that you have to:
Include :gen-class option in your namespace definition like this:
(ns mr1
(:gen-class)
...other options...)
Make the namespace AOT-compiled (AOT stands for Ahead Of Time). To do this you need to specify your namespace in the list of AOT-compiled namespaces in project.clj:
(defproject mr1 "0.0.1-SNAPSHOT"
...other definitions...
:aot [mr1]
:main mr1)
After you've done this, you can use cake to generate executable jar for you.
When unit testing some code that translates ascii sequences into unicode characters I have found a problem with the output of Clojure tests.
I have tested that my terminal can output unicode characters (by cat-ing the test files) and that works fine, so the problem seems related to leiningen, Clojure or clojure.test somehow.
Here's an example test (using the Greek section of unicode - I will also be using Greek extended but I assume the same problems will apply):
(deftest bc-string-w-comma
(is (= "αβγ, ΑΒΓ" (parse "abg,*a*b*g"))))
It is meant to fail due to the missing space in the input. The output from lein test is the following:
Testing parse_perseus.test.betacode
FAIL in (bc-string-w-comma) (betacode.clj:15)
expected: (= "???, ???" (parse "abg,*a*b*g"))
actual: (not (= "???, ???" "???,???"))
Testing parse_perseus.test.core
Testing parse_perseus.test.pluralise
Ran 10 tests containing 59 assertions.
1 failures, 0 errors.
What am I doing wrong here? Is this a terminal emulation problem or something clojure-related? I have the same problem running code in the REPL with Slime/swank/emacs. The REPL in emacs only outputs question marks for unicode output (although emacs is quite capable of understanding unicode).
I have tried running this in Terminal and iTerm (OS X) with the same results.
It turns out that you can pass options to java to force the output encoding of *out* so that unicode works, like this:
java -Dfile.encoding=utf-8 -cp lib/clojure-1.2.0.jar:lib/clojure-contrib-1.2.0.jar clojure.main -i src/whatever.clj
As I'm using Leiningen, I added this property to my project.clj file:
(defproject project_name "1.0.0-SNAPSHOT"
:description "A Clojure Project"
:dependencies [[org.clojure/clojure "1.2.0"]
[org.clojure/clojure-contrib "1.2.0"]]
:dev-dependencies [[swank-clojure "1.2.0"]]
:jvm-opts ["-Dfile.encoding=utf-8"])
Clojure itself seems in the clear (this is Ubuntu 10.10, gnome-terminal, OpenJDK):
john#woc-desktop$ java -cp /home/john/.m2/repository/org/clojure/clojure/1.2.0/clojure-1.2.0.jar:/home/john/.m2/repository/org/clojure/clojure-contrib/1.2.0/clojure-contrib-1.2.0.jar clojure.main
Clojure 1.2.0
user=> (use 'clojure.test)
nil
user=> (defn parse [s] "αβγ,ΑΒΓ")
#'user/parse
user=> (deftest greek (is (= "αβγ, ΑΒΓ" (parse ""))))
#'user/greek
user=> (run-tests)
Testing user
FAIL in (greek) (NO_SOURCE_FILE:3)
expected: (= "αβγ, ΑΒΓ" (parse ""))
actual: (not (= "αβγ, ΑΒΓ" "αβγ,ΑΒΓ"))
Ran 1 tests containing 1 assertions.
1 failures, 0 errors.
{:type :summary, :test 1, :pass 0, :fail 1, :error 0}
user=>
But it does break emacs/swank/clojure-maven-plugin/maven
at REPL in emacs:
> (is "αβγ""αβγ")
slime-net-send: Coding system iso-latin-1-unix not suitable for "000052(:emacs-rex (swank:listener-eval \"(is \\\"αβγ\\\"\\\"αβγ\\\")
\") \"user\" :repl-thread 33)
"
If I use maven, the simple pom file below, and mvn clojure:repl then it's ok:
[INFO] [clojure:repl {execution: default-cli}]
Clojure 1.2.0
user=> (use 'clojure.test) (is "αβγ""αβγ")
nil
"αβγ"
user=> (defn parse [s] "αβγ,ΑΒΓ")
#'user/parse
user=> (deftest greek (is (= "αβγ, ΑΒΓ" (parse ""))))
#'user/greek
user=> (run-tests)
Testing user
FAIL in (greek) (NO_SOURCE_FILE:3)
expected: (= "αβγ, ΑΒΓ" (parse ""))
actual: (not (= "αβγ, ΑΒΓ" "αβγ,ΑΒΓ"))
Ran 1 tests containing 1 assertions.
1 failures, 0 errors.
{:type :summary, :test 1, :pass 0, :fail 1, :error 0}
user=>
but if I add the jline library using this snippet:
<dependency>
<groupId>jline</groupId>
<artifactId>jline</artifactId>
<version>0.9.94</version>
</dependency>
then I get:
[INFO] [clojure:repl {execution: default-cli}]
[INFO] Enabling JLine support
Clojure 1.2.0
user=> (use 'clojure.test) (is "αβγ""αβγ")
nil
"���"
user=> (defn parse [s] "αβγ,ΑΒΓ")
#'user/parse
user=> (deftest greek (is (= "αβγ, ΑΒΓ" (parse ""))))
#'user/greek
user=> (run-tests)
Testing user
FAIL in (greek) (NO_SOURCE_FILE:3)
expected: (= "���, ���" (parse ""))
actual: (not (= "���, ���" "���,���"))
Ran 1 tests containing 1 assertions.
1 failures, 0 errors.
{:type :summary, :test 1, :pass 0, :fail 1, :error 0}
user=>
Which looks awfully like your error. So it may be that the problem is in jLine, or some other piece which Leiningen and maven have in common which is associated with jLine.
Or of course, there may be two independent unicode-related failures.
Here is my maven pom.xml file in case anyone is trying to debug this.
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.aspden</groupId>
<artifactId>maven-clojure-simple</artifactId>
<version>1.0-SNAPSHOT</version>
<name>maven-clojure-simple</name>
<description>maven, clojure: simple project</description>
<repositories>
<repository>
<id>clojure</id>
<url>http://build.clojure.org/releases</url>
</repository>
<repository>
<id>central</id>
<url>http://repo1.maven.org/maven2</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.clojure</groupId>
<artifactId>clojure</artifactId>
<version>1.2.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.theoryinpractise</groupId>
<artifactId>clojure-maven-plugin</artifactId>
<version>1.3.5-SNAPSHOT</version>
</plugin>
</plugins>
</build>
</project>
I appreciate this is not an answer, but i thought it might be helpful.