如何为多继承接口的对象层级编写Google Truth Subjects?
问题背景与场景
我基于Java 17开发个人项目,用JUnit5/Jupiter和Google Truth编写测试。项目里通过接口多继承定义类,核心代码结构如下:
public interface Taggable { public String getTagName(); public String getSimpleContent(); // ... 其他方法 } public interface CreatureContainer { public Collection<Creature> getCreatures(); public boolean addCreature(Creature c); public void announce(String message); // ... 其他方法 } public class Area implements Taggable, CreatureContainer { // 可能实现更多接口 private String name; private Set<Creature> creatures; // 构造方法等 @Override public String getTagName() { return this.getClass().getName(); } @Override public String getSimpleContent() { return this.name; } @Override public Collection<Creature> getCreatures() { return creatures; } // ...其他实现 }
由于Google Truth的Subject是类,Java不支持多继承,无法像业务代码那样给AreaSubject同时继承TaggableSubject和CreatureContainerSubject:
public class TaggableSubject extends Subject {}; public class CreatureContainerSubject extends Subject {}; public class AreaSubject extends TaggableSubject, CreatureContainerSubject {}; // 编译错误!
我想到两种解决方案:
方案1:拆分Subject并提供转换方法
为Taggable、CreatureContainer和Area分别编写对应的Subject,在AreaSubject中添加转换方法:
public TaggableSubject asTaggable() { return check("this").that(actual); } public CreatureContainerSubject asCreatureContainer() { return check("this").that(actual); }
方案2:单一Subject整合所有断言
只编写AreaSubject,把所有接口对应的断言方法都整合进去:
public StringSubject tagName() { return check("getTagName()").that(actual.getTagName()); } // 或者封装成更直接的断言 public void hasTagName(String tagName) { this.tagName().isEqualTo(tagName); } public void hasCreature(Creature c) { check("getCreatures()").that(actual.getCreatures()).contains(c); }
目前打算用方案1,但想知道哪种更符合Google Truth的设计范式?有没有更优的替代方案?
方案分析与建议
哪种方案更符合Google Truth范式?
Google Truth的设计核心是让断言代码贴近业务语义,同时鼓励复用和模块化。
方案1更贴合Truth的范式:
- 它遵循了"接口对应Subject"的模块化思路,
TaggableSubject和CreatureContainerSubject可以被所有实现对应接口的类复用(比如之后如果有Item implements Taggable,直接用TaggableSubject即可),不用重复编写断言逻辑。 - 通过
asTaggable()、asCreatureContainer()的转换,测试代码语义清晰:比如assertThat(area).asTaggable().hasTagName("Area");,一眼就能看出是在验证Area作为Taggable的行为。
方案2的问题在于:
- 断言逻辑完全耦合在
AreaSubject里,无法复用给其他实现Taggable或CreatureContainer的类。 - 如果接口方法增多,
AreaSubject会变得臃肿,不符合单一职责原则。
更优的优化方案
可以在方案1的基础上做一点改进,让语义和复用性更完善:
- 给每个接口Subject提供静态工厂方法,比如在
TaggableSubject里:
public static TaggableSubject assertThat(Taggable actual) { return Truth.assertAbout(taggableSubject()).that(actual); } private static SubjectFactory<TaggableSubject, Taggable> taggableSubject() { return TaggableSubject::new; }
- 在
AreaSubject的转换方法里直接调用这些工厂方法,替代check("this"):
public TaggableSubject asTaggable() { return TaggableSubject.assertThat(actual); } public CreatureContainerSubject asCreatureContainer() { return CreatureContainerSubject.assertThat(actual); }
这种方式既保留了方案1的复用性,又让测试代码的可读性更强,完全符合Truth的设计理念。
内容的提问来源于stack exchange,提问作者GeenDutchman
相关产品推荐
相关产品推荐

