Slide 1

Slide 1 text

΍ͬͯΈΔʂDMPKVSFTQFD DMKOBLBOP !BUIPT

Slide 2

Slide 2 text

ࣗݾ঺հ ‣ 5XJUUFS!BUIPT ‣ (JU)VCBUIPT ‣ χϟϯύεגࣜձࣾॴଐ ‣ $MPKVSFίϯτϦϏϡʔλ

Slide 3

Slide 3 text

ࠓ೔ͷ಺༰ ‣ DMPKVSFTQFDೖ໳ എܠ DMPKVSFTQFDͱ͸ TQFDͷجຊతͳ࢖͍ํ ‣ DMPKVSFTQFDΛ΍ͬͯΈΔ ؆୯ͳαϯϓϧϓϩδΣΫτͷதͰ࢖ͬͯΈ·͠ΐ͏

Slide 4

Slide 4 text

DMPKVSFTQFDೖ໳

Slide 5

Slide 5 text

എܠɿ$MPKVSFͷ՝୊ ‣ ؔ਺ΛͲ͏΍ͬͯ࢖͏ͷ͔෼͔Γʹ͍͘ʜ Ҿ਺ʹԿΛड͚औΔͷ͔ɺ໭Γ஋ͱͯ͠ԿΛฦ͢ͷ͔ $MPKVSFͰ͸ͨͩͷϚοϓ΍ϕΫλΛଟ༻͢ΔͷͰɺɹ Ͳ͏͍͏஋͕ظ଴͞Ε͍ͯΔͷ͔൑ผ͠ʹ͍͘ ‣ Τϥʔϝοηʔδ͕෼͔Γʹ͍͘ʜ $MPKVSFͷؔ਺͸جຊతʹ(BSCBHF*O(BSCBHF0VU ޡͬͨೖྗʹରͯ͠͸ɺ+7.ͷΤϥʔ͕ͦͷ··ग़Δ ͜ͱ΋ଟ͍

Slide 6

Slide 6 text

DMPKVSFTQFDͱ͸ ‣ $MPKVSFͰಋೖ͞Εͨ৽ػೳ ‣ ड़ޠϕʔεͰσʔλܕ΍ؔ਺ͷ࢓༷Λهड़͠ɺͦΕΛνΣοΫ͢Δ࢓૊Έ ੩తݕࠪͰ͸ͳ͘ɺܖ໿ϓϩάϥϛϯάʹ͍ۙ ‣ Ұ౓࢓༷ εϖοΫ Λॻ͚͹։ൃͷ༷ʑͳ৔໘Ͱར༻Մ υΩϡϝϯςʔγϣϯ όϦσʔγϣϯ σʔλੜ੒ ϓϩύςΟϕʔεςετ ϚΫϩͷߏจνΣοΫ

Slide 7

Slide 7 text

s/valid? ‣ (s/valid? <εϖοΫ> <஋>) ஋͕εϖοΫΛຬ͍ͨͯ͠Δ͔ΛνΣοΫ͢Δ (require ‘[clojure.spec.alpha :as s]) (s/valid? int? 42) ;; => true (s/valid? int? :foo) ;; => false

Slide 8

Slide 8 text

ड़ޠ͸εϖοΫʹͳΔ ‣ ೚ҙͷड़ޠ ਅِ஋Λฦؔ͢਺ ΛεϖοΫͱͯ͠࢖͑Δ ‣ ૊ࠐΈؔ਺͚ͩͰͳ͘Ϣʔβఆٛؔ਺Ͱ΋ແ໊ؔ਺Ͱ΋Մ (s/valid? string? “foo”) ;; => true (s/valid? #(> % 10) 11) ;; => true

Slide 9

Slide 9 text

εϖοΫʹ໊લΛ͚ͭΔ ‣ s/defͰεϖοΫʹ໊લΛ͚ͭΔ͜ͱ͕Ͱ͖Δ ‣ εϖοΫΛ࠶ར༻Ͱ͖Δ (s/def ::id int?) (s/def :person/name string?) (s/valid? ::id 42) ;; => true (s/valid? :person/name “Rich Hickey”) ;; => true

Slide 10

Slide 10 text

ू߹΋εϖοΫ ‣ ू߹#{e1 … en}͸e1 ʜ en ͷ͍ͣΕ͔ͱ౳͍͠Α͏ͳ ஋Λද͢εϖοΫ ‣ ڞ༻ମͷΑ͏ͳ஋Λදݱ͢Δͷʹศར (s/valid? #{0 1 2} 3) ;; => false (s/valid? #{0 1 2} 2) ;; => true

Slide 11

Slide 11 text

ίϨΫγϣϯͷεϖοΫ ‣ s/coll-of ΍ s/map-ofͰίϨΫγϣϯͱͦͷཁૉ ͷεϖοΫΛද͢͜ͱ͕Ͱ͖Δ (s/def ::nums (s/coll-of int?)) (s/def ::vals (s/map-of keyword? int?)) (s/valid? ::nums [3 1 4]) ;; => true (s/valid? ::vals {:a 0 :b 1}) ;; => true

Slide 12

Slide 12 text

ϚοϓͷεϖοΫ ‣ s/keysͰΩʔʹΑΓ஋ͷܕ͕ҧ͏ϚοϓΛදݱͰ͖Δ ‣ :req-unͰඞਢͷΩʔɺ:opt-unͰΦϓγϣφϧͳΩʔΛࢦఆ ͢Δ (s/def ::id int?) (s/def ::name string?) (s/def ::gender #{:male :female}) (s/def ::person (s/keys :req-un [::id ::name] :opt-un [::gender]) (s/valid? ::person {:id 1 :name “athos” :gender :male}) ;; => true (s/valid? ::person {:id 1 :name “athos”}) ;; => true

Slide 13

Slide 13 text

s/explain ‣ (s/explain <εϖοΫ> <஋>) ஋͕εϖοΫΛຬ͍ͨͯ͠ͳ͍৔߹ʹɺͲ͕͜Ͳ͏ޡͬͯ ͍Δ͔Λࢦఠͯ͘͠ΕΔ ग़ྗܗࣜ͸ΧελϚΠζͰ͖Δ (s/def ::id int?) (s/def ::name string?) (s/def ::person (s/keys :req-un [::id ::name])) (s/explain ::person {:id “42”}) ;; In: [:id] val: "42" fails spec: :user/id at: [:id] predicate: int? ;; val: {:id "42"} fails spec: :user/person predicate: (contains? % :name) ˡ:id͸int?ͩͱݴ͍ͬͯΔ ˡ:name͕ͳ͍ͱݴ͍ͬͯΔ

Slide 14

Slide 14 text

s/gen ‣ (s/gen <εϖοΫ>) εϖοΫΛຬͨ͢ϥϯμϜͳ஋Λੜ੒͢ΔδΣωϨʔλΛ ฦ͢ UFTUDIFDLHFOFSBUPST౳Λ࢖ͬͯϥϯμϜͳ஋Λੜ੒Ͱ͖Δ (require ‘[clojure.test.check.generators :as gen]) (s/gen (s/coll-of int?)) ;; => δΣωϨʔλ (gen/generate (s/gen (s/coll-of int?))) ;; => [-163 -1669986 346763815 11309055] (gen/generate (s/gen (s/coll-of int?))) ;; => [-116969371 -262061 95 10599611 36137714]

Slide 15

Slide 15 text

ؔ਺ͷεϖοΫ ‣ ؔ਺ͷҾ਺͓Αͼ໭Γ஋ͷεϖοΫΛهड़Ͱ͖Δ ‣ :fnͰҾ਺ͱ໭Γ஋ͷؒͷؔ܎ʹ͍ͭͯ΋نఆͰ͖Δ (defn square [x] (* x x)) (s/fdef square :args (s/cat :x int?) :ret int?)

Slide 16

Slide 16 text

st/instrument ‣ st/instrumentͰؔ਺ͷҾ਺͕εϖοΫΛຬ͍ͨͯ͠Δ͔ ͷνΣοΫ͕༗ޮʹͳΔ ‣ ։ൃ࣌ɾςετ࣌ʹ༗ޮʹ͓ͯ͘͠ͱศར (require ‘[clojure.spec.test.alpha :as st]) (st/instrument `square) (square 3) ;; => 9 (square :foo) ;; ExceptionInfo Call to #'user/square did not conform to spec: ;; In: [0] val: :foo fails at: [:args :x] predicate: int? ;; clojure.core/ex-info (core.clj:4739)

Slide 17

Slide 17 text

st/check ‣ Ҿ਺ͷεϖοΫ͔ΒϥϯμϜͳೖྗΛ࡞ͬͯؔ਺ʹ౉͠ɺ ໭Γ஋͕໭Γ஋ͷεϖοΫΛຬ͔ͨ͢Ͳ͏͔ΛνΣοΫ (st/check `square) ;; ({:spec #object[clojure.spec.alpha$fspec_impl$reify__2451 0x7caa6a11 "clojure.spec.alpha$fspec_impl$reify__2451@7caa6a11" ], :clojure.spec.test.check/ret {:result #error { :cause "integer overflow" :via [{:type java.lang.ArithmeticException :message "integer overflow" :at [clojure.lang.Numbers throwIntOverflow "Numbers.java" 1526]}] …

Slide 18

Slide 18 text

ࢀߟɿsquareͷగਖ਼൛ ‣ *͸MPOHͷൣғͷ੔਺ಉ࢜ͷֻ͚ࢉ͸Φʔόʔϑϩʔ͢Δ Մೳੑ͕͋ΔͷͰ୅ΘΓʹ*’Λ࢖͏ ‣ #JH*OU͸JOU Ͱ͸ͳ͍ͷͰJOUFHFS Λ࢖͏ (defn square [x] (*’ x x)) (s/fdef square :args (s/cat :x int?) :ret integer?)

Slide 19

Slide 19 text

DMPKVSFTQFDΛ΍ͬͯΈΔ

Slide 20

Slide 20 text

՝୊ɿλεΫ؅ཧ ‣ ΠϯϝϞϦͰ3&1-͔Β࢖͏λεΫ؅ཧ ‣ IUUQTHJUIVCDPNBUIPTTQFDFYBNQMF ‣ ࢓༷ λεΫ͸*%ͱઆ໌ EFTDSJQUJPO ɺঢ়ଶ TUBUVT Λ࣋ͭ λεΫϦετ͸ݸҎ্ͷλεΫΛอ࣋Ͱ͖Δ λεΫϦετʹ*%Λࢦఆͯ͠λεΫΛऔಘͨ͠ΓɺλεΫͷઆ ໌΍ঢ়ଶΛมߋͨ͠ΓͰ͖Δ λεΫͷঢ়ଶ͸QFOEJOH͔EPOFͷ͍ͣΕ͔ ॳظ͸QFOEJOH

Slide 21

Slide 21 text

͸͡Ί͔ͨ ‣ 3&1-Λىಈ ‣ ΋͘͠͸ ‣ 3&1-ͰҎԼΛೖྗ $ clj -Adev $ lein repl (goto ‘spec-example.todo)

Slide 22

Slide 22 text

λεΫͷεϖοΫ (ns spec-example.todo (:require [clojure.spec.alpha :as s])) (s/def ::id nat-int?) (s/def ::description string?) (s/def ::status #{:pending :done}) (s/def ::task (s/keys :req-un [::id ::description ::status])) (s/def ::items (s/map-of ::id ::task)) (s/def ::task-list (s/keys :req-un [::items])) (def empty-task-list {:items {}})

Slide 23

Slide 23 text

λεΫΛੜ੒ͯ͠ΈΔ (require ‘[clojure.test.check.generators :as gen]) (gen/generate (s/gen ::task)) ;; => {:id 19861176, :description “bVRFEN89TC", :status :done} (gen/generate (s/gen ::task)) ;; => {:id 16, :description “n18x5AwDH0Ej0yyvyw1u8FLv", :status :done}

Slide 24

Slide 24 text

λεΫϦετΛੜ੒ͯ͠ΈΔ (require ‘[clojure.test.check.generators :as gen]) (gen/generate (s/gen ::task-list)) ;; => {:items {87201 {:id 1, :description "8J6aNkRHe1dPSc8mtp0AMKKg4", :status :pending}, 58 {:id 196, :description "juPASwWa55URSp21", :status :done}, 47466 {:id 3, :description "F3O9", :status :pending}, 5 {:id 37, :description "kTN7H180rUcqHg9sZ29699mJu14c", :status :done}, 61 {:id 2529932, :description "eLO00", :status :done}, 811 {:id 206, :description "3gyMilIqz0aw", :status :pending}, 35 {:id 28457067, :description "RRxYbrL38Uj42kR1MiJ2S8qMZ2t", :status :pending}}}

Slide 25

Slide 25 text

add-task (defn add-task [tasks description] (let [id (count (:items tasks)) task {:id id :description description :status :pending}] (assoc-in tasks [:items id] task))) (s/fdef add-task :args (s/cat :tasks ::task-list :description ::description) :ret ::task-list)

Slide 26

Slide 26 text

add-taskΛinstrumentͯ͠ΈΔ (require ‘[clojure.spec.test.alpha :as st]) (st/instrument `add-task) (add-task empty-task-list “buy milk”) ;; => {:items {0 {:id 0, :description “buy milk”, :status :pending}}} (add-task empty-task-list :buy-milk) ;; ExceptionInfo Call to #'spec-example.todo/add-task did not conform to spec: ;; In: [1] val: :buy-milk fails spec: :spec-example.todo/description at: [:args :description] predicate: string? ;; clojure.core/ex-info (core.clj:4739)

Slide 27

Slide 27 text

add-taskΛcheckͯ͠ΈΔ (st/check `add-task) ;; => ({:spec #object[clojure.spec.alpha$fspec_impl$reify __2451 0x5e62054e “clojure.spec.alpha$fspec_impl$reify__2451@ 5e62054e"], :clojure.spec.test.check/ret {:result true, :num-tests 1000, :seed 1521012250499}, :sym spec-example.todo/add-task})

Slide 28

Slide 28 text

checkΛ௨ͯ͠ΈΔ ‣ count-tasksͱall-tasksʹਖ਼͍͠εϖοΫΛఆٛ͠ ͯɺcheckΛ௨ͯ͠Έ·͠ΐ͏ (defn count-tasks [tasks] (count (:items tasks))) (defn all-tasks [tasks] (sequence (vals (:items tasks))))

Slide 29

Slide 29 text

checkΛ௨ͯ͠ΈΔ ‣ count-tasksͱall-tasksʹਖ਼͍͠εϖοΫΛఆٛ͠ ͯɺcheckΛ௨ͯ͠Έ·͠ΐ͏ (s/fdef count-tasks :args (s/cat :tasks ::task-list) :ret int?) (s/fdef all-tasks :args (s/cat :tasks ::task-list) :ret (s/coll-of ::task))

Slide 30

Slide 30 text

͕࣌ؒ͋Ε͹ ϓϩύςΟϕʔεςετͷ঺հ

Slide 31

Slide 31 text

·ͱΊ ‣ DMPKVSFTQFDͰσʔλ΍ؔ਺ͷεϖοΫΛఆٛ͢Δ͜ͱ Ͱɺ$MPKVSFͰΑΓݎ࿚ͳίʔυΛॻ͚ΔΑ͏ʹ ‣ Ұ౓εϖοΫΛఆ͓ٛͯ͘͠ͱɺ։ൃ࣌ɾςετ࣌ͳͲ ༷ʑͳ৔໘ͰεϖοΫΛԠ༻͢Δ͜ͱ͕Ͱ͖Δ ‣ ͨͩ͠ɺ৽͔ͭ͘͠ಠಛͷػೳͳͨΊɺ·ͩ·ͩϕετ ϓϥΫςΟε΋ݻ·͍ͬͯͳ͍෦෼΋ଟ͍ ‣ ΈΜͳͰҰॹʹ΍͍͖ͬͯ·͠ΐ͏

Slide 32

Slide 32 text

͓͢͢ΊϥΠϒϥϦ ‣ 0SDIFTUSBinstrumentͰ໭Γ஋΋νΣοΫͯ͘͠ΕΔ IUUQTHJUIVCDPNKFBZFPSDIFTUSB ‣ UFTUDIVDLUFTUDIFDLΛ࢖͏ͱ͖ͷϢʔςΟϦςΟू IUUQTHJUIVCDPNHGSFEFSJDLTUFTUDIVDL ‣ &YQPVOEFYQMBJOͷ݁ՌΛݟ΍͘͢੔ܗͯ͘͠ΕΔ IUUQTHJUIVCDPNCICFYQPVOE ‣ 1JOQPJOUFSಉ্ IUUQTHJUIVCDPNBUIPT1JOQPJOUFS

Slide 33

Slide 33 text

ϦϯΫू ‣ DMPKVSFTQFDͷ֓ཁ ೔ຊޠʂ IUUQTKBQBODMPKVSJBOTHJUIVCJPDMPKVSFTJUFKBBCPVUTQFDIUNM ‣ DMPKVSFTQFDΨΠυ IUUQTDMPKVSFPSHHVJEFTTQFD ‣ 1SPHSBNNJOH$MPKVSF SEFE IUUQTQSBHQSPHDPNCPPLTIDMPKQSPHSBNNJOHDMPKVSFUIJSEFEJUJPO