【问题标题】:How to assert the number of assertions (or some other way to enforce that all members have been tested in an assertion)?如何断言断言的数量(或其他强制所有成员都已在断言中测试的方式)?
【发布时间】:2019-06-02 03:04:54
【问题描述】:

在我的班级的某个单元测试中,我想断言已为所有成员字段编写了一个断言。也就是说,给定

public class Foo {
  int a;
  int b;
  String c;
}

我想做类似的事情

@Test
public void testFieldsAgainstExternalModel(Foo model, Bar externalModel) {
  assertEquals(model.a, externalModel.getA());
  assertEquals(model.b, externalModel.getSomethingNamedDifferently());
  assertEquals(model.c, externalModel.getC().toString());
  assertNumberOfAssertionsInThisTest(3); // <-- How do I do this?
}

当然,计算断言的数量并不能确保它们与每个具有断言的字段有任何关系。但是,如果开发人员将字段添加到 Foo 并忘记更新此测试,我只是想编写一个失败的测试。

  • 最简单的方法:我能想到的最简单的方法是使用反射来断言 Foo 拥有的字段数量,例如 assertEquals(3, Foo.getDeclaredFields().count())。但是,我可以很容易地看到程序员在没有实际添加断言的情况下增加了数字。

  • 仍然是一种幼稚的方法,但可以接受:我认为如果我至少可以统计断言的数量,它将足以引导程序员达到预期的需求.

  • 更准确的方法:当然,最好的方法似乎是实际保留一个表,其中包含断言中出现的字段(例如通过包装像assertEqualsWithCounters) 这样的方法,但实际上我的情况有点复杂。例如,我的模型实际上是一个生成的类 (@AutoMatter),我将在断言的两边都使用 getter,因此很难真正推断出断言“触及”了哪个字段。这就是为什么只计算断言并对实际类型和计​​数保持不可知论对我来说是最简单的。

【问题讨论】:

  • 一个更简单的方法是 (1) 使用 Lombok 使您的类生成一个 toString,因此您知道它包含所有成员变量。 (2) 只是比较toString的结果。
  • @Zag 但是依赖解析 toString() 输出是非常糟糕的做法。如果那个类已经有一个 toString() 了,或者有些有呢?
  • 使用OpenPOJO

标签: java mockito assertion


【解决方案1】:

但是,我只是想编写一个测试,如果开发人员添加了一个失败的 字段为 Foo 并忘记更新此测试。

我的公司使用映射框架将bean从db映射,字段与数据库一一对应,映射到我们返回给客户端的模型bean,我们可以根据需要进行修改。我相信这是一个常见的用例,本质上是您想要达到的目的。 您应该不太关心测试对Foo 的更改,而应该更关心确保从FooBar 的映射不会中断。

Class BeanDb{
 private int a;
 private int b;
 ...
}

Class Bean{
 private int a;
 private int b;
 private String somethingElse;
 ...
}

然后我们有一个使用isEqualToComparingFieldByFieldRecursively 比较两个bean 的proivder 测试。

private Bean bean = new Bean();
private Bean beanDb = new BeanDb();
private BeanProvider provider = new BeanProvider();

@Before
public void setup(){
 bean.setA(1);
 bean.setB(2);

 beanDb.setA(1);
 beanDb.setB(2);
}

@Test
public void prepareForDatabase(){
    BeanDb db = provider.prepareForDatabase(bean);

    assertThat(beanDb).isEqualToComparingFieldByFieldRecursively(db);
}

@Test
public void populateFromDatabase(){
    Bean model = provider.populateFromDatabase(beanDb);

    assertThat(model).isEqualToComparingFieldByFieldRecursively(bean);
}

这实际上捕获了很多错误。如果模型 bean / db bean 发生变化,测试将在我们的持续集成套件中中断。这里需要注意的是isEqualToComparingFieldByFieldRecursively 不会捕获新添加的字段。 当我查看代码/拉取请求并且 BeanDb 或提供程序已更新时,我会问:“您是否更新了提供程序测试?”

【讨论】:

  • 我非常喜欢你的回答,所以我也添加了我的变体 ;-)
  • 嗯,添加新字段是我想要在我的案例中自动捕获的主要原因之一......但是谢谢,自从你提出建议以来,我一直牢记映射器。
【解决方案2】:

我认为您可能需要将PowerMockito 用作Mockito does not support mocking static methods

我没有测试下面的代码 - 但我的想法是执行以下操作:

@RunWith(PowerMockRunner.class)
public class Tester {
    public class Foo {
        int a = 5;
        int b = 6;
        String c = "7";
    }
    class Bar {

        public int getA() {
            return 5;
        }

        public int getSomethingNamedDifferently() {
            return 6;
        }

        public Integer getC() {
            return 7;
        }
    }

    @Test
    public void testFieldsAgainstExternalModel() {
        testFieldsAgainstExternalModel(new Foo(), new Bar());
    }
    public void testFieldsAgainstExternalModel(Foo model, Bar externalModel) {
        Assert spiedAssert = PowerMockito.spy(Assert.class);

        spiedAssert.assertEquals(model.a, externalModel.getA());
        spiedAssert.assertEquals(model.b, externalModel.getSomethingNamedDifferently());
        spiedAssert.assertEquals(model.c, externalModel.getC().toString());
        PowerMockito.verify(spiedAssert, times(3)).assertEquals(any(), any());
    }
}

【讨论】:

  • 一个好主意,但老实说,这是一个肮脏的解决方法。首先,静态模拟会很快导致各种问题。例如,您需要遵循许多规则,例如您需要 PrepareForTest 注释。您的示例中缺少哪个。所以我想知道你的代码是否真的有效。我还想知道那些监视断言的人是否真的在进行断言检查。您是否验证过它们在断言不成立时触发
【解决方案3】:

我认为答案是

assertNumberOfAssertionsInThisTest(3); // <-- How do I do this?

是:你会那样做。这个测试几乎没用。

如果只有 3 个字段,您可以用眼睛看到您正在检查这三个字段。您可能正在考虑该类中的 15 个字段,但随后:

assertEquals(model.a, externalModel.getA());
assertEquals(model.b, externalModel.getSomethingNamedDifferently());
assertEquals(model.c, externalModel.getC().toString());
assertEquals(model.c, externalModel.getC().toString());
assertEquals(model.d, externalModel.getD().toString());

... assertEquals(model.o, externalModel.getO().toString());

assertNumberOfAssertionsInThisTest(15); 

不会帮助你。你猜怎么着:当我把我伪造的例子放在一起时,我省略了属性 14 (model.n),但是我有一个复制/粘贴错误并检查了 model.c 两次,导致总数为 15。

因此,我同意用户 Phil 的观点,只是建议另一种方法,如下所示:

  • 确保 bean 类具有合理的equals()(可能还有hashCode() 方法)。要么使用 Lombok,要么拥有一个使用 EqualsBuilder 的基 bean 类(我们实际上在我们的项目中使用了后者)
  • 然后让测试代码使用伪造的 bean,并简单地比较该伪造的 bean 是否等于与您的生产代码从(可能是伪造的)外部模型对象创建的 bean。

【讨论】:

  • 啊,对不起。看,我正在建造的地方正在做这样的事情:assertEquals(3, MyBaseClass.class.getDeclaredFields().length);。因此,如果将字段添加到基类,则断言失败,提示程序员需要做一些事情。但我同意这并不是最好的设计。
  • @AndrewCheong 正如所说:对于 3 个字段,您可以看到。当你有 15 个字段时,你可以得到正确的数字,但仍然只能轻松测试 14 个字段。
猜你喜欢
  • 1970-01-01
  • 2020-02-11
  • 2019-11-22
  • 1970-01-01
  • 2020-03-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多