@jessitron
es!
Tests!
Clojure!
Contracts
in
Contracts and Clojure:
the Best-Yet Compromise
Between
Types and Tests
Philly ETE, 7 April 2015
What do we know?
How do we know it?
…
This code works!
Formal
Proofs
Informal
Reasoning
Experimental
Evidence
def formatReport(data: ReportData): ExcelSheet
// Scala
types
(defn format-report [report-data]
…)
(defn ad-performance-report [params]
(-> (fetch-events params)
(analyze-ad-performance params)
format-report))
(defn ad-performance-report [params]
(-> (fetch-events params)
(analyze-ad-performance params)
format-report))
(defn analyze-ad-performance [events params]
(-> events
(group-up params)
summarize
add-total-row
(add-headers params)))
{:when 12:34:56 7/8/90
:what "show"
:who "abc123"}
{:when org.joda.time.DateTime
:what java.lang.String
:who java.lang.String}
(def Event
)
(def Event
)
{:when DateTime
:what s/Str
:who s/Str}
(:require [schema.core :as s])
(def Event
)
{:when DateTime
:what Incident
:who Customer}
(:require [schema.core :as s])
Event
(def Event
)
{:when DateTime
:what Incident
:who Customer}
(:require [schema.core :as s])
[Event]
[Event]
(defn ad-performance-report [params]
(-> (fetch-events params)
(analyze-ad-performance params)
format-report))
[Event]
…)
(s/defn fetch-events [params]
[Event]
(:require [schema.core :as s])
…)
(s/defn fetch-events
[params]
:-
(deftest fetch-events-test
…
(= (expected (fetch-events input))))
(deftest fetch-events-test
…
(= (expected (fetch-events input))))
(use-fixtures schema.test/validate-schemas)
(def Event
)
{:when DateTime
:what Incident
:who Customer}
(:require [schema.core :as s])
[Event]
(def Event
)
{:when DateTime
:what Incident
:who Customer}
(:require [schema.core :as s])
[Event] [[Event]]
[Event] [[Event]][Event]
[Event] [[Event]]
[Event]
[Event] [[Event]]
[[Event] Summation]
[Event]
[( one [Event] )
( one Summation )]
[[Event]]
[Event]
[(s/one [Event] "event list")
(s/one Summation "group sum")]
[[Event]]
(def Group
)
[Event]
[(s/one [Event] "event list")
(s/one Summation "group sum")]
[[Event]] [Group]
[Event] [[Event]]
{:groups [Group]}
[Event] [[Event]]
{:groups [Group]
:totals Totals}
[Event] [[Event]]
{:header Headers
:groups [Group]
:totals Totals}
[Event] [[Event]]
{:header Headers
:groups [Group]
:totals Totals}
(def ReportData
)
{:header Headers
:groups [Group]
:totals Totals}
(def ReportData
)
ReportData(s/defn analyze-ad-performance :-
[events :-
params :- Params]
(-> events
(group-up params)
summarize
add-total-row
(add-headers params)))
[Event]
(deftest analyze-ad-performance-test
(testing "grouping of rows"
(let […
result (analyze-ad-performance
events
{})
(is (= expected (:groups result))))))
(deftest analyze-ad-performance-test
(testing "grouping of rows"
(let […
result (analyze-ad-performance
events
{})
(is (= expected (:groups result))))))
(use-fixtures schema.test/validate-schemas)
result (analyze-ad-performance
events
{})
(is (= expected (:groups result))))))
Input does not match schema Params
Missing required key :title
Missing required key :start
Missing required key :end
:title string
:start date
:end date
Params
tle string
art date
nd date
:title string
:start date
:end date
param-gen
n't empty
end date
now
:title string
:start date
:end date
:title string
:start date
:end date
param-gen
:title string that isn't empty
:start date before end date
:end date before now
:
:
:
param-gen
(deftest analyze-ad-performance-test
(testing "grouping of rows"
(let […
result (analyze-ad-performance
[events]
(sample-one param-gen))
(is (= expected (:groups result))))))
(defspec analyze-ad-performance-spec 100
(for-all [events events-gen
params param-gen]
(analyze-ad-performance events params))))
Q: What do we know?
A: Schemas
Q: How do we know it?
A: Generative Tests
Q: What do we know?
data shape
We live in this weird time where a rose by any other name
throws a compile/runtime error. @deech
Q: What do we know?
data shape
data value boundaries
:title
- string
- not empty
- capitalized
Headers
(def Headers
{:title
(s/both
s/Str
(s/pred (complement empty?) "nonempty")
(s/pred capitalized? "Title Caps"))
…})
Q: What do we know?
data shape
value boundaries
relationships within values
{:title …
:start DateTime
:end DateTime}
- start is before end
Headers
Q: What could we know?
produced types
(s/def params :- (generator-of Params)
(gen/hash-map
:title …
:start …
:end …))
what
if?
(defgen params Params (gen/hash-map
:title …
:start …
:end …))
what
if?
(defgen params Params (gen/hash-map
:title …
:start …
:end …))
what
if?
Generator: my-project.generators/params
Value: {:end #<DateTime -58693684-08-30T03:25:35.104Z>, :
Error: (not (keyword? a-clojure.lang.PersistentArrayMap))
(s/defn fetch-events :- [Event]
[params]
…)
(s/defn fetch-events :- (lazy-seq-of Event)
[params]
…)
what
if?
Generator of A
Function from A to B
Lazy sequence of A
Q: What could we know?
produced types
relationships between types
(s/defn sample-one [A :- Schema]
(s/fn :- A [g :- (generator-of A)]
(last (gen/sample g))))
((sample-one Params) params-gen)
(tdefn sample-one :- A
[A :- Schema
g :- (generator-of A)]
(last (gen/sample g)))
what
if?
(sample-one Params param-gen)
(sample-one param-gen)
what
if?
(tdefn add-headers :- (merge A
{:header Headers})
[data-so-far :- [A :< AnyMap]
params])
what
if?
(add-headers data params)
(add-headers GroupsWithTotal data params)
(tdefn add-headers :- (merge A
{:header Headers})
[data-so-far :- [A :< AnyMap]
params])
Q: What could we know?
produced types
relationships between types
relationships between values
(defn group-up [events params]
{:post [(as-lazy-as events %)]
…))
relationships between types
produced types
relationships between values
data shape
data value boundaries
relationships within values
(def Event {:when DateTime
:what Incident
:who Customer})
(s/defn fetch-events :- [Event]
[params]
…)
(def Event {:when DateTime
:what Incident
:who Customer})
(s/defn fetch-events :- [t/Event]
[params]
…)
(:require
[my-project.schemas :as t])
my-project.schemas
implementation
schemas
implementation
schemas
src
(def params (hash-map :title gen/string-alpha-numeric
:start (datetime-before (now))
:end (datetime-before (now))))
(defspec analyze-ad-performance-spe
(for-all [events mygen/events
params mygen/params]
(analyze-ad-performance events
(:require
[my-project.gen :as mygen])
my-project.gen
tests
generators
test
tests
generators
test
tests
generators
test
tests
generators
implementation
schemas
src
test
tests
generators
implementation
schemas
src
test
tests
generators
implementation
schemas
src
src test
client
test
tests
generators
implementation
schemas
src
src test
client
implementation
schemas generators
tests
implementation
schemas generators
tests
implementation
schemas
generators
tests
implementation
schemas
generators
tests
implementation
schemas
generators
tests
src test
client
implementation
schemas generators
tests
src test
implementation
schemas
generators
tests
implementation
schemas
generators
tests
src test
client
testkit
implementation
schemas
generators
tests
testkit
implementation
schemas
generators
tests
testkit
implementation
schemas
generators
tests
testkit
implementation
schemas
generators
tests
Executable Specifications
what do we know?
how do we know it?
Science!
test.check
prismatic/schema
Clojure
Science!
generative tests
types and contracts
… your language …
Science!
Science!
in-memory test tools
native API definitions
… your language …
everyone!forScience!
Formal
Proofs
Informal
Reasoning
Experimental
Evidence
@jessitron
blog.jessitron.com
https://github.com/jessitron/contracts-as-types-examples
https://github.com/jessitron/slack-client
examples
resources
https://github.com/Prismatic/schema
https://github.com/miner/herbert (value relationships)
http://david-mcneil.com/post/114783282473/
extending-prismatic-schema-to-higher-order
https://github.com/jessitron/schematron
http://dl.acm.org/citation.cfm?id=2661156
Static typing and productivity: Stefik & Hanenberg 2014

Contracts in-clojure-pete