Use Reasonable Tolerance Values

 class​ OxygenTankTest {
 
  @Test
 void​ testNewTankIsEmpty() {
  OxygenTank tank = OxygenTank.withCapacity(100);
  Assertions.assertEquals(0, tank.getStatus());
  }
 
  @Test
 void​ testFilling() {
  OxygenTank tank = OxygenTank.withCapacity(100);
 
  tank.fill(5.8);
  tank.fill(5.6);
 
» Assertions.assertEquals(0.114, tank.getStatus());
  }
 }

Here you can see tests that use floating-point numbers instead of integer values. From the source code alone, the tests seem good. The problem is that they’re going to fail, even if the code that’s tested is functionally correct.

Can you think of why? Here’s a tip: On our machine, the second test fails with the message expected: <0.114> but was <0.11399999999999999>. Obviously, the decimal places aren’t what we expected.

The problem here boils down to floating-point arithmetic,[38] the way how decimal numbers are represented with 64 or 32 bits. Java does this according to the IEEE Standard for Floating-Point Arithmetic (IEEE 754). We won’t go into the details here, but in a nutshell, it’s not possible to represent every floating-point number in a finite number of bits. That’s why basically all programming languages, Java included, only approximate floating-point numbers. For example, the decimal number 0.1 easily turns into a close approximation in memory, such as 0.100000000000000005551115123126. And if you only approximate something, there can be rounding errors.

In the test above, all decimals 5.6, 5.8, and 0.114 are only approximated with a double. Of course, we can specify these numbers directly in code, but as soon they’re combined at runtime with arithmetic operations, the result will only be a very close approximation to the expected number. And that’s why the assertion above fails.

When you test floating-point computations, you need to specify a precision:

 class​ OxygenTankTest {
 static​ ​final​ ​double​ TOLERANCE = 0.00001;
 
 
  @Test
 void​ testNewTankIsEmpty() {
  OxygenTank tank = OxygenTank.withCapacity(100);
 
  Assertions.assertEquals(0, tank.getStatus(), TOLERANCE);
  }
 
  @Test
 void​ testFilling() {
  OxygenTank tank = OxygenTank.withCapacity(100);
 
  tank.fill(5.8);
  tank.fill(5.6);
 
» Assertions.assertEquals(0.114, tank.getStatus(), TOLERANCE);
  }
 }

Exact matches aren’t reliable with floating-point arithmetic. Instead of pedantically insisting on an exact match, we need to tolerate a little bit of difference between the expected and actual values.

This is easy enough with JUnit. The assertion assertEquals(double expected, double actual, double delta) supports a tolerance value called delta. If you need to be exact up to two decimal places, use a tolerance value of 0.1*10^-2=0.001. Here, we want a precision of four decimal places, resulting in a tolerance value of at least 0.00001.

To sum up, you should be aware of precision and specify an acceptable tolerance level whenever you use assertEquals() with either float or double.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.253.31