Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
@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-...
(deftest analyze-ad-performance-test
(testing "grouping of rows"
(let […
result (analyze-ad-performance
events
{})
(is (= ...
(deftest analyze-ad-performance-test
(testing "grouping of rows"
(let […
result (analyze-ad-performance
events
{})
(is (= ...
result (analyze-ad-performance
events
{})
(is (= expected (:groups result))))))
Input does not match schema Params
Missing...
: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-o...
(defspec analyze-ad-performance-spec 100
(for-all [events events-gen
params param-gen]
(analyze-ad-performance events para...
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: {:e...
(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-ge...
(tdefn sample-one :- A
[A :- Schema
g :- (generator-of A)]
(last (gen/sample g)))
what
if?
(sample-one Params 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 Heade...
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 wit...
(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-proj...
implementation
schemas
implementation
schemas
src
(def params (hash-map :title gen/string-alpha-numeric
:start (datetime-before (now))
:end (datetime-before (now))))
(defsp...
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-...
Contracts in-clojure-pete
Contracts in-clojure-pete
Contracts in-clojure-pete
Contracts in-clojure-pete
Contracts in-clojure-pete
Upcoming SlideShare
Loading in …5
×

Contracts in-clojure-pete

3,194 views

Published on

Contracts in Clojure: the best-yet compromise between types and tests.
As delivered at Philly ETE, April 7, 2015

Published in: Software

Contracts in-clojure-pete

  1. 1. @jessitron es! Tests! Clojure! Contracts in
  2. 2. Contracts and Clojure: the Best-Yet Compromise Between Types and Tests Philly ETE, 7 April 2015
  3. 3. What do we know? How do we know it? … This code works!
  4. 4. Formal Proofs Informal Reasoning Experimental Evidence
  5. 5. def formatReport(data: ReportData): ExcelSheet // Scala
  6. 6. types
  7. 7. (defn format-report [report-data] …)
  8. 8. (defn ad-performance-report [params] (-> (fetch-events params) (analyze-ad-performance params) format-report))
  9. 9. (defn ad-performance-report [params] (-> (fetch-events params) (analyze-ad-performance params) format-report))
  10. 10. (defn analyze-ad-performance [events params] (-> events (group-up params) summarize add-total-row (add-headers params)))
  11. 11. {:when 12:34:56 7/8/90 :what "show" :who "abc123"}
  12. 12. {:when org.joda.time.DateTime :what java.lang.String :who java.lang.String} (def Event )
  13. 13. (def Event ) {:when DateTime :what s/Str :who s/Str} (:require [schema.core :as s])
  14. 14. (def Event ) {:when DateTime :what Incident :who Customer} (:require [schema.core :as s]) Event
  15. 15. (def Event ) {:when DateTime :what Incident :who Customer} (:require [schema.core :as s]) [Event]
  16. 16. [Event] (defn ad-performance-report [params] (-> (fetch-events params) (analyze-ad-performance params) format-report))
  17. 17. [Event] …) (s/defn fetch-events [params]
  18. 18. [Event] (:require [schema.core :as s]) …) (s/defn fetch-events [params] :-
  19. 19. (deftest fetch-events-test … (= (expected (fetch-events input))))
  20. 20. (deftest fetch-events-test … (= (expected (fetch-events input)))) (use-fixtures schema.test/validate-schemas)
  21. 21. (def Event ) {:when DateTime :what Incident :who Customer} (:require [schema.core :as s]) [Event]
  22. 22. (def Event ) {:when DateTime :what Incident :who Customer} (:require [schema.core :as s]) [Event] [[Event]]
  23. 23. [Event] [[Event]][Event]
  24. 24. [Event] [[Event]] [Event]
  25. 25. [Event] [[Event]] [[Event] Summation]
  26. 26. [Event] [( one [Event] ) ( one Summation )] [[Event]]
  27. 27. [Event] [(s/one [Event] "event list") (s/one Summation "group sum")] [[Event]]
  28. 28. (def Group ) [Event] [(s/one [Event] "event list") (s/one Summation "group sum")] [[Event]] [Group]
  29. 29. [Event] [[Event]] {:groups [Group]}
  30. 30. [Event] [[Event]] {:groups [Group] :totals Totals}
  31. 31. [Event] [[Event]] {:header Headers :groups [Group] :totals Totals}
  32. 32. [Event] [[Event]] {:header Headers :groups [Group] :totals Totals} (def ReportData )
  33. 33. {:header Headers :groups [Group] :totals Totals} (def ReportData )
  34. 34. ReportData(s/defn analyze-ad-performance :- [events :- params :- Params] (-> events (group-up params) summarize add-total-row (add-headers params))) [Event]
  35. 35. (deftest analyze-ad-performance-test (testing "grouping of rows" (let [… result (analyze-ad-performance events {}) (is (= expected (:groups result))))))
  36. 36. (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)
  37. 37. 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
  38. 38. :title string :start date :end date Params
  39. 39. tle string art date nd date :title string :start date :end date param-gen
  40. 40. n't empty end date now :title string :start date :end date :title string :start date :end date param-gen
  41. 41. :title string that isn't empty :start date before end date :end date before now : : : param-gen
  42. 42. (deftest analyze-ad-performance-test (testing "grouping of rows" (let [… result (analyze-ad-performance [events] (sample-one param-gen)) (is (= expected (:groups result))))))
  43. 43. (defspec analyze-ad-performance-spec 100 (for-all [events events-gen params param-gen] (analyze-ad-performance events params))))
  44. 44. Q: What do we know? A: Schemas Q: How do we know it? A: Generative Tests
  45. 45. Q: What do we know? data shape
  46. 46. We live in this weird time where a rose by any other name throws a compile/runtime error. @deech
  47. 47. Q: What do we know? data shape data value boundaries
  48. 48. :title - string - not empty - capitalized Headers
  49. 49. (def Headers {:title (s/both s/Str (s/pred (complement empty?) "nonempty") (s/pred capitalized? "Title Caps")) …})
  50. 50. Q: What do we know? data shape value boundaries relationships within values
  51. 51. {:title … :start DateTime :end DateTime} - start is before end Headers
  52. 52. Q: What could we know? produced types
  53. 53. (s/def params :- (generator-of Params) (gen/hash-map :title … :start … :end …)) what if?
  54. 54. (defgen params Params (gen/hash-map :title … :start … :end …)) what if?
  55. 55. (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))
  56. 56. (s/defn fetch-events :- [Event] [params] …)
  57. 57. (s/defn fetch-events :- (lazy-seq-of Event) [params] …) what if?
  58. 58. Generator of A Function from A to B Lazy sequence of A
  59. 59. Q: What could we know? produced types relationships between types
  60. 60. (s/defn sample-one [A :- Schema] (s/fn :- A [g :- (generator-of A)] (last (gen/sample g)))) ((sample-one Params) params-gen)
  61. 61. (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)
  62. 62. what if? (tdefn add-headers :- (merge A {:header Headers}) [data-so-far :- [A :< AnyMap] params])
  63. 63. what if? (add-headers data params) (add-headers GroupsWithTotal data params) (tdefn add-headers :- (merge A {:header Headers}) [data-so-far :- [A :< AnyMap] params])
  64. 64. Q: What could we know? produced types relationships between types relationships between values
  65. 65. (defn group-up [events params] {:post [(as-lazy-as events %)] …))
  66. 66. relationships between types produced types relationships between values data shape data value boundaries relationships within values
  67. 67. (def Event {:when DateTime :what Incident :who Customer}) (s/defn fetch-events :- [Event] [params] …)
  68. 68. (def Event {:when DateTime :what Incident :who Customer}) (s/defn fetch-events :- [t/Event] [params] …) (:require [my-project.schemas :as t]) my-project.schemas
  69. 69. implementation schemas
  70. 70. implementation schemas src
  71. 71. (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
  72. 72. tests generators
  73. 73. test tests generators
  74. 74. test tests generators
  75. 75. test tests generators implementation schemas src
  76. 76. test tests generators implementation schemas src
  77. 77. test tests generators implementation schemas src src test client
  78. 78. test tests generators implementation schemas src src test client
  79. 79. implementation schemas generators tests
  80. 80. implementation schemas generators tests
  81. 81. implementation schemas generators tests
  82. 82. implementation schemas generators tests
  83. 83. implementation schemas generators tests src test client
  84. 84. implementation schemas generators tests src test
  85. 85. implementation schemas generators tests
  86. 86. implementation schemas generators tests src test client
  87. 87. testkit implementation schemas generators tests
  88. 88. testkit implementation schemas generators tests
  89. 89. testkit implementation schemas generators tests
  90. 90. testkit implementation schemas generators tests
  91. 91. Executable Specifications what do we know? how do we know it?
  92. 92. Science! test.check prismatic/schema Clojure
  93. 93. Science! generative tests types and contracts … your language … Science!
  94. 94. Science! in-memory test tools native API definitions … your language … everyone!forScience!
  95. 95. Formal Proofs Informal Reasoning Experimental Evidence
  96. 96. @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

×