web-dev-qa-db-ja.com

Clojure.spec:ジェネレーターの他のフィールドに基づくフィールドの存在

異なるタイプのファイルの属性をDB *に保存するためのAPIがあるとします:テキスト、画像、オーディオ、ビデオファイル。タイプに基づいて次のフィールドを取得できるはずです。
すべてのファイルの基本プロパティ:

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

一部のタイプにのみ固有の追加の小道具:

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]))

また、ファイルタイプごとに正しいフィールドセットが渡されたことを確認するプログラム検証ツールがあるとします(たとえば、「ビデオ」には「期間」フィールドがありますが、「テキスト」にはありません)。
質問は次のとおりです。ユニットテスト(依存フィールドを含む)の場合、生成完全な既製これらの依存関係を使用して入力する方法は?

*(この例は実際のものではなく、デモンストレーションのみを目的としているため、APIの適切な設計であるかどうかはわからない)

6
jsay

multi-spec doc に基づいて、ケースごとに独自のフィールドセットを持つ個別のメソッドを追加する必要があります。

(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"})

もう1つのフィールド::file-type-keyをセレクターとして追加する必要がありましたが(キーワードにする必要があります)、テストには影響せず、簡単にdissocedできます。

8
jsay