【问题标题】:How can I compare BigDecimals to make my tests pass? [duplicate]如何比较 BigDecimals 以使我的测试通过? [复制]
【发布时间】:2016-08-26 10:07:37
【问题描述】:

我在JUnit 测试中遇到了以下同样奇怪的情况。

所以我有这个测试方法:

@Test
public void getNavInfoTest() throws ParseException {

    TirAliquotaRamoI expectedObject = new TirAliquotaRamoI();

    DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
    Date date;

    date = formatter.parse("2015-08-01");

    Date dataInizio = formatter.parse("2015-08-01");
    Date dataFine = formatter.parse("2100-12-31");

    expectedObject.setDataElaborazione(date);
    expectedObject.setTassoLordoAnnuoAppl(BigDecimal.ZERO);
    expectedObject.setTassoGiornalieroNetto(BigDecimal.ZERO);
    expectedObject.setAliquota(BigDecimal.ONE);
    expectedObject.setDataInizio(dataInizio);
    expectedObject.setDataFine(dataFine);

    TirAliquotaRamoI tirAliquotaRamoI = pucManager.getNavInfo(date);

    assertEquals(tirAliquotaRamoI.getAliquota(), expectedObject.getAliquota());

}

最后我只是测试tirAliquotaRamoI.getAliquota()(从数据库查询中获得)是否具有为创建的expectedObject定义的相同字段的相同值:

assertEquals(tirAliquotaRamoI.getAliquota(), expectedObject.getAliquota());

因此,预期对象的字段是使用BigDecimal.ONE 常量创建的,使用调试器我可以看到它的值为1

tirAliquotaRamoI.getAliquota()得到的值为1.000000000

所以,理论上,两者都代表相同的值 1 但测试失败,我得到:

java.lang.AssertionError: expected:<1.000000000> but was:<1>
    at org.junit.Assert.fail(Assert.java:88)
    at org.junit.Assert.failNotEquals(Assert.java:834)
    at org.junit.Assert.assertEquals(Assert.java:118)
    at org.junit.Assert.assertEquals(Assert.java:144)
    at com.fideuram.dbmanager.PucManagerTest.getNavInfoTest(PucManagerTest.java:90)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

如果两者都代表 ame 1.0 值,为什么?如何解决此问题以通过测试?

【问题讨论】:

    标签: java junit bigdecimal


    【解决方案1】:

    原因在于BigDecimalequals是如何实现的。 BigDecimal.ONE 构造为 new BigDecimal(BigInteger.ONE, 1, 0, 1)。 equals方法的实现方式如下(来自JDK源码):

    public boolean equals(Object x) {
        if (!(x instanceof BigDecimal))
            return false;
        BigDecimal xDec = (BigDecimal) x;
        if (x == this)
            return true;
        if (scale != xDec.scale)
            return false;
        long s = this.intCompact;
        long xs = xDec.intCompact;
        if (s != INFLATED) {
            if (xs == INFLATED)
                xs = compactValFor(xDec.intVal);
            return xs == s;
        } else if (xs != INFLATED)
            return xs == compactValFor(this.intVal);
    
        return this.inflated().equals(xDec.inflated());
    }
    

    所以两个BigDecimals 只有当它们具有相同的比例时才相等。 ONEs 的比例小于返回的 BigDecimal 的比例,所以它们不相等。

    我看到一些回答说你可以取 floatValue of BigDecimal。好吧,你不能。 BigDecimal 用于处理更大的数字,因此在这种情况下它会起作用,但这是一个糟糕的模式。

    不过还好我们可以使用compareTo方法!

    assertTrue(tirAliquotaRamoI.getAliquota().compareTo(expectedObject.getAliquota()) == 0);
    

    它并不完美,但会起作用!

    【讨论】:

    • 为什么不完美?
    • @Line 首先这条线很长:)
    【解决方案2】:

    BigDecimal 比较也始终使用比例,因此您的测试失败。

    详情请见Javadoc

    如果您只对值感兴趣而不对比例感兴趣,那么在比较时考虑使用 stripTrailingZeros()。

    assertEquals(
        tirAliquotaRamoI.getAliquota().stripTrailingZeros(),
        expectedObject.getAliquota().stripTrailingZeros()
    );
    

    【讨论】:

      【解决方案3】:

      在我看来他们有不同的scales

      试试compareTo

       assertEquals(0, tirAliquotaRamoI.compareTo(expectedObject.getAliquota());
      

      或者,如果您关心匹配比例,那么您必须使用 setScale(int newScale) 更改任一对象的比例以匹配另一个。

      希望对您有所帮助。

      感谢@David SN 的更正。

      【讨论】:

      • 这种方法的问题在于,当测试失败时,您将不知道原因,因为您无法诊断出实际值是什么
      • assertTrue 有一个布尔参数。你应该使用assertTrue(tirAliquotaRamoI.compareTo(expectedObject.getAliquota() == 0);assertEquals(0, tirAliquotaRamoI.compareTo(expectedObject.getAliquota());
      • 或在我的示例中使用 stripTrailingZeroes()
      • tddmonkey:但是这个测试不只是为了确保两个值相等吗? DavidSN:我不敢相信我错过了,谢谢,我会编辑答案。
      【解决方案4】:

      虽然我不知道 JUnit 方法,但我猜问题与:

      公共布尔等于(对象 x)

      将此 BigDecimal 与指定的 Object 进行比较是否相等。 与 compareTo 不同,此方法认为两个 BigDecimal 对象相等 仅当它们在值和规模上相等时(因此 2.0 不等于 用此方法比较时为 2.00)。

      (见BigDecimal.equals() docs

      assertEquals() 可能正在使用上述方法,它比较您的数字的 比例

      您可能想尝试assertTrue() 中建议的方法x870eaddd's answer

      【讨论】:

        【解决方案5】:

        试试这个:

        assertEquals((int)tirAliquotaRamoI.getAliquota(), (int)expectedObject.getAliquota());
        

        我认为AssertEquals 只能比较同一类型的两个变量(intintdoubledouble...)。在您的测试中,一个是小数,另一个不是,所以它可能无法比较它们。

        【讨论】:

        • 两者都是 BigDecimal 并且可以有十进制数字
        【解决方案6】:

        tirAliquotaRamoI.getAliquota() 的数据类型是什么?是否也是BigDecimal,因为BigDecimal.ONEBigDecimal 数据类型。

        如果 tirAliquotaRamoI.getAliquota() 是 Long 或 Int 或 Float,则可以使用

        assertEquals(tirAliquotaRamoI.getAliquota(), BigDecimal.ONE.intValue())

        assertEquals(tirAliquotaRamoI.getAliquota(), BigDecimal.ONE.longValue())

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多