Нужна помощь в понимании того, почему тест/проверка спецификации Clojure не проходит проверку возврата, когда REPL не терпит неудачу

Я экспериментировал с Clojure Spec для тестирования и генерации данных и наблюдаю странное поведение, когда функция работает в модульных тестах, а проверка работает в REPL, но генеративное тестирование с помощью spec.test/check терпит неудачу.

Я создал набор спецификаций следующим образом:

(s/def ::significant-string (s/with-gen 
                              (s/and string? #(not (nil? %)))
                              (fn [] (gen/such-that #(not= % "")
                                             (gen/string-alphanumeric)))))

(s/def ::byte-stream 
  (s/with-gen #(instance? java.io.ByteArrayInputStream %)
   (gen/fmap #(string->stream %) (gen/string-alphanumeric))))


(s/fdef string->stream
  :args (s/cat :s ::significant-string)
  :ret ::byte-stream
  :fn #(instance? java.io.ByteArrayInputStream %))

И реализация fn:

  (defn string->stream
  "Given a string, return a java.io.ByteArrayInputStream"
  ([s] {:pre [(s/valid? ::significant-string s)]
        :post [(s/valid? ::byte-stream %)]}
       (string->stream s "UTF-8"))
  ([s encoding]
   (-> s
       (.getBytes encoding)
       (java.io.ByteArrayInputStream.))))

И в REPL я вижу то, что ожидаю увидеть как от генерации, так и от тестирования спецификаций:

=> (instance? java.io.ByteArrayInputStream (string->stream "test"))
true

=> (s/valid? ::byte-stream (string->stream "0"))
true

=> (s/exercise-fn 'calais-response-processor.rdf-core/string->stream)
([("Y") #object[java.io.ByteArrayInputStream 0x57210dd7 "java.io.ByteArrayInputStream@57210dd7"]] [("d") #object[java.io.ByteArrayInputStream 0x7ec14113 "java.io.ByteArrayInputStream@7ec14113"]] [("5") #object[java.io.ByteArrayInputStream 0x1e85195b "java.io.ByteArrayInputStream@1e85195b"]] [("9c") #object[java.io.ByteArrayInputStream 0x3769ddef "java.io.ByteArrayInputStream@3769ddef"]] [("P0N") #object[java.io.ByteArrayInputStream 0x68793160 "java.io.ByteArrayInputStream@68793160"]] [("7tvN1") #object[java.io.ByteArrayInputStream 0x1cc43ca5 "java.io.ByteArrayInputStream@1cc43ca5"]] [("LjH4U") #object[java.io.ByteArrayInputStream 0x2a3da1a7 "java.io.ByteArrayInputStream@2a3da1a7"]] [("W") #object[java.io.ByteArrayInputStream 0x534287aa "java.io.ByteArrayInputStream@534287aa"]] [("x867VLr") #object[java.io.ByteArrayInputStream 0x72915e93 "java.io.ByteArrayInputStream@72915e93"]] [("moucN3vr") #object[java.io.ByteArrayInputStream 0x4f0d7570 "java.io.ByteArrayInputStream@4f0d7570"]])

Но я не понимаю, почему я вижу это из теста/проверки:

(stest/check 'calais-response-processor.rdf-core/string->stream)
    ({:spec #object[clojure.spec.alpha$fspec_impl$reify__2451 0x1acb0d46 "clojure.spec.alpha$fspec_impl$reify__2451@1acb0d46"], :clojure.spec.test.check/ret {:shrunk {:total-nodes-visited 4, :depth 3, :pass? false, :result #error {
     :cause "Specification-based check failed"
     :data {:clojure.spec.alpha/problems [{:path [:fn], :pred (clojure.core/fn [%] (clojure.core/instance? java.io.ByteArrayInputStream %)), :val {:args {:s "0"}, :ret #object[java.io.ByteArrayInputStream 0x7bee9d86 "java.io.ByteArrayInputStream@7bee9d86"]}, :via [], :in []}], :clojure.spec.alpha/spec #object[clojure.spec.alpha$spec_impl$reify__1987 0x16a19b4c "clojure.spec.alpha$spec_impl$reify__1987@16a19b4c"], :clojure.spec.alpha/value {:args {:s "0"}, :ret #object[java.io.ByteArrayInputStream 0x7bee9d86 "java.io.ByteArrayInputStream@7bee9d86"]}, :clojure.spec.test.alpha/args ("0"), :clojure.spec.test.alpha/val {:args {:s "0"}, :ret #object[java.io.ByteArrayInputStream 0x7bee9d86 "java.io.ByteArrayInputStream@7bee9d86"]}, :clojure.spec.alpha/failure :check-failed}
     :via
     [{:type clojure.lang.ExceptionInfo
       :message "Specification-based check failed"
       :data {:clojure.spec.alpha/problems [{:path [:fn], :pred (clojure.core/fn [%] (clojure.core/instance? java.io.ByteArrayInputStream %)), :val {:args {:s "0"}, :ret #object[java.io.ByteArrayInputStream 0x7bee9d86 "java.io.ByteArrayInputStream@7bee9d86"]}, :via [], :in []}], :clojure.spec.alpha/spec #object[clojure.spec.alpha$spec_impl$reify__1987 0x16a19b4c "clojure.spec.alpha$spec_impl$reify__1987@16a19b4c"], :clojure.spec.alpha/value {:args {:s "0"}, :ret #object[java.io.ByteArrayInputStream 0x7bee9d86 "java.io.ByteArrayInputStream@7bee9d86"]}, :clojure.spec.test.alpha/args ("0"), :clojure.spec.test.alpha/val {:args {:s "0"}, :ret #object[java.io.ByteArrayInputStream 0x7bee9d86 "java.io.ByteArrayInputStream@7bee9d86"]}, :clojure.spec.alpha/failure :check-failed}
       :at [clojure.core$ex_info invokeStatic "core.clj" 4739]}]
     :trace
     [[clojure.core$ex_info invokeStatic "core.clj" 4739]
      [clojure.core$ex_info invoke "core.clj" 4739]
      ...
      ...(lots more)

Такое ощущение, что это связано с композицией fn генератора, хотя возвращаемый объект сейчас выглядит "хорошо" для меня.


person Chris Lester    schedule 25.01.2019    source источник


Ответы (1)


:fn #(instance? java.io.ByteArrayInputStream %))

Проблема в том, что похоже, что спецификация :fn ожидает только значение функции return, когда она на самом деле вызывается с картой, содержащей входные и возвращаемые значения. Вместо этого попробуйте эту версию:

:fn (fn [{:keys [args ret]}]
      (instance? java.io.ByteArrayInputStream ret))

Спецификация :fn должна быть функцией, которая принимает карту, содержащую входное значение функции :args и выходное значение :ret. Он предназначен для сравнения вывода функции с ее вводом.

В этом примере спецификация :fn, кажется, делает то же утверждение, что и ваша спецификация :ret, и не смотрит на :args, поэтому вам может не понадобиться здесь спецификация :fn, если между вводом/выводом нет значимого утверждения — это будет только подтверждать возвращаемое значение, избыточно.

И в REPL я вижу то, что ожидаю увидеть как от генерации, так и от тестирования спецификаций.

Причина, по которой вы видите только сбой с check, заключается в том, что ни один из этих других вызовов не учитывает спецификацию вашей функции :fn, например. s/exercise-fn не учитывает спецификацию :fn.

Я сделал несколько примеров, используя :fn спецификации здесь.

person Taylor Wood    schedule 25.01.2019
comment
Спасибо! Ни один из примеров в руководстве (или где-либо еще), с которыми я столкнулся, не был настолько четким (хотя я вижу, что теперь вывод журнала был буквальным). Это избыточно, правда ... в качестве варианта использования была простая fn. Спасибо еще раз! - person Chris Lester; 25.01.2019