2018/3/14のclj-nakano #5の資料です。
ͬͯΈΔʂDMPKVSFTQFDDMKOBLBOP!BUIPT
View Slide
ࣗݾհ‣ 5XJUUFS!BUIPT‣ (JU)VCBUIPT‣ χϟϯύεגࣜձࣾॴଐ‣ $MPKVSFίϯτϦϏϡʔλ
ࠓͷ༰‣ DMPKVSFTQFDೖ എܠ DMPKVSFTQFDͱ TQFDͷجຊతͳ͍ํ‣ DMPKVSFTQFDΛͬͯΈΔ ؆୯ͳαϯϓϧϓϩδΣΫτͷதͰͬͯΈ·͠ΐ͏
DMPKVSFTQFDೖ
എܠɿ$MPKVSFͷ՝‣ ؔΛͲ͏ͬͯ͏ͷ͔͔Γʹ͍͘ʜ ҾʹԿΛड͚औΔͷ͔ɺΓͱͯ͠ԿΛฦ͢ͷ͔ $MPKVSFͰͨͩͷϚοϓϕΫλΛଟ༻͢ΔͷͰɺɹͲ͏͍͏͕ظ͞Ε͍ͯΔͷ͔ผ͠ʹ͍͘‣ Τϥʔϝοηʔδ͕͔Γʹ͍͘ʜ $MPKVSFͷؔجຊతʹ(BSCBHF*O(BSCBHF0VU ޡͬͨೖྗʹରͯ͠ɺ+7.ͷΤϥʔ͕ͦͷ··ग़Δ͜ͱଟ͍
DMPKVSFTQFDͱ‣ $MPKVSFͰಋೖ͞Εͨ৽ػೳ‣ ड़ޠϕʔεͰσʔλܕؔͷ༷Λهड़͠ɺͦΕΛνΣοΫ͢ΔΈ ੩తݕࠪͰͳ͘ɺܖϓϩάϥϛϯάʹ͍ۙ‣ Ұ༷ εϖοΫΛॻ͚։ൃͷ༷ʑͳ໘Ͱར༻Մ υΩϡϝϯςʔγϣϯ όϦσʔγϣϯ σʔλੜ ϓϩύςΟϕʔεςετ ϚΫϩͷߏจνΣοΫ
s/valid?‣ (s/valid? <εϖοΫ> <>) ͕εϖοΫΛຬ͍ͨͯ͠Δ͔ΛνΣοΫ͢Δ(require ‘[clojure.spec.alpha :as s])(s/valid? int? 42);; => true(s/valid? int? :foo);; => false
ड़ޠεϖοΫʹͳΔ‣ ҙͷड़ޠ ਅِΛฦؔ͢ΛεϖοΫͱͯ͑͠Δ‣ ࠐΈ͚ؔͩͰͳ͘ϢʔβఆٛؔͰແ໊ؔͰՄ(s/valid? string? “foo”);; => true(s/valid? #(> % 10) 11);; => true
εϖοΫʹ໊લΛ͚ͭΔ‣ s/defͰεϖοΫʹ໊લΛ͚ͭΔ͜ͱ͕Ͱ͖Δ‣ εϖοΫΛ࠶ར༻Ͱ͖Δ(s/def ::id int?)(s/def :person/name string?)(s/valid? ::id 42);; => true(s/valid? :person/name “Rich Hickey”);; => true
ू߹εϖοΫ‣ ू߹#{e1 … en}e1 ʜ enͷ͍ͣΕ͔ͱ͍͠Α͏ͳΛද͢εϖοΫ‣ ڞ༻ମͷΑ͏ͳΛදݱ͢Δͷʹศར(s/valid? #{0 1 2} 3);; => false(s/valid? #{0 1 2} 2);; => true
ίϨΫγϣϯͷεϖοΫ‣ 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
ϚοϓͷεϖοΫ‣ 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
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/personpredicate: (contains? % :name)ˡ:idint?ͩͱݴ͍ͬͯΔˡ:name͕ͳ͍ͱݴ͍ͬͯΔ
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]
ؔͷεϖοΫ‣ ؔͷҾ͓ΑͼΓͷεϖοΫΛهड़Ͱ͖Δ‣ :fnͰҾͱΓͷؒͷؔʹ͍ͭͯنఆͰ͖Δ(defn square [x](* x x))(s/fdef square:args (s/cat :x int?):ret int?)
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)
st/check‣ ҾͷεϖοΫ͔ΒϥϯμϜͳೖྗΛ࡞ͬͯؔʹ͠ɺΓ͕ΓͷεϖοΫΛຬ͔ͨ͢Ͳ͏͔ΛνΣοΫ(st/check `square);; ({:spec#object[clojure.spec.alpha$fspec_impl$reify__24510x7caa6a11"[email protected]"], :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]}] …
ࢀߟɿsquareͷగਖ਼൛‣ *MPOHͷൣғͷಉ࢜ͷֻ͚ࢉΦʔόʔϑϩʔ͢ΔՄೳੑ͕͋ΔͷͰΘΓʹ*’Λ͏‣ #JH*OUJOU Ͱͳ͍ͷͰJOUFHFS Λ͏(defn square [x](*’ x x))(s/fdef square:args (s/cat :x int?):ret integer?)
DMPKVSFTQFDΛͬͯΈΔ
՝ɿλεΫཧ‣ ΠϯϝϞϦͰ3&1-͔Β͏λεΫཧ‣ IUUQTHJUIVCDPNBUIPTTQFDFYBNQMF‣ ༷ λεΫ*%ͱઆ໌ EFTDSJQUJPOɺঢ়ଶ TUBUVTΛ࣋ͭ λεΫϦετݸҎ্ͷλεΫΛอ࣋Ͱ͖Δ λεΫϦετʹ*%Λࢦఆͯ͠λεΫΛऔಘͨ͠ΓɺλεΫͷઆ໌ঢ়ଶΛมߋͨ͠ΓͰ͖Δ λεΫͷঢ়ଶQFOEJOH͔EPOFͷ͍ͣΕ͔ ॳظQFOEJOH
͡Ί͔ͨ‣ 3&1-Λىಈ‣ ͘͠‣ 3&1-ͰҎԼΛೖྗ$ clj -Adev$ lein repl(goto ‘spec-example.todo)
λεΫͷεϖοΫ(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 {}})
λεΫΛੜͯ͠ΈΔ(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}
λεΫϦετΛੜͯ͠ΈΔ(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}}}
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)
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 tospec:;; In: [1] val: :buy-milk fails spec: :spec-example.todo/description at:[:args :description] predicate: string?;; clojure.core/ex-info (core.clj:4739)
add-taskΛcheckͯ͠ΈΔ(st/check `add-task);; =>({:spec#object[clojure.spec.alpha$fspec_impl$reify__2451 0x5e62054e“[email protected]5e62054e"],:clojure.spec.test.check/ret{:result true,:num-tests 1000,:seed 1521012250499},:sym spec-example.todo/add-task})
checkΛ௨ͯ͠ΈΔ‣ count-tasksͱall-tasksʹਖ਼͍͠εϖοΫΛఆٛͯ͠ɺcheckΛ௨ͯ͠Έ·͠ΐ͏(defn count-tasks [tasks](count (:items tasks)))(defn all-tasks [tasks](sequence (vals (:items tasks))))
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))
͕࣌ؒ͋ΕϓϩύςΟϕʔεςετͷհ
·ͱΊ‣ DMPKVSFTQFDͰσʔλؔͷεϖοΫΛఆٛ͢Δ͜ͱͰɺ$MPKVSFͰΑΓݎ࿚ͳίʔυΛॻ͚ΔΑ͏ʹ‣ ҰεϖοΫΛఆ͓ٛͯ͘͠ͱɺ։ൃ࣌ɾςετ࣌ͳͲ༷ʑͳ໘ͰεϖοΫΛԠ༻͢Δ͜ͱ͕Ͱ͖Δ‣ ͨͩ͠ɺ৽͔ͭ͘͠ಠಛͷػೳͳͨΊɺ·ͩ·ͩϕετϓϥΫςΟεݻ·͍ͬͯͳ͍෦ଟ͍‣ ΈΜͳͰҰॹʹ͍͖ͬͯ·͠ΐ͏
͓͢͢ΊϥΠϒϥϦ‣ 0SDIFTUSBinstrumentͰΓνΣοΫͯ͘͠ΕΔIUUQTHJUIVCDPNKFBZFPSDIFTUSB‣ UFTUDIVDLUFTUDIFDLΛ͏ͱ͖ͷϢʔςΟϦςΟूIUUQTHJUIVCDPNHGSFEFSJDLTUFTUDIVDL‣ &YQPVOEFYQMBJOͷ݁ՌΛݟ͘͢ܗͯ͘͠ΕΔIUUQTHJUIVCDPNCICFYQPVOE‣ 1JOQPJOUFSಉ্IUUQTHJUIVCDPNBUIPT1JOQPJOUFS
ϦϯΫू‣ DMPKVSFTQFDͷ֓ཁ ຊޠʂIUUQTKBQBODMPKVSJBOTHJUIVCJPDMPKVSFTJUFKBBCPVUTQFDIUNM‣ DMPKVSFTQFDΨΠυIUUQTDMPKVSFPSHHVJEFTTQFD‣ 1SPHSBNNJOH$MPKVSF SEFEIUUQTQSBHQSPHDPNCPPLTIDMPKQSPHSBNNJOHDMPKVSFUIJSEFEJUJPO