bigywater.tech
Java · 329

Java 模块化测试

模块化之后测试怎么写?测试能不能访问没有导出的包和包级私有成员?为什么 JUnit 需要 --add-opens?这篇用一个小模块把黑盒、越界访问、白盒三种测试都跑一遍。

4 → 0 → 4
白盒测试加 --add-opens 前后通过的个数:不加 0 个通过,加了 4 个通过
not visible
黑盒测试去碰未导出的包时编译器报的错误
IllegalAccessError
没有 --add-exports 运行时抛出的错误

被测模块

实测 · 本机执行输出
  文件: ['com/shop/api/Pricing.java', 'com/shop/internal/Tax.java', 'module-info.java']
  java --describe-module: com.shop file://<实验目录> | exports com.shop.api | requires java.base mandated | contains com.shop.internal |

黑盒测试

实测 · 本机执行输出
  编译黑盒测试(--module-path mods --add-modules com.shop,junit 在类路径):rc 0 
  运行: ('1 tests found | 1 tests successful | 0 tests failed', [])

越界访问

实测 · 本机执行输出
  编译:rc 1
    test_bad/ReachInTest.java:1: error: package com.shop.internal is not visible
    (package com.shop.internal is declared in module com.shop, which does not export it)
    1 error
  加 --add-exports com.shop/com.shop.internal=ALL-UNNAMED 之后编译:rc 0
  运行(没有 --add-exports): ('1 tests found | 0 tests successful | 1 tests failed', ['=> java.lang.IllegalAccessError: class ReachInTest (in unnamed module @0x39fb3ab6) cannot access class com.shop.internal.Tax (in module com.shop) because module com.shop does not export com.shop.internal to unnamed module @0x39fb3ab6'])
   ['=> java.lang.IllegalAccessError: class ReachInTest (in unnamed module @0x39fb3ab6) cannot access class com.shop.internal.Tax (in module com.shop) because module com.shop does not export com.shop.inter']
  运行(加 --add-exports): ('1 tests found | 1 tests successful | 0 tests failed', [])

白盒测试

实测 · 本机执行输出
  编译(--add-modules com.shop --patch-module com.shop=test_white --add-reads com.shop=ALL-UNNAMED):rc 0 
  运行(没有 --add-opens): ('4 tests found | 0 tests successful | 4 tests failed', ['│     ├─ of() ✘ class org.junit.platform.commons.util.ReflectionUtils cannot access class com.shop.internal.TaxTest (in module com.shop) because module com.shop does not export com.shop.internal to unnamed module @c8e4bb0', '│     ├─ inst() ✘ class org.junit.platform.commons.util.ReflectionUtils cannot access class com.shop.internal.TaxTest (in module com.shop) because module com.shop does not export com.shop.internal to unnamed module @c8e4bb0', '│     └─ secretIsPackagePrivate() ✘ class org.junit.platform.commons.util.ReflectionUtils cannot access class com.shop.internal.TaxTest (in module com.shop) because module com.shop does not export com.shop.internal to unnamed module @c8e4bb0'])
   ['│  │  └─ gross() ✘ Unable to make void com.shop.api.PricingTest.gross() accessible: module com.shop does not "opens com.shop.api" to unnamed module @c8e4bb0', '│     ├─ of() ✘ class org.junit.platform.commons.util.ReflectionUtils cannot access class com.shop.internal.TaxTest (in module com.shop) because module com.shop does not export com.shop.internal to unnamed module @c8e4bb0', '│     ├─ inst() ✘ class org.junit.platform.commons.util.ReflectionUtils cannot access class com.shop.internal.TaxTest (in module com.shop) because module com.shop does not export com.shop.internal to unnamed module @c8e4bb0', '│     └─ secretIsPackagePrivate() ✘ class org.junit.platform.commons.util.ReflectionUtils cannot access class com.shop.internal.TaxTest (in module com.shop) because module com.shop does not export com.shop.internal to unnamed modu']
  运行(加 --add-opens com.shop/com.shop.internal=ALL-UNNAMED 与 com.shop/com.shop.api=ALL-UNNAMED): ('4 tests found | 4 tests successful | 0 tests failed', [])
   ['├─ JUnit Platform Suite ✔', '├─ JUnit Jupiter ✔', '│  ├─ PricingTest ✔', '│  │  └─ gross() ✔', '│  └─ TaxTest ✔', '│     ├─ of() ✔', '│     ├─ inst() ✔', '│     └─ secretIsPackagePrivate() ✔']

类路径模式

实测 · 本机执行输出
  编译:rc 0
  运行: ('4 tests found | 4 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;junit-platform-console-standalone 1.11.3(放在类路径,被测模块在模块路径);只有一个小模块 com.shop
    • 没有验证:Maven Surefire / Gradle 对模块测试的自动处理、测试自己也是命名模块(module-info 的测试模块)的做法、JUnit 放在模块路径上、opens 写进 module-info 的 qualified opens、多个模块之间的 --patch-module 组合、jlink 之后的测试
    京ICP备2026067502号-1京公网安备11010502063487号