2024 年 Clojure 状态调查!中分享您的想法。

欢迎!请参阅 关于页面了解更多关于本网站的信息。

0
test.check

Haskell QuickCheck 允许-for-all 表达式嵌套。当生成的值之间存在依赖关系时,这非常有用。test.check 也应该允许这样做。

目前,嵌套的 for-alls 总是成功,这是有些危险的。

我已经添加了一个补丁来实现这一点。

9 个答案

0

评论由:reiddraper 发布

感谢Michael。我很欣赏这个补丁,但在我们深入代码细节之前,还有一些设计细节可以讨论。作为一个独立但相关的问题,我一直想实现类似于您的{{CheckResult}}类型,但作为一个有更多字段的记录。这些字段将保存诸如任何找到的错误的纯文本描述、统计信息(类似于QuickCheck的{{collect}},{{classify}}等)。我还想编写一个协议,允许基本类型(如布尔值)转换为这种记录。这将类似于Haskell QuickCheck的{{Testable}}类型类。虽然这是一个技术上的独立问题,但我认为我们应该与可嵌套的for-alls一起解决这个问题,尤其是考虑到嵌套的for-alls可以通过在生成器级别使用bind来模拟。这个解释是否合理?

0

评论由:sperber 发布

当然。

我个人会开始这个补丁,并据此开展工作,除非你想要进行根本性的改变而不是添加更多内容。

无论如何,我如何帮它实现呢?

0

评论由:reiddraper 发布

这是个好问题。让我考虑一下,尽快给您答复。我也希望能尽快促成此事。

0
评论者:reiddraper

抱歉延迟,这是我一直在工作的草图


diff --git a/src/main/clojure/clojure/test/check/properties.clj b/src/main/clojure/clojure/test/check/properties.clj
index 99b5222..139ae9a 100644
--- a/src/main/clojure/clojure/test/check/properties.clj
+++ b/src/main/clojure/clojure/test/check/properties.clj
@@ -8,13 +8,47 @@
 ;   您不得从本软件中移除此声明,或任何其他声明。
 
 (ns clojure.test.check.properties
+  (:import clojure.test.check.generators.Generator)
   (:require [clojure.test.check.generators :as gen]))
 
+(defrecord Result [result pass? message stamps])
+
+(defprotocol ToResult
+  (to-result [a]))
+
+(extend java.lang.Object
+  ToResult
+  {:to-result (fn [b]
+                    ;;; 在此处未检查捕获的异常
+                    (->Result b (not (false? b)) nil nil))})
+
+(extend nil
+  ToResult
+  {:to-result (fn [b]
+               (->Result b false nil nil))})
+
+(extend java.lang.Boolean
+  ToResult
+  {:to-result (fn [b]
+               (->Result b b nil nil))})
+
+(extend Generator
+  ToResult
+  {:to-result identity})
+
+(extend Result
+  ToResult
+  {:to-result identity})
+
+(defn message
+  [m property]
+  (assoc property :message m))
+
 (defn- apply-gen
   [function]
   (fn [args]
-    (let [result (try (apply function args) (catch Throwable t t))]
-      {:result result
+    (let [result (to-result (try (apply function args) (catch Throwable t t)))]
+      {:result (:result result)
        :function function
        :args args})))
 
@@ -29,9 +63,18 @@
   (for-all* [gen/int gen/int] (fn [a b] (>= (+ a b) a)))
   "
   [args function]
-  (gen/fmap
-    (apply-gen function)
-    (apply gen/tuple args)))
+  (gen/bind
+    (apply gen/tuple args)
+    (fn [a]
+      (let [result ((apply-gen function) a)]
+        (cond (gen/generator? result) (gen/fmap (fn [r] (println "foo") (update-in r :args #(conj % a))) result)
+              ;; 注意:我在离开代码前做的快速笔记,
+              ;;; 这个 :else 是因为我们在将结果
+              ;;; 包裹在一个 {:result ...} 地图中击中。
+              ;;; 我认为我们可能需要两种结果类型,一种是从属性自身返回的结果
+              ;;; 和一个将 'args' 压在顶级的结果。
+              :else (do (println "bar") (gen/return result)))))
+    ))
 
 (defn binding-vars
   [bindings]
0
by

评论由:sperber 发布

看起来还可以。但是,很难理解为什么它会比你说的快速修复你的patch...

0
by

评论由:reiddraper 发布

bq. 看起来还可以。但是,很难理解为什么它会比你说的快速修复你的patch...

很公平。这部分是因为它更容易为我编写草图。我在支持嵌套生成器时试图涵盖的主要事情是确保...

  1. 我们还支持收集和返回测试统计信息的功能,类似于 Haskell QuickCheck 中的 collect
  2. 我们有一种合理的方式将失败的测试返回给用户。目前,在返回的映射的 {{:fail}} 和 {{:smallest}} 键中,我们告诉用户失败的参数。由于你可能使用多个生成器,它们总是被至少一个向量包围。对于嵌套属性我们怎么办?我们如何在‘同一级别’的多个生成器与嵌套属性之间区分?或者我们不需要区分?我们决定的任何东西是否能够向后兼容?

关键是,我想确保我们在得到一些答案之前不会将嵌套属性固定下来,并且就我个人而言,尝试一起玩这些事情更容易,看看它们如何配合。

0
by
评论者:expez

我真的不想嵌套 for-all,我更希望它像 let 那样工作,我们可以引用前面的值。话虽如此,不管是哪种解决方案,这是我目前对 test.check 最大的不满,因此任何解决方案都优于另一年的懒散时间。

这里有一个解决方案,摘自野地。我相信还有很多其他的解决方案,人们对这个需求都很强烈,但这个方案是从 Nathan Marz 的 specter 库中摘录的


(defmacro for-all+ [bindings & body]
  (let [parts (partition 2 bindings)
    (vec (map first parts))
    genned (reduce
        (fn [curr [v code]]
            (gen/bind ~code (fn [~v] ~curr)))
            (gen/return ~vars)
            (reverse parts))]
    (prop/for-all [~vars ~genned]
                ~@body )))
0

评论者:gfredericks

我们正在考虑一个类似的问题(链接:http://dev.clojure.org/jira/browse/TCHECK-81 文字:TCHECK-81),同时还有一个更炫酷的 {{for-all}} 变体(链接:https://github.com/gfredericks/test.chuck#properties 文字:这里)。

0
参考资料:https://clojure.atlassian.net/browse/TCHECK-44 (由 alex+import 报告)
...