| 构件 | io.github.microsphere-projects:microsphere-java-test(0.3.20-SNAPSHOT,release 分支) |
| 角色 | Shared fixture artifact — types whose only purpose is to be reflected over, compiled, or annotation-processed by other modules |
| 主源码类型 | 16 — io.microsphere.test.annotation (1) · io.microsphere.test.model (11) · io.microsphere.test.service (4) |
| 可执行逻辑 | None beyond getters, setters, toString() and "[ECHO] " + message concatenation |
| 依赖 | 9 declared, all optional, none with <scope> — microsphere-java-test/pom.xml:21-85 |
| 随包发布的资源 | src/main/resources/junit-platform.properties, src/main/resources/logback-test.xml; no META-INF/services files |
| 测试 | 14 JUnit 5 test classes under src/test/java/io/microsphere/test/ |
| 字节码级别 | Not set in this repository; inherited from the external parent microsphere-build:0.3.16 (pom.xml:6-10). CI matrix: Temurin java: [ '8' , '11' , '17' , '21' , '25' ] (.github/workflows/maven-build.yml:25, distribution: 'temurin' :33) |
Note
本页使用的引用简写。 模块源码的路径相对于 microsphere-java-test/src/main/java/io/microsphere/
(test/model/Model.java:29),本模块自身测试的路径相对于 microsphere-java-test/src/test/java/io/microsphere/
(test/service/TestServiceImplTest.java:31)。跨模块路径写全。
microsphere-java-test 是 reactor 中第 6 个模块(pom.xml:63,<modules> 在 pom.xml:58-66),这个位置排在
microsphere-java-annotations(:60)、microsphere-java-core(:61)和 microsphere-jdk-tools(:62)之后,
并排在 microsphere-annotation-test(:64)、microsphere-lang-model(:65)和
microsphere-annotation-processor(:66)之前。这个顺序并非装饰性的:正是它使得
microsphere-annotation-test 能够以 compile 作用域依赖这些夹具
(microsphere-annotation-test/pom.xml:24-28)并把它们向下游再导出。
graph TD
subgraph PROD["生产层"]
ANNO["microsphere-java-annotations"]
CORE["microsphere-java-core"]
JDKTOOLS["microsphere-jdk-tools"]
end
subgraph FIX["夹具层"]
JTEST["microsphere-java-test"]
end
subgraph HARNESS["测试装置层"]
ATEST["microsphere-annotation-test"]
end
subgraph CONSUMERS["lang-model / processor"]
LANGMODEL["microsphere-lang-model"]
AP["microsphere-annotation-processor"]
end
ANNO --> CORE
CORE --> JDKTOOLS
ANNO -. optional .-> JTEST
JTEST -. test scope : microsphere-java-core/pom.xml:66-71 .-> CORE
JTEST -. test scope : microsphere-jdk-tools/pom.xml:45-48 .-> JDKTOOLS
JTEST -- compile scope : microsphere-annotation-test/pom.xml:24-28 --> ATEST
JDKTOOLS --> ATEST
ATEST -. test scope : microsphere-lang-model/pom.xml:52-57 .-> LANGMODEL
LANGMODEL --> AP
为什么是独立构件、而不是每个模块各自的 src/test 树。 有三个机制使这次拆分真正承重:
- 夹具必须能以源码形式对
javac可见,而不只是字节码。 测试装置把Class<?>对象喂进一次真实的编译器调用 (microsphere-annotation-test/src/main/java/io/microsphere/test/annotation/processing/CompilerInvocationInterceptor.java:52-63), 随后它们以TypeElement/DeclaredType的形式暴露出来。要让这件事成立,同一个类也必须位于运行时 classpath 上,这样测试才能把反射视图与语言模型视图做对比。每个模块src/test里的一份副本会产生由不同 class loader 加载 的两个不同 Class 对象。 - "全 optional"这个技巧只有打成 jar 才成立。
microsphere-java-test以optional方式请求 JUnit、Mockito、 Logback、JAX-RS、JAX-WS 与 Spring(microsphere-java-test/pom.xml:31-84),因此消费方完全掌握自己的版本 (microsphere-lang-model/pom.xml:30-91又把它们全部按test作用域声明了一遍)。位于src/test树之内的夹具 根本无法表达这一契约。 TestServiceImpl上的注解否则会漏进生产 jar。@Service、@ComponentScans、@ServiceMode、@Cacheable和@TestAnnotation全部叠在同一个类型上(test/service/TestServiceImpl.java:41-76),而spring-context/spring-web是本模块 compile 作用域的 optional 依赖 (microsphere-java-test/pom.xml:73-83)。如果把这些夹具放进microsphere-java-core,就会把 Spring 拖进一个本应 保持无依赖的模块。
| # | 类型 | 种类 | 声明位置 | 父类型 / 值得注意的成员 | 用途 |
|---|---|---|---|---|---|
| 1 | TestAnnotation |
annotation | test/annotation/TestAnnotation.java:43 |
RUNTIME, TYPE, @Documented (:40-42) |
14 attributes covering every annotation member kind |
| 2 | Ancestor |
class | test/model/Ancestor.java:27 |
implements Serializable |
Level 3 of the POJO chain; boolean z (:29) |
| 3 | Parent |
class | test/model/Parent.java:25 |
extends Ancestor |
Level 2; byte b, short s, int i, long l (:27-33) |
| 4 | Model |
class | test/model/Model.java:29 |
extends Parent |
Level 1; float, double, TimeUnit, String, BigInteger, BigDecimal (:31-41) |
| 5 | PrimitiveTypeModel |
class | test/model/PrimitiveTypeModel.java:25 |
getters only (:43-73) |
All 8 primitives, no setters |
| 6 | SimpleTypeModel |
class | test/model/SimpleTypeModel.java:29 |
13 accessor pairs + 1 orphan field | Wrapper catalogue incl. Void (:31); private int invalid has no accessor (:57) |
| 7 | Color |
enum | test/model/Color.java:25 |
RED(1), YELLOW(2), BLUE(3) |
Enum with state, getValue() (:44), overridden toString() (:38-42) |
| 8 | ArrayTypeModel |
class | test/model/ArrayTypeModel.java:25 |
5 array fields | int[], String[], PrimitiveTypeModel[], Model[], Color[] (:27-35) |
| 9 | CollectionTypeModel |
class | test/model/CollectionTypeModel.java:31 |
5 Collection sub-interfaces |
Collection/List/Queue/Deque/Set, element kinds vary (:33-41) |
| 10 | MapTypeModel |
class | test/model/MapTypeModel.java:31 |
5 Map variants |
Map/SortedMap/NavigableMap/HashMap/TreeMap (:33-41) |
| 11 | StringArrayList |
class | test/model/StringArrayList.java:29 |
extends ArrayList<String> |
Empty body — a type argument fixed by inheritance |
| 12 | ConfigurationPropertyModel |
class | test/model/ConfigurationPropertyModel.java:29 |
5 fields, each @ConfigurationProperty |
Metadata fixture; property names prefixed microsphere.annotation.processor. (:31-44) |
| 13 | TestService |
interface | test/service/TestService.java:36 |
@Path("/echo") (:35) |
5 methods with JAX-RS annotations; spurious <T> on echo (:39) |
| 14 | DefaultTestService |
class | test/service/DefaultTestService.java:29 |
implements TestService |
Full implementation; echo returns "[ECHO] " + message (:34-36) |
| 15 | GenericTestService |
class | test/service/GenericTestService.java:27 |
extends DefaultTestService implements TestService, EventListener |
Re-declares TestService, adds the EventListener marker (:19, :27) |
| 16 | TestServiceImpl |
class | test/service/TestServiceImpl.java:77 |
extends GenericTestService implements TestService, AutoCloseable, Serializable |
The annotation-saturated leaf; 2 constructors (:84-90) |
各族数量:1 / 11 / 4 = 16,与声明的包划分一致。种类分布为 1 annotation interface + 1 interface + 1 enum +
13 classes。16 个类型中有 14 个拥有专属测试类;TestAnnotation 与 TestService 没有——它们通过 TestServiceImpl
以及 microsphere-lang-model 被间接测试(microsphere-lang-model/src/test/java/io/microsphere/lang/model/util/AnnotationUtilsTest.java:714)。
消费路径是一个由 JUnit 5 InvocationInterceptor 驱动的模板方法,而这个拦截器位于装置模块、不在本模块。夹具提供
类;装置负责编译它们。
sequenceDiagram
participant T as TypeUtilsTest (microsphere-lang-model)
participant I as CompilerInvocationInterceptor
participant A as AbstractAnnotationProcessingTest
participant C as io.microsphere Compiler (jdk-tools)
participant P as AnnotationProcessingTestProcessor
participant R as ServiceLoader of Processor
T->>I: interceptTestMethod(invocation, ctx, extCtx)
I->>I: compiledClassesSet.add(testClass)
I->>A: addCompiledClasses(compiledClassesSet)
Note over A: 基类中的空钩子,由测试子类覆写
A-->>I: ArrayTypeModel, CollectionTypeModel, Color, MapTypeModel
I->>C: compiler.sourcePaths(compiledClasses)
C-->>P: Elements / RoundEnvironment / TypeElement per class
I->>R: load(Processor.class, classLoader)
R-->>P: SPI processors appended
I->>C: compiler.compile(compiledClasses)
P->>T: testTypeElement / testDeclaredType populated
每一跳的证据:
| 跳 | 源码 |
|---|---|
| 拦截器入口 | microsphere-annotation-test/src/main/java/io/microsphere/test/annotation/processing/CompilerInvocationInterceptor.java:42-44 |
| 最先加入测试类自身 | CompilerInvocationInterceptor.java:47-49 |
| 钩子被调用 | CompilerInvocationInterceptor.java:50 |
| 钩子默认为空实现 | microsphere-annotation-test/src/main/java/io/microsphere/test/annotation/processing/AbstractAnnotationProcessingTest.java:133-134 |
| 编译器接收到这些类 | CompilerInvocationInterceptor.java:52-55 |
| 通过 SPI 装载 Processor | CompilerInvocationInterceptor.java:59-62 |
| 触发编译 | CompilerInvocationInterceptor.java:63 |
| 消费方对钩子的覆写 | microsphere-lang-model/src/test/java/io/microsphere/lang/model/util/TypeUtilsTest.java:189-193 |
| 被填充的语言模型句柄 | AbstractAnnotationProcessingTest.java:110-126, :136-142 |
运行时视图还存在第二条完全独立的发现路径:microsphere-java-core 只是用 Class#getDeclaredFields()
之类反射这些类,正如它自己的 io.microsphere.test 夹具那样
(microsphere-java-core/src/test/java/io/microsphere/reflect/TypeUtilsTest.java:22-29)。两条路径只在断言处汇合,
测试在那里把一个 TypeMirror 与一个 Class 相比较。
flowchart LR
FIXTURE["test/model/Model.java:29"] -->|测试 classpath 上的字节码| REFL["java.lang.reflect: getDeclaredFields()"]
FIXTURE -->|sourcePaths(compiledClasses)| JAVAC["javac: TypeElement + DeclaredType"]
REFL --> A1["core 的断言 (microsphere-java-core/pom.xml:66-71)"]
JAVAC --> A2["lang-model 的断言 (TypeUtilsTest.java:189-193)"]
A1 --> SAME["同一个 Class 对象,两个视图"]
A2 --> SAME
Important
本模块没有 META-INF/services 文件。src/main/resources 只包含 junit-platform.properties 和
logback-test.xml,已通过列目录确认。这些夹具是通过直接的编译期引用
(import io.microsphere.test.model.ArrayTypeModel,见
microsphere-lang-model/src/test/java/io/microsphere/lang/model/util/TypeUtilsTest.java:19)被发现,或者被交给
addCompiledClasses——从来不是通过 service loader。CompilerInvocationInterceptor.java:60 处的
ServiceLoader<Processor> 调用属于装置模块,解析的是 microsphere-annotation-processor 的注册项,与本模块无关。
test.model 里的十一个类型不是随机拼凑的;它们坐落在两条正交的轴上,而源码中每一处字段注释都是关于它在探测哪条
轴的说明。
轴 A——继承深度。 Ancestor → Parent → Model 从同一个根出发给出 1、2、3 层深的链:
public class Ancestor implements Serializable {
private boolean z;
public class Parent extends Ancestor {
private byte b;
private short s;
private int i;
private long l;
public class Model extends Parent {
private float f;
private double d;
private TimeUnit tu;
private String str;
private BigInteger bi;
private BigDecimal bd;(test/model/Ancestor.java:27-29、test/model/Parent.java:25-33、test/model/Model.java:29-41——合并为一个代码块,
声明保持逐字原样。)一个只停在 getDeclaredFields() 的字段遍历工具在 Model 上看到 6 个字段;一个沿
getSuperclass() 递归的工具看到 11 个。StringArrayList(test/model/StringArrayList.java:29)是同一条轴上零字段的
极端形态:它除了一个被解析出的父类型参数之外什么都不添加。
轴 B——元素种类。 ArrayTypeModel、CollectionTypeModel 和 MapTypeModel 各持有五个字段,其元素种类按同一种
顺序递增复杂度,源码注释明确写着:
| 探测的种类 | ArrayTypeModel (test/model/ArrayTypeModel.java:27-35) |
CollectionTypeModel (test/model/CollectionTypeModel.java:33-41) |
MapTypeModel (test/model/MapTypeModel.java:33-41) |
|---|---|---|---|
| primitive | int[] integers |
— | — |
| simple | String[] strings |
Collection<String> strings |
Map<String, String> strings |
| enum | Color[] colors |
List<Color> colors |
SortedMap<String, Color> colors |
| 扁平 POJO | PrimitiveTypeModel[] primitiveTypeModels |
Queue<PrimitiveTypeModel> primitiveTypeModels |
NavigableMap<Color, PrimitiveTypeModel> primitiveTypeModels |
| 层级 POJO | Model[] models |
Deque<Model> models |
HashMap<String, Model> models |
| POJO 数组作为元素 | — | Set<Model[]> modelArrays |
TreeMap<PrimitiveTypeModel, Model[]> modelArrays |
两处刻意的不对称使这两条轴可测:
- 访问器完备性不同。
PrimitiveTypeModel只有 getter(test/model/PrimitiveTypeModel.java:43-73);SimpleTypeModel为 13 个字段提供 getter 与 setter,但对private int invalid没有任何访问器 (test/model/SimpleTypeModel.java:57);StringArrayList什么都不提供 (test/model/StringArrayList.java:29-30)。因此一个 bean 自省路径会从同一个包里的三个夹具得到三个不同的答案。 - 同一角色内的声明原始类型不同。
MapTypeModel用具体类声明了两个字段 (test/model/MapTypeModel.java:39、:41),而另外三个用接口(:33、:35、:37)。归一化到Map的消费方会 丢失这份信息,而microsphere-lang-model/src/test/java/io/microsphere/lang/model/util/TypeUtilsTest.java:189-193正是为捕捉这一点而搭起来的。
Color(test/model/Color.java:25-46)与 ConfigurationPropertyModel
(test/model/ConfigurationPropertyModel.java:29-44)是这个包的两个"值承载"夹具:前者证明一个带构造参数并覆写了
toString() 的枚举能在两个视图中往返,后者证明 @ConfigurationProperty 的每个属性
(name、type、defaultValue、required、description)都能在注解抽取中存活。
四个类型,一个接口,三级实现,每一级增加一个正交特征:
| 层级 | 类型 | 该层级增加了什么 | 源码 |
|---|---|---|---|
| 0 | TestService |
JAX-RS 契约:@Path("/echo")、@GET/@POST/@PUT、@PathParam、@DefaultValue;五个方法 |
test/service/TestService.java:35-54 |
| 1 | DefaultTestService |
五个方法的一个具体实现;三个返回 null,两个返回实值 |
test/service/DefaultTestService.java:29-56 |
| 2 | GenericTestService |
重复声明 TestService(冗余)并加上 java.util.EventListener;用完全相同的方法体覆写 echo |
test/service/GenericTestService.java:27-31 |
| 3 | TestServiceImpl |
Spring + JAX-WS + @TestAnnotation 元数据;两个构造器;AutoCloseable + Serializable |
test/service/TestServiceImpl.java:41-90 |
test/service/GenericTestService.java:27 与 test/service/TestServiceImpl.java:77 处冗余的
implements TestService——尽管 DefaultTestService 在 test/service/DefaultTestService.java:29 已经实现过它——
正是夹具在发挥作用:一个朴素的层次遍历器若用 getInterfaces() 收集接口,会遇到 TestService 三次,而消费方的期望值
数组证明了去重是有效的:
private static final Type[] ALL_TYPES = ofArray(SELF_TYPE, SUPER_CLASS, DefaultTestService.class, Object.class, TestService.class, EventListener.class, AutoCloseable.class, Serializable.class);(microsphere-lang-model/src/test/java/io/microsphere/lang/model/util/TypeUtilsTest.java:131,其中
SELF_TYPE = TestServiceImpl.class 在 :121,SUPER_CLASS = GenericTestService.class 在 :126;:136、:141
和 :146 给出了裁剪后的变体。)
TestServiceImpl 上的 @TestAnnotation(test/service/TestServiceImpl.java:57-76)是本模块中最大的单个注解字面量,
也是 TestAnnotation 的 @since 成为必填项的原因(test/annotation/TestAnnotation.java:69):该字面量必须在
test/service/TestServiceImpl.java:70 处写出 since = @Since("1.0.0"),从而执行嵌套注解值,并在
test/service/TestServiceImpl.java:71-75 处执行 @ConfigurationProperty[] 数组值。
| 模式 | 真实存在的位置 | 证据 |
|---|---|---|
| Fixture Object / Object Mother | 本模块的每个类型;各自提供一个规范、构造廉价的形状 | test/model/Model.java:29, test/model/ArrayTypeModel.java:25 |
| Template Method(钩子) | addCompiledClasses——基类中为空,由消费方覆写以提名夹具 |
microsphere-annotation-test/.../AbstractAnnotationProcessingTest.java:133-134, overridden at microsphere-lang-model/src/test/java/io/microsphere/lang/model/util/TypeUtilsTest.java:189 |
| Marker Interface | Ancestor 上的 Serializable、GenericTestService 上的 EventListener、TestServiceImpl 上的 AutoCloseable/Serializable |
test/model/Ancestor.java:27, test/service/GenericTestService.java:27, test/service/TestServiceImpl.java:77 |
| 继承梯(分层夹具) | 两条互不相干的三级链,一条给字段、一条给服务 | test/model/Ancestor.java:27 → Parent.java:25 → Model.java:29; DefaultTestService.java:29 → GenericTestService.java:27 → TestServiceImpl.java:77 |
| Type Token / 泛型父类型捕获 | StringArrayList extends ArrayList<String> 且方法体为空 |
test/model/StringArrayList.java:29-30 |
| 值承载枚举 | Color(int value) 配 getValue() 与 toString() |
test/model/Color.java:31-46 |
| Null Object / 桩 | 三个 TestService 方法返回 null 而非抛异常 |
test/service/DefaultTestService.java:44-56 |
| 注解装饰叠加 | 框架元数据层层加在一个叶子类型上,却不改动其行为 | test/service/TestServiceImpl.java:41-76, :93 |
| 构造器链 / 阶梯式默认值 | TestServiceImpl() 委托给 this(null) |
test/service/TestServiceImpl.java:84-86 |
| Test Double(Mockito) | 被 mock 的 ApplicationContext 与 Environment,加上字段注入 |
test/service/TestServiceImplTest.java:27-32 |
经由 ServiceLoader 的 SPI |
存在于消费方装置中,本模块中不存在 | microsphere-annotation-test/.../CompilerInvocationInterceptor.java:59-62 |
刻意不予声称:没有 Builder、没有 Factory、没有 Singleton、没有 Strategy、没有 Observer。16 个类型中没有任何 分派、注册表或状态机——而这正是一个夹具模块的意义。
本模块从 src/main/resources 打包了两个文件,这意味着它们在每一个消费方的 classpath 上都可见——实际上也包括每个
消费方的 test classpath。
junit.jupiter.extensions.autodetection.enabled = true(src/main/resources/junit-platform.properties:1。)JUnit Platform 会从任意 classpath 根读取
junit-platform.properties,所以这一行会为任何依赖本构件的模块在整个测试运行中打开基于 ServiceLoader 的扩展自动
探测。请注意该装置并不依赖它:AbstractAnnotationProcessingTest 用
@ExtendWith(CompilerInvocationInterceptor.class) 显式注册了自己的拦截器
(microsphere-annotation-test/src/main/java/io/microsphere/test/annotation/processing/AbstractAnnotationProcessingTest.java:53)。
第二个文件是一份完整的 Logback 配置:
<configuration>
<property name="ENCODER_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %X{traceId} %level %logger{0} [%t] %m%n"/>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>test.log</file>
<append>false</append>
<immediateFlush>false</immediateFlush>(src/main/resources/logback-test.xml:1-12;该文件继续包含包裹 FILE 的 ASYNC appender(:14-16)、一个
STDOUT 控制台 appender(:18-22)、root level="INFO"(:24-26),以及一个名为 io.microsphere、级别 TRACE、
additivity="false" 的 logger(:28-30)。)由于 logback-test.xml 会先于 logback.xml 被按名字拾取,依赖本构件的
消费方可能最终把工作目录下的 test.log 写出来。
Warning
这两个资源都是打包期的决定、运行时的后果。如果你是本模块的维护者,请把 src/main/resources 当作一个 API
界面:在那里新增第三个文件(例如另一个 META-INF/services 条目或第二份 properties 文件)会同时把它发布给所有
消费方。如果你是消费方并希望恢复旧行为,请添加你自己的 src/test/resources/junit-platform.properties 和
logback-test.xml——位于你自己源码树中的那份副本在 Surefire 的 classpath 排序中优先。
十四个测试类覆盖了拥有配对测试的十四个类型(TestService 与 TestAnnotation 没有直接测试)。形状高度统一,扩展本
模块时值得照此匹配:
| 关注点 | 约定 | 证据 |
|---|---|---|
| 测试类命名 | <Type>Test,package-private 类 |
test/model/ModelTest.java:17, test/service/TestServiceImplTest.java:18 |
| 夹具构造 | @BeforeEach 新建实例,不共享状态 |
test/model/ModelTest.java:21-24 |
| 默认值契约 | 在任何 setter 运行前断言每个字段为 0/0.0f/null |
test/model/ModelTest.java:27-37 |
| 引用同一性 | 对实际传入的集合使用 assertSame |
test/model/CollectionTypeModelTest.java:59 |
| 别名(aliasing)契约 | 在 set… 之后修改源集合,断言其可见 |
test/model/CollectionTypeModelTest.java:124-134 |
| 置空赋值 | 每个 setter 以 null 调用并重新读取 |
test/model/CollectionTypeModelTest.java:105-121 |
| 继承 | assertTrue(model instanceof Parent, …) |
test/model/ModelTest.java:84 |
| 测试替身 | mock(ApplicationContext.class) / mock(Environment.class) |
test/service/TestServiceImplTest.java:27-28 |
| 字段注入 | 对 protected 字段执行 testService.context = mockContext; |
test/service/TestServiceImplTest.java:32 |
| 两个构造器 | 无参构造导致 environment 为 null |
test/service/TestServiceImplTest.java:71-74 |
本模块自身的测试是在测夹具的行为,而不是测库的逻辑。这是一条微妙但真实的义务:由于
CollectionTypeModelTest.java:124-134 断言 setter 是别名存储而非拷贝存储,未来一次在 setColors
(test/model/CollectionTypeModel.java:55-57)内部做防御性拷贝的"改进"会在这里失败——并且还会悄然改变消费方通过
反射所观察到的东西。
Tip
新增夹具类型时,在同一次提交里加上配对的 <Type>Test,并断言既有测试所断言的四件事:默认值、set/get 的同一性、
置空赋值,以及任何别名或继承契约。消费方(microsphere-java-core、microsphere-lang-model)会把期望的字段与类型
列表硬编码下来,因此一个没有本地测试的夹具,就是一个可以在它们脚下悄然变形的夹具。
| 我想…… | 扩展点 | 做法 |
|---|---|---|
为一次 javac 轮次提名更多夹具 |
addCompiledClasses(Set<Class<?>>) |
在你的测试类中覆写它,正如 TypeUtilsTest.java:189-193 所做 |
| 把夹具暴露给一个新模块 | 添加一个 test 作用域依赖 |
照抄 microsphere-java-core/pom.xml:66-71;只有当你也是像 microsphere-annotation-test 那样的装置模块时才使用 compile 作用域(microsphere-annotation-test/pom.xml:24-28) |
| 往覆盖率矩阵添加一种新的注解成员种类 | TestAnnotation |
在 test/annotation/TestAnnotation.java:45-71 添加属性,并且在 test/service/TestServiceImpl.java:57-76 的字面量里给它一个值,否则必填属性的问题会咬人 |
| 探测一种新的类型形状 | test.model 中的新类 |
遵循"每个种类一个字段"的约定;除非不对称本身才是被测对象,否则保持访问器对称 |
| 探测新的框架注解散布面 | TestServiceImpl 的类级注解 |
先在 microsphere-java-test/pom.xml:21-85 把该依赖声明为 optional |
| 修改消费方的测试日志 | src/main/resources/logback-test.xml |
注意它是被发布的、不是本地的——见 §7 |
| 调整 JUnit 扩展发现 | src/main/resources/junit-platform.properties |
同样的告诫;装置使用显式 @ExtendWith(AbstractAnnotationProcessingTest.java:53) |
本模块没有属于自己的 SPI(见上文 §3 的说明),这意味着扩展它永远是源码级或 POM 级的改动——绝不是注册。
- 不带任何成员的
@ServiceMode(test/service/TestServiceImpl.java:42,import 在:30)。该类型上的其他注解 都带值,唯独这个一个都不带。无论意图如何,一个遍历注解成员值的断言循环在它上面会看到零个条目——所以如果这正 是被测场景,就值得配一条注释;如果不是,那就是缺了属性。 - 一个没有任何用途的类型参数。
TestService.echo被声明为<T> String echo(...)(test/service/TestService.java:39),而每个实现者覆写的都是擦除后的形式 (test/service/DefaultTestService.java:34、test/service/GenericTestService.java:29、test/service/TestServiceImpl.java:94)。如果多余的类型变量正是夹具的目的,请写明;否则删掉它——它也是本模块中 唯一的泛型签名。 - 死字段。
DefaultTestService声明了private String name(test/service/DefaultTestService.java:31), 没有访问器,模块内也没有任何读取点。其他每个夹具字段都是可达的;这一个对基于反射的消费方不可见,只能通过getDeclaredFields()看到,这让它看起来像无心之失。 SimpleTypeModel.invalid(test/model/SimpleTypeModel.java:57)大概是有意的——不可达字段是个不错的探针—— 但它是模块中唯一一个名字编码了判断而非类型、且没有任何测试对它做出断言的字段。- 元素种类注释里的复制粘贴。
CollectionTypeModel.modelArrays是Set<Model[]>,其注释却写着 "The composite element is hierarchical POJO type"(test/model/CollectionTypeModel.java:41),与紧邻其上的Deque<Model>那一行(:39)措辞完全相同。同样的措辞也出现在HashMap<String, Model>(test/model/MapTypeModel.java:39)。数组装进集合恰恰是最有趣的情形,却被标注得好像它们是普通 POJO。 - 具体声明类型打破了包自身的约定。
HashMap与TreeMap作为字段类型 (test/model/MapTypeModel.java:39、:41),而ArrayTypeModel与CollectionTypeModel全用接口。这对测试 "声明类型 vs 归一化类型"的解析有用,但没有文档——读者必须把三个模型互相对比才能注意到。 TreeMap<PrimitiveTypeModel, Model[]>没有自然键序。PrimitiveTypeModel什么都没实现、什么都没覆写 (test/model/PrimitiveTypeModel.java:25-74),因此该字段只能被赋以一个自带 comparator 的 map 或null。没有测试 这样做;MapTypeModelTest大概只断言了 null 默认值。- 被发布的测试资源。 位于
src/main/resources下的junit-platform.properties与logback-test.xml(§7)是本 模块最出人意料的设计决策。把它们移到src/test/resources可以让它们继续服务于本模块自己的 14 个测试,同时不再 强加给 3 个以上下游模块——但那也会破坏 reactor 当前享有的(未写文档、看起来是刻意的)便利。 - 每个依赖都是
optional,包括microsphere-java-annotations(microsphere-java-test/pom.xml:24-29)。TestAnnotation导入了io.microsphere.annotation.ConfigurationProperty与Since(test/annotation/TestAnnotation.java:20-21),因此省略 annotations 构件的消费方在运行时无法读取@TestAnnotation。它照样能编译通过——只有在反射解析时才需要注解类——这让失败模式变成静默而非响亮。 - 与
microsphere-java-core的包名冲突。 core 的测试树拥有microsphere-java-core/src/test/java/io/microsphere/test/(A.java、Data.java、StringIntegerHashMap.java……), 而本模块拥有io.microsphere.test.annotation|model|service。两者都是io.microsphere.test。运行时什么都不坏, 但它会挡住 split-package 检查(JPMS),并且在新贡献者眼中读起来像一个错误。 - 两个接口形态的夹具没有测试。
TestAnnotation与TestService只通过TestServiceImpl和microsphere-lang-model被覆盖。TestAnnotation属性集的一次回归会先在另一个模块的构建中暴露。 - 冗余的
implements TestService。 沿梯级声明了三次(test/service/DefaultTestService.java:29、test/service/GenericTestService.java:27、test/service/TestServiceImpl.java:77)。无害,甚至可以当作重复接口的 探针——但同样没有文档。
- 开发者指南索引
- 本页英文版:English Developer Guide
- 本模块的用户指南:microsphere-java-test —— 用户指南
- microsphere-java-annotations —— 提供
@Since与@ConfigurationProperty,即@TestAnnotation的两个注解型属性(test/annotation/TestAnnotation.java:69,:71) - microsphere-annotation-test —— 其
addCompiledClasses钩子是本模块主要消费点的 装置(AbstractAnnotationProcessingTest.java:133-134) - microsphere-lang-model —— 模型与服务夹具的最大消费方
- microsphere-jdk-tools —— 提供夹具被送入的那个
Compiler(CompilerInvocationInterceptor.java:54-55) - microsphere-annotation-processor —— 读取
ConfigurationPropertyModel - core 模块页面:microsphere-java-core 子包索引,尤其是 反射、类型与 Bean 与 配置属性元数据