You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为多继承接口的对象层级编写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的基础上做一点改进,让语义和复用性更完善:

  1. 给每个接口Subject提供静态工厂方法,比如在TaggableSubject里:
public static TaggableSubject assertThat(Taggable actual) {
    return Truth.assertAbout(taggableSubject()).that(actual);
}

private static SubjectFactory<TaggableSubject, Taggable> taggableSubject() {
    return TaggableSubject::new;
}
  1. 在AreaSubject的转换方法里直接调用这些工厂方法,替代check("this"):
public TaggableSubject asTaggable() {
    return TaggableSubject.assertThat(actual);
}

public CreatureContainerSubject asCreatureContainer() {
    return CreatureContainerSubject.assertThat(actual);
}

这种方式既保留了方案1的复用性,又让测试代码的可读性更强,完全符合Truth的设计理念。

内容的提问来源于stack exchange,提问作者GeenDutchman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 03:01:11