技术控

    今日:126| 主题:49289
收藏本版 (1)
最新软件应用技术尽在掌握

[其他] Interactive Development with Clojure.spec

[复制链接]
溫柔﹌散了場 发表于 2016-10-6 02:14:09
218 11

立即注册CoLaBug.com会员,免费获得投稿人的专业资料,享用更多功能,玩转个人品牌!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
clojure.spec provides seamless integration with      clojure.test.check's generators. Write a spec, get a functioning generator, and you can use that generator in a REPL as you're developing, or in a generative test.   
    To explore this, we'll use clojure.spec to specify a scoring function for Codebreaker, a game based on an old game called Bulls and Cows, a predecessor to the board game, Mastermind. You might recognize this exercise if you've read      The RSpec Book, however this will be a bit different.   
    Although I'll explain some things as I go along, I'm going to assume that you're already familiar with the      clojure.spec Rationale and Overviewand the      spec Guide.   
    If you like, you can follow along by evaluating the forms (in order of appearance - some redef vars that are def'd earlier in the namespace) in      codebreaker.clj.   
        Problem

    We want a function that accepts a secret code and a guess, and returns a score for that guess. Codes are made of 4 to 6 colored pegs, selected from six colors: [r]ed, [y]ellow, [g]reen, [c]yan, lack, and [w]hite. The score is based on the number of pegs in the guess that match the secret code. A peg in the guess that matches the color of the peg in the same position in the secret code is considered an exact match, and a peg that matches a peg in a different position in the secret code is considered a loose match.
    For example, if the secret code is      [:r :y :g :c]and the guess is      [:c :y :g :b], the score would be      {:codebreaker/exact-matches 2 :codebreaker/loose-matches 1}because      :yand      :gappear in the same positions and      :cappears in a different position.   
    We want to invoke this fn with two codes and get back a map like the one above, e.g.
   
  1. (score [:r :y :g :c] [:c :y :g :r])
  2. ;; {:codebreaker/exact-matches 2
  3. ;;  :codebreaker/loose-matches 2}
复制代码
   Properties and property-based testing

    In property-based testing, we make assertions about properties of a function and provide test data generators. The testing tool then generates test data, applies the function to it, and invokes the assertions. Properties are more general than examples in example based tests. For example, rather than writing a test that expresses the example above and asserts that the resulting map looks exactly like the one above, we'd write expressions that express more general properties like:
   
          
  • The return value should be a map with the two keys        :codebreaker/exact-matchesand        :codebreaker/loose-matches      
  • the values should be natural (i.e. non-negative) integers      
  • the sum of the values should be        >=0      
  • the sum of the values should be        <=data-preserve-html-node="true" the number of pegs in the secret code   
    We'll express all of these properties using clojure.spec, and we're also going to describe the arguments to the function using the same tooling. This is one way in clojure.spec departs from other property-based testing tools.
    First, in English:
   
          
  • there are two arguments      
  • the arguments should both be        codes
               
    • a code is a sequence of 4 to 6 colored pegs         
    • the available colors are red, yellow, green, cyan, black, and white, represented by            :r,            :y,            :g,            :c,            :b, and            :w         
    • a code may contain duplicates        
                
  • the two codes should be of equal length   
    :args

    We have a few more questions to answer, but that's enough to get started, which we'll do with a function spec. We'll start with just the spec for the arguments, and a couple of supporting definitions.
   
  1. (ns codebreaker
  2.   (:require [clojure.spec :as s]
  3.             [clojure.spec.test :as stest]))
  4. (def peg? #{:y :g :r :c :w :b})
  5. (s/def ::code (s/coll-of peg? :min-count 4 :max-count 6))
  6. (s/fdef score
  7.         :args (s/cat :secret ::code :guess ::code))
复制代码
   Now we can exercise the      :argsspec:   
   
  1. (s/exercise (:args (s/get-spec `score)))
  2. ;; ([([:y :w :g :y :c] [:c :g :y :y :y :c]) {:secret [:y :w :g :y :c], :guess [:c :g :y :y :y :c]}]
  3. ;;  [([:c :w :g :r :g] [:r :b :c :r :w :g]) {:secret [:c :w :g :r :g], :guess [:r :b :c :r :w :g]}]
  4. ;;  ...
  5. ;;  [([:r :c :w :w :y :r] [:y :r :y :y :c]) {:secret [:r :c :w :w :y :r], :guess [:y :r :y :y :c]}]
  6. ;;  [([:c :g :b :g :w :b] [:r :y :w :r :b]) {:secret [:c :g :b :g :w :b], :guess [:r :y :w :r :b]}])
复制代码
         s/exercisereturns a collection of tuples of a value generated by the generator associated with the spec, and the same value conformed by the spec. This can give us a lot of confidence that the spec expresses what we think it does and that the generator produces the values we expect. The generator we get for free from the      :argsspec is sufficient, so we don't need to explicitly define one.   
    The output reveals that we didn't yet specify one of the properties described earlier: the two codes should be of equal length. So let's specify that:
   
  1. (s/fdef score
  2.         :args (s/and (s/cat :secret ::code :guess ::code)
  3.                      (fn [{:keys [secret guess]}]
  4.                        (= (count secret) (count guess)))))
  5. (s/exercise (:args (s/get-spec `score)))
  6. ;; ([([:w :w :y :b :c] [:c :b :y :w :c]) {:secret [:w :w :y :b :c], :guess [:c :b :y :w :c]}]
  7. ;;  [([:y :g :b :r :b] [:b :y :r :b :r]) {:secret [:y :g :b :r :b], :guess [:b :y :r :b :r]}]
  8. ;;  ...
  9. ;;  [([:y :w :g :w :g] [:y :c :c :y :y]) {:secret [:y :w :g :w :g], :guess [:y :c :c :y :y]}]
  10. ;;  [([:b :w :c :r :c :w] [:b :g :r :y :y :g]) {:secret [:b :w :c :r :c :w], :guess [:b :g :r :y :y :g]}])
复制代码
   :ret

    Now the      :argsspec represents the properties we laid out above, so let's move on to spec the      :retspec.   
   
  1. (s/def ::exact-matches nat-int?)
  2. (s/def ::loose-matches nat-int?)
  3. (s/fdef score
  4.         :args (s/and (s/cat :secret ::code :guess ::code)
  5.                      (fn [{:keys [secret guess]}]
  6.                        (= (count secret) (count guess))))
  7.         :ret (s/keys :req [::exact-matches ::loose-matches]))
  8. (s/exercise (:ret (s/get-spec `score)))
  9. ;; ([{:codebreaker/exact 0, :codebreaker/loose 1} {:codebreaker/exact 0, :codebreaker/loose 1}]
  10. ;;  [{:codebreaker/exact 0, :codebreaker/loose 0} {:codebreaker/exact 0, :codebreaker/loose 0}]
  11. ;;  ...
  12. ;;  [{:codebreaker/exact 0, :codebreaker/loose 10} {:codebreaker/exact 0, :codebreaker/loose 10}]
  13. ;;  [{:codebreaker/exact 0, :codebreaker/loose 15} {:codebreaker/exact 0, :codebreaker/loose 15}])
复制代码
   Again, we see tuples of a generated value and the same value conformed by the spec. And here we see that the map keys are correct but the map values may exceed the number of pegs in the code, violating one of the properties we laid out earlier: the sum of the values in the returned map should be between 0 and the count of either of the codes. The values generated by the      :retspec are always      >= 0because they are spec'd with      nat-int?, and their sum is therefore always      >= 0, but we can't specify that the sum is      <=data-preserve-html-node="true" the number of pegs without knowing the number of pegs, and that information is in the      :argsspec, which is not exposed to the      :retspec.   
    :fn

    For relationships between      :argsand      :retvalues, we use a      :fnspec:   
   
  1. (s/fdef score
  2.         :args (s/and (s/cat :secret ::code :guess ::code)
  3.                      (fn [{:keys [secret guess]}]
  4.                        (= (count secret) (count guess))))
  5.         :ret (s/keys :req [::exact-matches ::loose-matches])
  6.         :fn (fn [{{secret :secret} :args ret :ret}]
  7.               (<= 0 (apply + (vals ret)) (count secret))))
复制代码
   Here we're choosing to explicitly specify that the sum of the values is      <= 0even though it's already specified implicitly by the      nat-int?predicates we used to specify the values in the returned map. This is not necessary, but it clearly does a better job of expressing the properties we described earlier.   
    So now we have specs for the      :args, the      :retvalue, and the relationship between them (in the      :fnspec). So we're done, right? Well, not quite. We still don't have a function!   
    Wire it up

    We need a function to tie it all together. Here's a skeletal implementation:
   
  1. (defn score [secret guess]
  2.   {::exact-matches 0
  3.    ::loose-matches 0})
  4. (s/exercise-fn `score)
  5. ;; ([([:g :w :w :c :y :g] [:b :c :g :w :c :w]) {:codebreaker/exact-matches 0, :codebreaker/loose-matches 0}]
  6. ;;  [([:y :r :w :c :b] [:b :b :c :r :c]) {:codebreaker/exact-matches 0, :codebreaker/loose-matches 0}]
  7. ;;  ...
  8. ;;  [([:r :w :r :y :r :r] [:g :w :g :y :r :r]) {:codebreaker/exact-matches 0, :codebreaker/loose-matches 0}]
  9. ;;  [([:y :c :r :c] [:y :r :g :c]) {:codebreaker/exact-matches 0, :codebreaker/loose-matches 0}])
复制代码
   This is incomplete, but we can see that the generated args and the function's return value match the specs, including the      :fnspec. So now let's use clojure.spec's test/check wrapper to actually test the function:   
   
  1. (stest/check `score)
  2. ;; ...
  3. ;; :clojure.spec.test.check/ret {:result true, :num-tests 1000, :seed 1471029622166},
复制代码
   This happens to pass because the 0 values conform to the      ::exact-matchesand      ::loose-matchesspecs, and their sum conforms to the      :retspec. We can validate that the test is actually testing what we think it is by providing hard coded values that would not conform to the spec, e.g.   
   
  1. (defn score [secret guess]
  2.   {::exact-matches 4
  3.    ::loose-matches 3})
  4. (s/exercise-fn `score)
  5. ;; ([([:y :w :b :g :g] [:c :c :r :r :y]) {:codebreaker/exact-matches 4, :codebreaker/loose-matches 3}]
  6. ;;  [([:y :y :b :g :c] [:r :b :w :y :g]) {:codebreaker/exact-matches 4, :codebreaker/loose-matches 3}]
  7. ;;  ...
  8. ;;  [([:r :w :y :g] [:y :r :y :g]) {:codebreaker/exact-matches 4, :codebreaker/loose-matches 3}])
  9. ;;     (stest/check `score)
  10. ;;       :clojure.spec.test.check/ret {:result #error {
  11. ;;  :cause "Specification-based check failed"
  12. ;;  :data {:clojure.spec/problems
  13. ;;         [{:path [:fn]
  14. ;;           :pred (fn [{{secret :secret} :args, ret :ret}]
  15. ;;                   (<= 0 (apply + (vals ret)) (count secret)))
  16. ;;           :val {:args {:secret [:w :b :w :w :r :g]
  17. ;;                        :guess  [:c :c :b :c :r :c]}
  18. ;;           :ret #:codebreaker{:exact-matches 4, :loose-matches 3}}
  19. ;;           :via []
  20. ;;           :in []}]
  21. ;;  :clojure.spec.test/args ([:w :b :w :w :r :g] [:c :c :b :c :r :c])
  22. ;;  :clojure.spec.test/val {:args {:secret [:w :b :w :w :r :g]
  23. ;;                                 :guess  [:c :c :b :c :r :c]}
  24. ;;                          :ret #:codebreaker{:exact-matches 4, :loose-matches 3}}
  25. ;;  :clojure.spec/failure :check-failed}
复制代码
   Now we know that everything's wired up correctly, and we can start to flesh out the solution.
    The approach we'll take is to calculate all of the matches, ignoring position, and then the exact matches, and then subtract the exact matches from all matches to calculate the number of loose matches. For example, given the secret      [:r :g :y :c]and the guess      [:g :w :b :c], there are 2 matches altogether,      :gand      :c, so the count of all matches would be 2. One of those,      :c, is an exact match, so exact matches would be 1, leaving 1 loose match.   
    exact-matches

    The exact match calculation is easy to imagine: we need to compare each peg in the guess to the peg in the same position in the secret:
   
  1. (defn score [secret guess]
  2.   {::exact-matches (count (filter true? (map = secret guess)))
  3.    ::loose-matches 0})
复制代码
   Let's exercise that and see what we get:
   
  1. (ns codebreaker
  2.   (:require [clojure.spec :as s]
  3.             [clojure.spec.test :as stest]))
  4. (def peg? #{:y :g :r :c :w :b})
  5. (s/def ::code (s/coll-of peg? :min-count 4 :max-count 6))
  6. (s/fdef score
  7.         :args (s/cat :secret ::code :guess ::code))0
复制代码
   And here we can see the incredible value we get from generators! In this one sample set we got at least one case each of 0, 1, 2, and 3 exact matches. This is not guaranteed, of course. We got lucky! And the next time we run it we'll get different combinations that may or may not be as lucky. But this is far easier than imagining different scenarios, and the result is an arguably more effective evaluation of the function we've written.
    You can easily scan these examples visually and validate that they're all producing the correct result for      :codebreaker/exact-matches. We can run      stest/checkagain and see that we're still passing:   
   
  1. (ns codebreaker
  2.   (:require [clojure.spec :as s]
  3.             [clojure.spec.test :as stest]))
  4. (def peg? #{:y :g :r :c :w :b})
  5. (s/def ::code (s/coll-of peg? :min-count 4 :max-count 6))
  6. (s/fdef score
  7.         :args (s/cat :secret ::code :guess ::code))1
复制代码
   One thing that we don't have, however, is a specification for the exact-matches function. In fact, we don't even have an exact matches function, so let's extract it:
   
  1. (ns codebreaker
  2.   (:require [clojure.spec :as s]
  3.             [clojure.spec.test :as stest]))
  4. (def peg? #{:y :g :r :c :w :b})
  5. (s/def ::code (s/coll-of peg? :min-count 4 :max-count 6))
  6. (s/fdef score
  7.         :args (s/cat :secret ::code :guess ::code))2
复制代码
   Now we can add a spec for it. It has the same args as      score, so let's extract the      :argsspec to something we can share:   
   
  1. (ns codebreaker
  2.   (:require [clojure.spec :as s]
  3.             [clojure.spec.test :as stest]))
  4. (def peg? #{:y :g :r :c :w :b})
  5. (s/def ::code (s/coll-of peg? :min-count 4 :max-count 6))
  6. (s/fdef score
  7.         :args (s/cat :secret ::code :guess ::code))3
复制代码
   And now we can use that      ::secret-and-guessspec for our      exact-matchesspec:   
   
  1. (ns codebreaker
  2.   (:require [clojure.spec :as s]
  3.             [clojure.spec.test :as stest]))
  4. (def peg? #{:y :g :r :c :w :b})
  5. (s/def ::code (s/coll-of peg? :min-count 4 :max-count 6))
  6. (s/fdef score
  7.         :args (s/cat :secret ::code :guess ::code))4
复制代码
   This is quite similar to the      scorespec, but the      :retis a single      nat-int?between 0 and the count of pegs in the secret. So let's exercise this:   
   
  1. (ns codebreaker
  2.   (:require [clojure.spec :as s]
  3.             [clojure.spec.test :as stest]))
  4. (def peg? #{:y :g :r :c :w :b})
  5. (s/def ::code (s/coll-of peg? :min-count 4 :max-count 6))
  6. (s/fdef score
  7.         :args (s/cat :secret ::code :guess ::code))5
复制代码
   Again, we can scan the results to validate them visually, and then run      stest/checkto validate the results against the      :fnspec.   
   
  1. (ns codebreaker
  2.   (:require [clojure.spec :as s]
  3.             [clojure.spec.test :as stest]))
  4. (def peg? #{:y :g :r :c :w :b})
  5. (s/def ::code (s/coll-of peg? :min-count 4 :max-count 6))
  6. (s/fdef score
  7.         :args (s/cat :secret ::code :guess ::code))6
复制代码
   And now we can start tying things together by instrumenting      exact-matchesand exercising and testing      score.      s/instrumentwraps a fn in a fn that checks args for conformance to the      :argsspec before delegating to the original fn:   
   
  1. (ns codebreaker
  2.   (:require [clojure.spec :as s]
  3.             [clojure.spec.test :as stest]))
  4. (def peg? #{:y :g :r :c :w :b})
  5. (s/def ::code (s/coll-of peg? :min-count 4 :max-count 6))
  6. (s/fdef score
  7.         :args (s/cat :secret ::code :guess ::code))7
复制代码
   Everything still passes because      scoreis invoking      exact-matchescorrectly, but it would report incorrect calls to      exact-matchesif we had a bug in      score:   
   
  1. (ns codebreaker
  2.   (:require [clojure.spec :as s]
  3.             [clojure.spec.test :as stest]))
  4. (def peg? #{:y :g :r :c :w :b})
  5. (s/def ::code (s/coll-of peg? :min-count 4 :max-count 6))
  6. (s/fdef score
  7.         :args (s/cat :secret ::code :guess ::code))8
复制代码
   This is a really great way to uncover problems below the surface. It is similar to a mock object in that incorrect calls to      exact-matchesthrow errors, but different in that nothing happens when there are no calls to      exact-matches. Still, we're in an interactive session here, and we can see that      scoreis calling      exact-matches. Gray box testing FTW!   
    Also note that we have property based tests for both functions, with no specific examples codified. More on this later!
    If you're following along in a REPL, don't forget to fix the bug by restoring the      scorefn:   
   
  1. (ns codebreaker
  2.   (:require [clojure.spec :as s]
  3.             [clojure.spec.test :as stest]))
  4. (def peg? #{:y :g :r :c :w :b})
  5. (s/def ::code (s/coll-of peg? :min-count 4 :max-count 6))
  6. (s/fdef score
  7.         :args (s/cat :secret ::code :guess ::code))9
复制代码
   all-matches

    Thinking of a function to calculate all of the matches, it would have the same properties as the      exact-matchesfunction: it takes a pair of codes and returns a      nat-int?between 0 and the count of pegs in either of the codes. We already have a spec for that, so let's generalize its name, and then we can use it for      exact-matchesand      all-matches:   
   
  1. (s/exercise (:args (s/get-spec `score)))
  2. ;; ([([:y :w :g :y :c] [:c :g :y :y :y :c]) {:secret [:y :w :g :y :c], :guess [:c :g :y :y :y :c]}]
  3. ;;  [([:c :w :g :r :g] [:r :b :c :r :w :g]) {:secret [:c :w :g :r :g], :guess [:r :b :c :r :w :g]}]
  4. ;;  ...
  5. ;;  [([:r :c :w :w :y :r] [:y :r :y :y :c]) {:secret [:r :c :w :w :y :r], :guess [:y :r :y :y :c]}]
  6. ;;  [([:c :g :b :g :w :b] [:r :y :w :r :b]) {:secret [:c :g :b :g :w :b], :guess [:r :y :w :r :b]}])0
复制代码
   These calls to      s/exercise-fnand      stest/check-fnproduce similar results to those above. Now we can instrument      exact-matcheswith the      match-countspec and exercise and test      scoreas we did earlier as well:   
   
  1. (s/exercise (:args (s/get-spec `score)))
  2. ;; ([([:y :w :g :y :c] [:c :g :y :y :y :c]) {:secret [:y :w :g :y :c], :guess [:c :g :y :y :y :c]}]
  3. ;;  [([:c :w :g :r :g] [:r :b :c :r :w :g]) {:secret [:c :w :g :r :g], :guess [:r :b :c :r :w :g]}]
  4. ;;  ...
  5. ;;  [([:r :c :w :w :y :r] [:y :r :y :y :c]) {:secret [:r :c :w :w :y :r], :guess [:y :r :y :y :c]}]
  6. ;;  [([:c :g :b :g :w :b] [:r :y :w :r :b]) {:secret [:c :g :b :g :w :b], :guess [:r :y :w :r :b]}])1
复制代码
   So now comes the hard part: the      all-matchescalculation. We need to allow for duplicates, so we can count the number of appearances of e.g.      :rin the secret and the guess and take the lower of the two numbers. Then we can do the same for all the colors and add up the resulting counts. For example, with a secret      [:r :r :y :b]and a guess      [:r :g :c :y], we can see that      :rappears twice in the secret and once in the guess, so the score for      :rwould be 1. Yellow appears once in each, so its score is 1. Neither      :gnor      :cever appear in the secret, so we don't count those and the total is 2. Make sense? Here's one way to express that:   
   
  1. (s/exercise (:args (s/get-spec `score)))
  2. ;; ([([:y :w :g :y :c] [:c :g :y :y :y :c]) {:secret [:y :w :g :y :c], :guess [:c :g :y :y :y :c]}]
  3. ;;  [([:c :w :g :r :g] [:r :b :c :r :w :g]) {:secret [:c :w :g :r :g], :guess [:r :b :c :r :w :g]}]
  4. ;;  ...
  5. ;;  [([:r :c :w :w :y :r] [:y :r :y :y :c]) {:secret [:r :c :w :w :y :r], :guess [:y :r :y :y :c]}]
  6. ;;  [([:c :g :b :g :w :b] [:r :y :w :r :b]) {:secret [:c :g :b :g :w :b], :guess [:r :y :w :r :b]}])2
复制代码
   All together now

    Scanning the output of      s/exercise, we can see we got it right. So now let's put that to work in      score:   
   
  1. (s/exercise (:args (s/get-spec `score)))
  2. ;; ([([:y :w :g :y :c] [:c :g :y :y :y :c]) {:secret [:y :w :g :y :c], :guess [:c :g :y :y :y :c]}]
  3. ;;  [([:c :w :g :r :g] [:r :b :c :r :w :g]) {:secret [:c :w :g :r :g], :guess [:r :b :c :r :w :g]}]
  4. ;;  ...
  5. ;;  [([:r :c :w :w :y :r] [:y :r :y :y :c]) {:secret [:r :c :w :w :y :r], :guess [:y :r :y :y :c]}]
  6. ;;  [([:c :g :b :g :w :b] [:r :y :w :r :b]) {:secret [:c :g :b :g :w :b], :guess [:r :y :w :r :b]}])3
复制代码
   We can see that we're calculating the exact and loose matches correctly, and the tests all pass. So now we can memorialize these generative tests in a repeatable automated test.
    Testing, testing, 1, 2, 3 ...

    We can get a summary of the test results:
   
  1. (s/exercise (:args (s/get-spec `score)))
  2. ;; ([([:y :w :g :y :c] [:c :g :y :y :y :c]) {:secret [:y :w :g :y :c], :guess [:c :g :y :y :y :c]}]
  3. ;;  [([:c :w :g :r :g] [:r :b :c :r :w :g]) {:secret [:c :w :g :r :g], :guess [:r :b :c :r :w :g]}]
  4. ;;  ...
  5. ;;  [([:r :c :w :w :y :r] [:y :r :y :y :c]) {:secret [:r :c :w :w :y :r], :guess [:y :r :y :y :c]}]
  6. ;;  [([:c :g :b :g :w :b] [:r :y :w :r :b]) {:secret [:c :g :b :g :w :b], :guess [:r :y :w :r :b]}])4
复制代码
   If there were failures the result would look like this instead:
   
  1. (s/exercise (:args (s/get-spec `score)))
  2. ;; ([([:y :w :g :y :c] [:c :g :y :y :y :c]) {:secret [:y :w :g :y :c], :guess [:c :g :y :y :y :c]}]
  3. ;;  [([:c :w :g :r :g] [:r :b :c :r :w :g]) {:secret [:c :w :g :r :g], :guess [:r :b :c :r :w :g]}]
  4. ;;  ...
  5. ;;  [([:r :c :w :w :y :r] [:y :r :y :y :c]) {:secret [:r :c :w :w :y :r], :guess [:y :r :y :y :c]}]
  6. ;;  [([:c :g :b :g :w :b] [:r :y :w :r :b]) {:secret [:c :g :b :g :w :b], :guess [:r :y :w :r :b]}])5
复制代码
   Then we can build predicates around that like:
   
  1. (s/exercise (:args (s/get-spec `score)))
  2. ;; ([([:y :w :g :y :c] [:c :g :y :y :y :c]) {:secret [:y :w :g :y :c], :guess [:c :g :y :y :y :c]}]
  3. ;;  [([:c :w :g :r :g] [:r :b :c :r :w :g]) {:secret [:c :w :g :r :g], :guess [:r :b :c :r :w :g]}]
  4. ;;  ...
  5. ;;  [([:r :c :w :w :y :r] [:y :r :y :y :c]) {:secret [:r :c :w :w :y :r], :guess [:y :r :y :y :c]}]
  6. ;;  [([:c :g :b :g :w :b] [:r :y :w :r :b]) {:secret [:c :g :b :g :w :b], :guess [:r :y :w :r :b]}])6
复制代码
   And then we can hook those into assertions in clojure.test or whatever tool you prefer for repeatable tests.
    Note that there are no example based tests here: everything is being generated by generators built for us by clojure.spec. If this makes you uncomfortable, then add a couple of example based tests! But you certainly don't need an exhaustive set of them. The handful of functions and specs are all small, expressive, and easy for any experienced clojure developer to read and understand, and the generative, property-based tests that we get for      score,      exact-matches, and      all-matchesprovide a lot of confidence that they are all working correctly.   
    Experience report

    I've been using TDD in some form or another for many years, and when clojure.spec appeared I was curious to see how it would fit into or change my approach. I won't go as far as to say that this post represents "my approach" as that is still evolving in light of the presence of spec, but there are clear similarities to and differences from TDD in this example.
    Like TDD, there is a tight feedback loop: write a spec and exercise it right away, all before any implementation code. The spec itself is not a test, but it      isa reusable source for generated sample data that we can use in an interactive REPL session and in a repeatable test.   
    Like TDD, I did some refactoring as I discovered opportunities to improve the code. Sometimes I used visual inspection of the result of exercising specs and sometimes I used test.check to cast a wider net.
    Unlike TDD, I didn't go through a consistent cycle of watching a test fail and then making it pass, and then refactoring (red/green/refactor). You can use these tools for that cycle, but generative tests are, by design, more coarse than example based tests, so it might be more of a challenge to keep that very granular cycle consistent. Perhaps a subject for another post (perhaps written by you!).
    Unlike example based tests in TDD, generation allowed me to quickly spot-check dozens of correct invocations without having to hand write them or wire them to assertions.
    Unlike TDD, generation can sometimes find categories of inputs that we've failed to consider. That didn't happen in this example, but if you've ever forgotten to account for nil or empty string in tests for a string processing function, then you know what I'm talking about.
    In summary, I think that clojure.spec provides a powerful set of tools for interactive development that should appeal to anybody developing at the REPL, regardless of your particular process.
友荐云推荐




上一篇:Python: Declaring Dynamic Attributes
下一篇:A new decentralized microblogging platform
酷辣虫提示酷辣虫禁止发表任何与中华人民共和国法律有抵触的内容!所有内容由用户发布,并不代表酷辣虫的观点,酷辣虫无法对用户发布内容真实性提供任何的保证,请自行验证并承担风险与后果。如您有版权、违规等问题,请通过"联系我们"或"违规举报"告知我们处理。

贺晓静 发表于 2016-10-6 09:13:39
世界末日我都挺过去了,看到溫柔﹌散了場我才知道为什么上帝留我到现在!
回复 支持 反对

使用道具 举报

小哩猫的天空 发表于 2016-10-10 21:11:22
技术控老油条路过
回复 支持 反对

使用道具 举报

彭某某人 发表于 2016-10-10 22:04:28
看来楼下的有话说!
回复 支持 反对

使用道具 举报

柏元 发表于 2016-10-19 05:22:17
牛人 佩服!
回复 支持 反对

使用道具 举报

a86982972 发表于 2016-10-20 23:40:29
楼主说的,句句都是真理啊!
回复 支持 反对

使用道具 举报

zxyxy 发表于 2016-10-29 03:16:00
是爷们的娘们的都帮顶!大力支持
回复 支持 反对

使用道具 举报

xiaoxi2011 发表于 2016-10-29 10:23:50
最近回了很多帖子,都没人理我!
回复 支持 反对

使用道具 举报

邓胜薛 发表于 2016-11-3 14:01:55
已经习惯给自己第一朵了
回复 支持 反对

使用道具 举报

梅子依旧 发表于 2016-11-9 18:30:10
最近练了葵花宝典吧?
回复 支持 反对

使用道具 举报

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

我要投稿

推荐阅读

扫码访问 @iTTTTT瑞翔 的微博
回页顶回复上一篇下一篇回列表手机版
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 )|网站地图 酷辣虫

© 2001-2016 Comsenz Inc. Design: Dean. DiscuzFans.

返回顶部 返回列表