Clojure.spe c: существование поля на основе другого поля в генераторе - PullRequest
6 голосов
/ 25 апреля 2020

Допустим, у нас есть API для сохранения различных типов атрибутов файлов в БД *: текстовых, графических, аудио- и видеофайлов. Он должен иметь возможность получать следующие поля в зависимости от их типа:
базовые свойства для всех файлов:

{"file-type": "text",
"location": "/Documents",
"creation-time": "2020-03-02",
"name": "sometext.txt"}

дополнительные реквизиты, указанные c только для некоторых типов:

text: only base props
video file: base props + {"duration(s)":123, "resolution":"4k"}
audio file: base props + {"duration(s)":123}
image: base props + {"resolution":"2048x1536"}

как мы видим, некоторые поля должны быть нулевыми, в зависимости от типа файла. Допустим, нам нужно проверить ввод такого рода, который может быть любым из описанных. Итак, спецификации таковы:

(s/def ::file-type (s/with-gen (s/and string? not-empty) #(s/gen #{"text" "image" "video" "audio"})))
(s/def ::location (s/with-gen (s/and string? not-empty) #(s/gen #{"/Documents"})))
(s/def ::creation-time (s/with-gen (s/and string? not-empty) #(s/gen #{"2020-03-02"}))) ;for simplicity
(s/def ::name (s/with-gen (s/and string? not-empty) #(s/gen #{"sometext.txt" "image.jpg" "video.mp4" "audio.mp3"}))) ;for simplicity
(s/def ::duration (s/and int? not-empty))                   ;for simplicity
(s/def ::resolution (s/with-gen (s/and string? not-empty) #(s/gen #{"4k" "2048x1536"}))) ;for simplicity
(s/def ::files-input (s/keys :req-un [::file-type ::location ::creation-time ::extension] :opt-un [::duration ::resolution]))

Скажем также, что существует программный валидатор c, который проверяет, был ли передан правильный набор полей для каждого типа файла (например, «video» имеет поле «duration», но «текст» не имеет).
Вопрос заключается в следующем: как создать полный готовый ввод с этими зависимостями для юнит-тестов (включая зависимые поля)?

* (давайте оставим в стороне вопрос, является ли это правильным дизайном для API, поскольку этот пример не из реальной жизни и только для демонстрационных целей)

1 Ответ

8 голосов
/ 25 апреля 2020

На основе multi-spe c do c, мы должны добавить отдельный метод с собственным набором полей для каждого случая:

(s/def ::file-type (s/with-gen (s/and string? not-empty) #(s/gen #{"text" "image" "video" "audio"})))
(s/def ::location (s/with-gen (s/and string? not-empty) #(s/gen #{"/Documents"})))
(s/def ::creation-time (s/with-gen (s/and string? not-empty) #(s/gen #{"2020-03-02"}))) ;for simplicity
(s/def ::name (s/with-gen (s/and string? not-empty) #(s/gen #{"sometext.txt" "image.jpg" "video.mp4" "audio.mp3"}))) ;for simplicity
(s/def ::duration pos-int?)                                 ;for simplicity
(s/def ::resolution (s/with-gen (s/and string? not-empty) #(s/gen #{"4k" "2048x1536"}))) ;for simplicity
(s/def ::base-props (s/keys :req-un [::file-type ::location ::creation-time ::name]))
(s/def ::file-type-key (s/with-gen keyword? #(s/gen #{:text :image :video :audio})))

(defmulti file :file-type-key)
(defmethod file :text [_]
    (s/merge (s/keys :req-un [::file-type-key]) ::base-props))
(defmethod file :image [_]
    (s/merge (s/keys :req-un [::file-type-key ::resolution]) ::base-props))
(defmethod file :video [_]
    (s/merge (s/keys :req-un [::file-type-key ::duration ::resolution]) ::base-props))
(defmethod file :audio [_]
    (s/merge (s/keys :req-un [::file-type-key ::duration]) ::base-props))
(defmethod file :default [_]
    (s/merge (s/keys :req-un [::file-type-key]) ::base-props))

(s/def ::file-input (s/and (s/multi-spec file :file-type-key)
                           (fn [{:keys [file-type file-type-key]}]
                               (= file-type-key (keyword file-type)))))

даст сгенерированный вход для юнит-теста (проверяется stest/check):

(gen/sample (s/gen ::file-input) 2)
=>
({:file-type-key :text, :file-type "text", :location "/Documents", :creation-time "2020-03-02", :name "sometext.txt"}
 {:file-type-key :image,
  :resolution "4k",
  :file-type "image",
  :location "/Documents",
  :creation-time "2020-03-02",
  :name "sometext.txt"})

нам пришлось добавить еще одно поле ::file-type-key в качестве селектора (должно быть ключевым словом), но это не влияет на тесты и dissoc легко редактировать.

...