Unit testing

Some parts of the examination require implementing methods adhering to a given specification. Consider an example:

Figure 304. Recommended reading Slide presentation Create comment in forum

Figure 305. Test categories Slide presentation Create comment in forum
  • Unit test: Test individual methods, classes and packages in isolation.

  • Integration Test: Test a group of associated components/classes.

  • Acceptance / Functional Test: Operate on a fully integrated system, testing against the user interface.

  • Regression Test: Ensure system integrity after (implementation) change.

  • Load test: Responsiveness vs. system load.


Figure 306. Example: Computing prime numbers Slide presentation Create comment in forum

Informal problem specification:

A prime number is a whole number greater than 1 whose only factors are 1 and itself.

Examples: 2, 3, 5, 7, 11, 13, 17, 23, ...


Figure 307. Test driven development Slide presentation Create comment in forum

First write tests, then implement.


Figure 308. Steps in Unit Testing Slide presentation Create comment in forum
  1. Specify but not yet implement classes / methods.

  2. Write skeleton (dummy) implementations.

  3. Write corresponding unit tests.

  4. Implement skeleton.

  5. Test your implementation.


Figure 309. Step 1 + 2: Specify method, write skeleton Slide presentation Create comment in forum
/**
 * Dealing with prime numbers.
 */
public class Prime {
  /**
   * Check whether a given value is prime or not 
   * @param value A positive value
   * @return true if and only if value is a prime number.
   */
  public static boolean isPrime(int value) {
    return true ; //TODO: Dummy value to be implemented correctly 
  }
}

An informal specification of the method's expected behaviour. This comprises the descriptions of all method parameters among with the expected outcome.

Note that boolean isPrime(int value) is being specified as a partial method: The value parameter must not contain negative values. Thus negative values likely lead to unexpected results.

Since our current implementation is just a skeleton we simply return a constant value. Other choices:

  • return false;

  • return 231 < value;

  • ...

In fact every syntactically correct expression will do since we defer our implementation to a later step. Thus the only requirement with respect to our code is its ability to get compiled. Returning a single value obviously is the most simple way to comply.


The static method getFirstNegative(...) may be executed from an arbitrary context:

Figure 310. Obviously execution yet flawed Slide presentation Create comment in forum
for (int i = 1; i < 20;i++) {
  System.out.println(i + " is " + (Prime.isPrime(i) ? " a " : " not a ")
    + " prime number");
}
1 is a prime number
2 is a prime number
3 is a prime number
4 is a prime number
5 is a prime number
...

Figure 311. Sample test data Slide presentation Create comment in forum
Input Expected output Input Expected output
1 false 7 true
2 true 8 false
3 true 9 false
4 false 10 false
5 true 11 true
6 false 12 false

Figure 312. Step 3: Junit based specification test Slide presentation Create comment in forum
public class PrimeTest {

  @Test  public void test_1_isNotPrime() {
    Assert.assertFalse(Prime.isPrime(1));
  }
  @Test  public void test_2_isPrime() {
    Assert.assertTrue(Prime.isPrime(2));
  }

  void someOrdinaryMethod()  {...}
...

A @Test annotation triggers automatic method execution by the Junit framework.

Any methods not being annotated by @Test will not be called by the Junit framework. They may however be called by e.g. test_1_isNotPrime() thus acting as helper methods.


Figure 313. Junit skeleton test result (Maven CLI) Slide presentation Create comment in forum
goik@goiki Prime_v01> mvn test
...
Running de.hdm_stuttgart.mi.sd1.PrimeTest
Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, 
         Time elapsed: 0.065 sec <<< FAILURE!
...
test_1_isNotPrime(de.hdm_stuttgart.mi.sd1.PrimeTest)
         Time elapsed: 0.001 sec  <<< FAILURE!
java.lang.AssertionError
	at org.junit.Assert.fail(Assert.java:86)
	at org.junit.Assert.assertTrue(Assert.java:41)
	at org.junit.Assert.assertFalse(Assert.java:64)
	at org.junit.Assert.assertFalse(Assert.java:74)
...

Similar to a main(...) method execution we may execute Junit Tests using our IDE:

Figure 314. Junit skeleton test result (IDE) Slide presentation Create comment in forum
Junit skeleton test result (IDE)

Test test_1_isNotPrime() accidentally failing due to dummy implementation Figure 309, “Step 1 + 2: Specify method, write skeleton ”.

Test test_2_isPrime() accidentally succeeding due to dummy implementation Figure 309, “Step 1 + 2: Specify method, write skeleton ”.


Before replacing the skeleton implementation Figure 309, “Step 1 + 2: Specify method, write skeleton ” we supply additional tests:

Figure 315. Step 3: Providing more prime tests Slide presentation Create comment in forum
@Test public void test_Primes() {
  Assert.assertTrue(Prime.isPrime(3));
  Assert.assertTrue(Prime.isPrime(5));
  Assert.assertTrue(Prime.isPrime(7));
  Assert.assertTrue(Prime.isPrime(11));
    ...  }
@Test public void testOddNonPrimes() {
  Assert.assertFalse(Prime.isPrime(9));
  Assert.assertFalse(Prime.isPrime(15));
  Assert.assertFalse(Prime.isPrime(21));...}

Since all even numbers greater than two are non-prime we add:

Figure 316. Step 3: Prime mass testing Slide presentation Create comment in forum
@Test public void testEvenNonPrimes() {
  for (int i = 2; i < 100; i++) {
    Assert.assertFalse(Prime.isPrime(2 * i));
  }
}

Now its time actually implementation isPrime():

Figure 317. Step 4: Implement skeleton Slide presentation Create comment in forum
public static boolean isPrime(int value) {
  for (int i = 2; i < value; i++) {
    if (0 == value % i) { // i divides value
      return false;
    }
  }
  return value != 1;
}

Figure 318. Step 5: Testing our first implementation Slide presentation Create comment in forum
goik@goiki Prime_v01> mvn test
...
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running de.hdm_stuttgart.mi.sd1.PrimeTest
Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.055 sec

Results :

Tests run: 5, Failures: 0, Errors: 0, Skipped: 0

Figure 319. Implementation observation Slide presentation Create comment in forum
101 / 2  = 50.5
101 / 3  = 33.6...
101 / 4  = 25.25
101 / 5  = 20.20
101 / 6  = 16.83...
101 / 7  = 14.43...
101 / 8  = 12.625
101 / 9  = 11.2...
101 / 10  = 10.1
101 / 11  = 9.18...
101 / 12  = 8.4...
101 / 13  = 7.7...
...

Figure 320. Changing the implementation Slide presentation Create comment in forum
public static boolean isPrime(int value) {
  for (int i = 2; i * i < value; i++) {
    if (0 == value % i) {
      return false;
    }
  }
  return value != 1;
}

Figure 321. Regression test Slide presentation Create comment in forum
Regression test

Figure 322. Correcting the implementation Slide presentation Create comment in forum
public static boolean isPrime(int value) {
  for (int i = 2; i * i <= value; i++) {
    if (0 == value % i) {
      return false;
    }
  }
  return value != 1;
}

Figure 323. Repeating regression test Slide presentation Create comment in forum
Repeating regression test


Figure 325. Caution comparing float / double !! Slide presentation Create comment in forum
Caution comparing float / double !!

Figure 326. Weird arithmetics? Slide presentation Create comment in forum
java.lang.AssertionError: Use assertEquals(expected, actual, delta)
  to compare floating-point numbers

at org.junit.Assert.assertEquals(Assert.java:656)
at qq.doubleCompareTest.test1_3(doubleCompareTest.java:15)
...

Figure 327. Limited representation precision Slide presentation Create comment in forum
System.out.println("3.6 - 3 * 1.2 == " + (3.6 - 3 * 1.2));

Result:

3.6 - 3 * 1.2 == 4.440892098500626E-16

Figure 328. Solving the issue Slide presentation Create comment in forum
public class doubleCompareTest {
  static final double delta = 1E-15;
  /**
   * Comparing 3.6 and 3 * 1.2 within delta's limit
   */
  @Test
  public void test1_3() {
    Assert.assertEquals(3.6, 3 * 1.2 , delta);
  }
}

exercise No. 112

Summing up integers to a given limit Create comment in forum

Q:

Suppose an arbitrary number n is given e.g n=5. We want to compute the sum of all integers ranging from 1 to 5:

1 + 2 + 3 + 4 + 5 = 15

Implement the following method by using a loop:

/**
 * Summing up all integers starting from 0 up to and including a given limit
 * Example: Let the limit be 5, then the result is 1 + 2 + 3 + 4 + 5
 *
 * @param limit The last number to include into the computed sum
 * @return The sum of 1 + 2 + ... + limit
 */
public static long getSum (int limit) {
   ...
}

For the sake of getting used to it write some unit tests beforehand.

BTW: Is it possible to avoid the loop completely achieving the same result?

A:

The solution's class de.hdm_stuttgart.de.sd1.sum.Summing is being contained within:

We start by defining unit tests beforehand:

/**
 * Testing negative values and zero.
 */
 @Test
 public void testNonPositiveLimits() {
   assertEquals(0, Summing.getSum(-5));
   assertEquals(0, Summing.getSum(0));
 }
/**
 * Testing positive values.
 */
@Test
  public void testPositiveLimits() {
    assertEquals(1, Summing.getSum(1));
    assertEquals(3, Summing.getSum(2));
    assertEquals(15, Summing.getSum(5));
    assertEquals(5050, Summing.getSum(100)); // Carl Friedrich Gauss at school
    assertEquals(2147450880, Summing.getSum(65535));// Highest Integer.MAX_VALUE compatible value
}

The crucial part here is determining 1 + 2 + ... + 65535 being equal to 2147450880. Legend has it the young Carl Friedrich Gauss at school was asked summing up 1 + 2 + ... + 99 + 100. His teacher was keen for some peaceful time but the young pupil decided otherwise:

$\underbrace{1 + \underbrace{2 + \underbrace{3 + \dots + 98}_{101} + 99}_{101} + 100}_{101}$

Since there are 50 such terms the result is 50 × 101 = 5050 . Generalizing this example we have:

i = 1 n i = n ( n + 1 ) 2

For the current exercise we do not make use of this. Instead we implement Summing.getSum(...) by coding a loop:

/**
 * Summing up all integers starting from 0 up to and including a given limit
 * Example: Let the limit be 5, then the result is 1 + 2 + 3 + 4 + 5
 *
 * @param limit The last number to include into the computed sum
 * @return The sum of 1 + 2 + ... + limit
 */
  public static long getSum (int limit) {
    int sum = 0;
    for (int i = 1; i <= limit; i++) {
      sum += i;
    }
    return sum;
  }
}

This passes all tests.

exercise No. 113

Summing up, the better way Create comment in forum

Q:

The previous solution of Summing up integers to a given limit suffers from a performance issue. When dealing with large values like 65535 looping will take some time:

long start = System.nanoTime();
System.out.println("1 + 2 + ... + 65535" + "=" + getSum(65535));
long end = System.nanoTime();
System.out.println("Elapsed time: " + (end - start) + " nanoseconds");
1 + 2 + ... + 65535=2147450880
Elapsed time: 1169805 nanoseconds

Barely more than one millisecond seems to be acceptable. But using the method for calculations inside some tight loop this might have a serious negative performance impact.

Thus implement a better (quicker) solution avoiding the loop by using the explicit form. When you are finished re-estimate execution time and compare the result to the previous solution.

Provide unit tests and take care of larger values. What is the largest possible value? Test it as well!

A:

The solution's class de.hdm_stuttgart.de.sd1.sum.Summing is being contained within:

Since only our implementation changes we reuse our existing unit tests. Our first straightforward implementation attempt reads:

public static long getSum (int limit) {
  return limit * (limit + 1) / 2;
}

This fails both unit tests. The first error happens at:

assertEquals(0, Summing.getSum(-5));
java.lang.AssertionError:
Expected :0
Actual   :10

We forgot to deal with negative limit values. Our sum is supposed to start with 0 so negative limit values should yield 0 like in our loop based solution:

public static long getSum(int limit) {
  if (limit < 0) {
    return 0;
  } else {
    return limit * (limit + 1) / 2; // Order of operation matters
  }
}

This helps but one test still fails:

assertEquals(2147450880, Summing.getSumUsingGauss(65535));
java.lang.AssertionError:
Expected :2147450880
Actual   :-32768

This actually is a showstopper for large limit values: The algebraic value of limit * (limit + 1) / 2 might still fit into an int. But limit * (limit + 1) itself not yet divided by 2 may exceed Integer.MAX_VALUE. Since the multiplication happens prior to dividing by 2 we see this overflow error happen.

Solving this issue requires changing the order of operations avoiding arithmetic overflow. Unfortunately the following simple solution does not work either:

public static long getSum(int limit) {
  if (limit < 0) {
    return 0;
  } else {
    return limit / 2 * (limit + 1);
  }
}

This is only correct if limit is even. Otherwise division by 2 leaves us with a remainder of 1.

However if limit is uneven the second factor limit + 1 will be even. This observation leads us to the final solution:

public static long getSum(int limit) {
  if (limit < 0) {
    return 0;
  } else if (0 == limit % 2){       // even limit, divide by 2
    return limit / 2 * (limit + 1); // Avoiding arithmetic overflow
  } else {                          // uneven limit, divide (limit + 1 ) by 2
    return (limit + 1) / 2 * limit; // Avoiding arithmetic overflow
  }
}

This passes all tests. We finally reconsider execution time:

1 + 2 + ... + 65535=2147450880
Elapsed time: 25422 nanoseconds

Thus execution is roughly 46 times faster compared to the loop based approach. This is not surprising since loop execution is expensive in terms of performance.