Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Clojure.pdf
Скачиваний:
17
Добавлен:
09.05.2015
Размер:
12.92 Mб
Скачать

Chapter 9

Clojure in the Wild

Now that you have learned the basics of the Clojure language, it is time for you to begin using Clojure in your own projects. But as you run out the door to start work on your killer Clojure app, you realize that you do not yet know how to use Clojure for everyday programming tasks:

How do I write unit tests for my Clojure code?

How do I access relational data from Clojure?

How do I build a simple web application in Clojure?

These questions do not have a simple answer because there are so many choices. With Clojure, you have access to the entire world of Java APIs for data access, XML, GUI development, web development, testing, graphics, and more. In addition, idiomatic Clojure libraries are beginning to appear.

There are so many choices, and innovation is proceeding so swiftly, that I will not attempt to cover all the possible choices. Instead, I will simply open Pandora’s box and show you a few possible answers to the earlier questions. In this chapter, you will learn how to do the following:

Build unit tests with the test-is library

Access relational data with JDBC and Clojure’s sql library

Create web applications with the Compojure framework

These examples demonstrate Clojure’s value in a wide variety of settings and should whet your appetite for the exciting world of Clojure development.

Prepared exclusively for WG Custom Motorcycles

AUTOMATING TESTS 266

9.1 Automating Tests

Clojure makes it easy to test your code in the REPL. But for any nontrivial codebase, manual testing at the REPL is not enough. You should also have a set of self-validating tests. Self-validating tests report success or failure, so you do not have to interpret results manually by reading off values at the REPL. This section introduces two testing libraries: the test function in the Clojure core and clojure.contrib.test-is.

Test with :test

Clojure attaches tests to functions with :test metadata. To see :test in action, consider the index-of-any function introduced in Section 2.6, Where’s My for Loop?, on page 70. The Java version includes the following examples in the method comment:

StringUtils.indexOfAny(null, *)

= -1

StringUtils.indexOfAny("" , *)

= -1

StringUtils.indexOfAny(*, null)

= -1

StringUtils.indexOfAny(*, [])

= -1

StringUtils.indexOfAny("zzabyycdxx" ,['z' ,'a' ]) = 0

StringUtils.indexOfAny("zzabyycdxx" ,['b' ,'y' ]) = 3

StringUtils.indexOfAny("aba" , ['z' ])

= -1

In Clojure, you can add these tests as metadata on the index-of-any function:

Download examples/index_of_any.clj

(defn index-filter [pred coll]

(when pred (for [[idx elt] (indexed coll) :when (pred elt)] idx)))

(defn

#^{:test (fn []

(assert (nil? (index-of-any #{\a} nil))) (assert (nil? (index-of-any #{\a} "" )))

(assert (nil? (index-of-any nil "foo" )))

(assert (nil? (index-of-any #{} "foo" )))

(assert (zero? (index-of-any #{\z \a} "zzabyycdxx" )))

(assert (= 3 (index-of-any #{\b \y} "zzabyycdxx" )))

(assert (nil? (index-of-any #{\z} "aba" ))))}

index-of-any [pred coll]

(first (index-filter pred coll)))

The :test metadata key takes as its value a test function. In the previous example, the test function exercises index-of-any, using (assert expr) to test that various exprs are true.

Prepared exclusively for WG Custom Motorcycles

Report erratum

this copy is (P1.0 printing, May 2009)

AUTOMATING TESTS 267

You can run the tests from the REPL using the test function:

(test a-var)

test looks up the test function in a-var’s :test metadata and runs the test. Note that test takes a var, not a symbol. Try testing index-of-any:

(test #'index-of-any)

:ok

To see what happens when a test fails, create a test with an assertion that is not true:

Download examples/exploring.clj

(defn

#^{:test (fn []

(assert (nil? (busted))))} busted [] "busted" )

(test #'busted)

java.lang.Exception: Assert failed: (nil? (busted))

:test is simple and easy to use, and it keeps your tests as close as possible to the code they test. However, most developers are accustomed to frameworks that keep tests separate from code. Clojure-contrib provides this via the test-is library.

Test with test-is

Stuart Sierra’s test-is library lets you write tests with the deftest macro:

(clojure.contrib.test-is/deftest testname & forms)

The test forms make assertions via the is macro:

(clojure.contrib.test-is/is form message?)

form is any predicate, and message is an optional error message. You could begin testing index-of-any thusly:

Download examples/test/index_of_any.clj

(ns examples.test.index-of-any

(:use examples.index-of-any clojure.contrib.test-is))

(deftest test-index-of-any-with-nil-args (is (nil? (index-of-any #{\a} nil))) (is (nil? (index-of-any nil "foo" ))))

(deftest test-index-of-any-with-empty-args (is (nil? (index-of-any #{\a} "" )))

(is (nil? (index-of-any #{} "foo" ))))

Prepared exclusively for WG Custom Motorcycles

Report erratum

this copy is (P1.0 printing, May 2009)

AUTOMATING TESTS 268

(deftest test-index-of-any-with-match

(is (zero? (index-of-any #{\z \a} "zzabyycdxx" )))

(is (= 3 (index-of-any #{\b \y} "zzabyycdxx" ))))

(deftest test-index-of-any-without-match (is (nil? (index-of-any #{\z} "aba" ))))

In the previous section’s :test example, the test function contained seven assertions. Here, those seven assertions are divided into four different tests. This is a matter of taste. Since deftest gives you the chance to create more than one test for the same function, it encourages you to group smaller sets of assertions under meaningful test names.

You can run all tests in a group of namespaces with run-tests:

(clojure.contrib.test-is/run-tests & namespaces)

If you do not specify any namespaces, run-tests will default to the current namespace. Assuming you entered the index-of-any tests at the REPL, you could run them with this:

(run-tests) Testing user

Ran 4 tests 7 assertions. 0 failures, 0 exceptions.

To see a test fail, create a test with a failing assertion:

Download examples/test/fail.clj

(deftest test-that-demonstrates-failure (is (= 5 (+ 2 2))))

Testing user

FAIL in (test-that-demonstrates-failure) (NO_SOURCE_FILE:5) expected: (= 5 (+ 2 2))

actual: (not= 5 4)

Ran 1 tests containing 1 assertions. 1 failures, 0 errors.

Unlike assert, is can also take an optional error message. When a test fails, is appends the optional error message after the generic error message:

Download examples/test/fail.clj

(deftest test-that-demonstrates-error-message (is (= 3 Math/PI) "PI is an integer!?" ))

Prepared exclusively for WG Custom Motorcycles

Report erratum

this copy is (P1.0 printing, May 2009)

AUTOMATING TESTS 269

Testing user

FAIL in (test-that-demonstrates-error-message) (NO_SOURCE_FILE:2) PI is an integer!?

expected: (= 3 Math/PI)

actual: (not= 3 3.141592653589793)

The is macro can, of course, also be used inside a :test function.

It is particularly important that tests cover less-used paths through code, such as error handling. To this end, you can use the (is (thrown? )) form to test that an exception occurred:

Download examples/test/exploring.clj

(deftest test-divide-by-zero

(is (thrown? ArithmeticException (/ 5 0))))

thrown? is an example of a custom assert expression. You can add your own assert expressions to test-is; see the source code for the assert-expr multimethod for details.

The example code for this book is tested using test-is. The tests demonstrate several features that are not covered here; see the examples/test and lancet/test directories for more examples using test-is.

Test However You Want

Clojure’s test capabilities are lightweight and easy to use. What if you want something that integrates with an existing build system and runs on your continuous integration box?

If you have an existing Java infrastructure, another answer is “Test with whatever framework you already use.” Clojure code is Java code.

All Clojure sequences implement Java collection interfaces. All Clojure functions implement Callable and Runnable. So, you can write your tests with any Java-compatible test framework: JUnit, TestNG, or even EasyB or JTestR. Good luck, and please consider open sourcing any reusable pieces you invent along the way.

Next, let’s take a look at how Clojure can access relational data.

Prepared exclusively for WG Custom Motorcycles

Report erratum

this copy is (P1.0 printing, May 2009)