bigywater.tech
Java · 326

Java 属性测试

属性测试不是写几个例子,而是描述一条对所有输入都成立的规律,让框架去找反例。找到之后怎么收缩成最小的反例?下次怎么复现?边界值怎么混进随机数据?这篇用三个故意写错的函数逐个演示。

12 步
平均值缺陷的反例从 (-2147483648, -58912738) 收缩到 (-1, -2147483648) 用的步数
1 次
第二次运行时 jqwik 从 .jqwik-database 重放失败样本后失败的调用数
8 步
排序缺陷从 11 个元素的列表收缩到 [0, 0, 0, 0, 0, 0] 的步数

成立的性质

实测 · 本机执行输出
                                |-----------------------jqwik-----------------------
  tries = 1000                  | # of calls to property
  checks = 1000                 | # of not rejected calls
  generation = RANDOMIZED       | parameters are randomly generated
  after-failure = SAMPLE_FIRST  | try previously failed sample, then previous seed
  when-fixed-seed = ALLOW       | fixing the random seed is allowed
  edge-cases#mode = MIXIN       | edge cases are mixed in
  edge-cases#total = 10         | # of all combined edge cases
  edge-cases#tried = 10         | # of edge cases tried in current run
  seed = 7629694908616210214    | random seed to reproduce generated values
  ├─ jqwik (JUnit Platform) ✔
  │  └─ Props ✔
  │     └─ reverseTwiceIsIdentity ✔
  ├─ JUnit Platform Suite ✔
  └─ JUnit Vintage ✔
  [         1 tests found           ]
  [         1 tests successful      ]
  [         0 tests failed          ]

溢出缺陷与收缩

实测 · 本机执行输出
      Property [Props:averageBetween] failed with sample {0=-1, 1=-2147483648}
                                |-----------------------jqwik-----------------------
  tries = 44                    | # of calls to property
  checks = 44                   | # of not rejected calls
  generation = RANDOMIZED       | parameters are randomly generated
  after-failure = SAMPLE_FIRST  | try previously failed sample, then previous seed
  when-fixed-seed = ALLOW       | fixing the random seed is allowed
  edge-cases#mode = MIXIN       | edge cases are mixed in
  edge-cases#total = 81         | # of all combined edge cases
  edge-cases#tried = 4          | # of edge cases tried in current run
  seed = -1833469337275626533   | random seed to reproduce generated values
  Shrunk Sample (12 steps)
    arg0: -1
    arg1: -2147483648
  Original Sample
    arg0: -2147483648
    arg1: -58912738
  ├─ jqwik (JUnit Platform) ✔
  │  └─ Props ✔
  │     └─ averageBetween ✘ Property [Props:averageBetween] failed with sample {0=-1, 1=-2147483648}
  ├─ JUnit Platform Suite ✔
  └─ JUnit Vintage ✔
      => org.opentest4j.AssertionFailedError: Property [Props:averageBetween] failed with sample {0=-1, 1=-2147483648}
  [         1 tests found           ]
  [         0 tests successful      ]
  [         1 tests failed          ]
  捕获到的种子: -1833469337275626533

失败样本的重放

实测 · 本机执行输出
      Property [Props:averageBetween] failed with sample {0=-1, 1=-2147483648}
                                |-----------------------jqwik-----------------------
  tries = 1                     | # of calls to property
  checks = 1                    | # of not rejected calls
  generation = RANDOMIZED       | parameters are randomly generated
  after-failure = SAMPLE_FIRST  | try previously failed sample, then previous seed
  when-fixed-seed = ALLOW       | fixing the random seed is allowed
  edge-cases#mode = MIXIN       | edge cases are mixed in
  edge-cases#total = 81         | # of all combined edge cases
  edge-cases#tried = 4          | # of edge cases tried in current run
  seed = -1833469337275626533   | random seed to reproduce generated values
  Sample
    arg0: -1
    arg1: -2147483648
  ├─ jqwik (JUnit Platform) ✔
  │  └─ Props ✔
  │     └─ averageBetween ✘ Property [Props:averageBetween] failed with sample {0=-1, 1=-2147483648}
  ├─ JUnit Platform Suite ✔
  └─ JUnit Vintage ✔
      => org.opentest4j.AssertionFailedError: Property [Props:averageBetween] failed with sample {0=-1, 1=-2147483648}
  [         1 tests found           ]
  [         0 tests successful      ]
  [         1 tests failed          ]
  工作目录里 .jqwik-database 是否存在: True ,大小 3078 字节

MIN_VALUE 边界

实测 · 本机执行输出
      Property [Props:absNonNegative] failed with sample {0=-2147483648}
                                |-----------------------jqwik-----------------------
  tries = 90                    | # of calls to property
  checks = 90                   | # of not rejected calls
  generation = RANDOMIZED       | parameters are randomly generated
  after-failure = SAMPLE_FIRST  | try previously failed sample, then previous seed
  when-fixed-seed = ALLOW       | fixing the random seed is allowed
  edge-cases#mode = MIXIN       | edge cases are mixed in
  edge-cases#total = 9          | # of all combined edge cases
  edge-cases#tried = 5          | # of edge cases tried in current run
  seed = 3878513427101564004    | random seed to reproduce generated values
  Sample
    arg0: -2147483648
  ├─ jqwik (JUnit Platform) ✔
  │  └─ Props ✔
  │     └─ absNonNegative ✘ Property [Props:absNonNegative] failed with sample {0=-2147483648}
  ├─ JUnit Platform Suite ✔
  └─ JUnit Vintage ✔
      => org.opentest4j.AssertionFailedError: Property [Props:absNonNegative] failed with sample {0=-2147483648}
  [         1 tests found           ]
  [         0 tests successful      ]
  [         1 tests failed          ]

列表缺陷的最小反例

实测 · 本机执行输出
      Property [Props:sortKeepsSize] failed with sample {0=[0, 0, 0, 0, 0, 0]}
                                |-----------------------jqwik-----------------------
  tries = 3                     | # of calls to property
  checks = 3                    | # of not rejected calls
  generation = RANDOMIZED       | parameters are randomly generated
  after-failure = SAMPLE_FIRST  | try previously failed sample, then previous seed
  when-fixed-seed = ALLOW       | fixing the random seed is allowed
  edge-cases#mode = MIXIN       | edge cases are mixed in
  edge-cases#total = 5          | # of all combined edge cases
  edge-cases#tried = 0          | # of edge cases tried in current run
  seed = 4804554757082382072    | random seed to reproduce generated values
  Shrunk Sample (8 steps)
    arg0: [0, 0, 0, 0, 0, 0]
  Original Sample
    arg0: [0, 2, 0, 3, 2, 1, 2, 1, 2, 3, 3]
  ├─ jqwik (JUnit Platform) ✔
  │  └─ Props ✔
  │     └─ sortKeepsSize ✘ Property [Props:sortKeepsSize] failed with sample {0=[0, 0, 0, 0, 0, 0]}
  ├─ JUnit Platform Suite ✔
  └─ JUnit Vintage ✔
      => org.opentest4j.AssertionFailedError: Property [Props:sortKeepsSize] failed with sample {0=[0, 0, 0, 0, 0, 0]}
  [         1 tests found           ]
  [         0 tests successful      ]
  [         1 tests failed          ]

Assume、统计与样本

实测 · 本机执行输出
  withAssume(int, int)
                                  |-----------------------jqwik-----------------------
    tries = 200                   | # of calls to property
    checks = 195                  | # of not rejected calls
    generation = RANDOMIZED       | parameters are randomly generated
    after-failure = SAMPLE_FIRST  | try previously failed sample, then previous seed
    when-fixed-seed = ALLOW       | fixing the random seed is allowed
    edge-cases#mode = MIXIN       | edge cases are mixed in
    edge-cases#total = 81         | # of all combined edge cases
    edge-cases#tried = 37         | # of edge cases tried in current run
    seed = -3671341646525514134   | random seed to reproduce generated values
    ├─ jqwik (JUnit Platform) ✔
    │  └─ Props ✔
    │     └─ withAssume ✔
    ├─ JUnit Platform Suite ✔
    └─ JUnit Vintage ✔
    [         1 tests found           ]
    [         1 tests successful      ]
    [         0 tests failed          ]
  stats(int)
    timestamp = 2026-10-04T00:34:19.496794, [Props:stats] (100) 范围 =
        51-100 (50) : 50 %
        11-50  (40) : 40 %
        1-10   (10) : 10 %
    timestamp = 2026-10-04T00:34:19.497443, [Props:stats] (100) 奇偶 =
        奇数 (50) : 50 %
        偶数 (50) : 50 %
                                  |-----------------------jqwik-----------------------
    tries = 100                   | # of calls to property
    checks = 100                  | # of not rejected calls
    generation = EXHAUSTIVE       | parameters are exhaustively generated
    after-failure = SAMPLE_FIRST  | try previously failed sample, then previous seed
    when-fixed-seed = ALLOW       | fixing the random seed is allowed
    edge-cases#mode = MIXIN       | edge cases are mixed in
    edge-cases#total = 0          | # of all combined edge cases
    edge-cases#tried = 0          | # of edge cases tried in current run
    seed = 8106488218275411       | random seed to reproduce generated values
    ├─ jqwik (JUnit Platform) ✔
    │  └─ Props ✔
    │     └─ stats ✔
    ├─ JUnit Platform Suite ✔
    └─ JUnit Vintage ✔
    [         1 tests found           ]
    [         1 tests successful      ]
    [         0 tests failed          ]
  generated(int, java.lang.String)
      arg0: 2
      arg1: "N"
      arg0: 0
      arg1: ""
      arg0: 8
      arg1: "a"
      arg0: 9
      arg1: "A"
      arg0: 4
      arg1: "WMl"
      arg0: 1
      arg1: "iAT"
      arg0: 1
      arg1: "z"
      arg0: 0
      arg1: "A"
      arg0: 4
      arg1: "z"
      arg0: 7
      arg1: ""
      arg0: 0
      arg1: "Z"
      arg0: 9
      arg1: "al"
      arg0: 7
      arg1: ""
      arg0: 8
      arg1: "YK"
      arg0: 5
      arg1: "PxI"
      arg0: 3
      arg1: "vp"
      arg0: 2
      arg1: "f"
      arg0: 0
      arg1: "a"
      arg0: 5
      arg1: "Z"
      arg0: 6
      arg1: ""
                                  |-----------------------jqwik-----------------------
    tries = 20                    | # of calls to property
    checks = 20                   | # of not rejected calls
    generation = RANDOMIZED       | parameters are randomly generated
    after-failure = SAMPLE_FIRST  | try previously failed sample, then previous seed
    when-fixed-seed = ALLOW       | fixing the random seed is allowed
    edge-cases#mode = MIXIN       | edge cases are mixed in
    edge-cases#total = 20         | # of all combined edge cases
    edge-cases#tried = 3          | # of edge cases tried in current run
    seed = 505804894492505318     | random seed to reproduce generated values
    ├─ jqwik (JUnit Platform) ✔
    │  └─ Props ✔
    │     └─ generated ✔
    ├─ JUnit Platform Suite ✔
    └─ JUnit Vintage ✔
    [         1 tests found           ]
    [         1 tests successful      ]
    [         0 tests failed          ]

分步演示

Java 属性测试
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12;jqwik 1.9.1 运行在 JUnit Platform 1.11.3 的 ConsoleLauncher 上
    • 没有验证:状态化测试(@Property + Action)、自定义 Arbitrary 的收缩质量、jqwik 与 JUnit Jupiter 引擎并存的行为、并行执行、反复运行的种子分布(每次运行的随机数据不同,文中的 tries / 原始样本是某一次运行的结果)
    京ICP备2026067502号-1京公网安备11010502063487号